diff --git a/.github/workflows/release-go-task.yml b/.github/workflows/release-go-task.yml
index db0d696f..b19e2b8d 100644
--- a/.github/workflows/release-go-task.yml
+++ b/.github/workflows/release-go-task.yml
@@ -9,8 +9,6 @@ env:
# The project's folder on Arduino's download server for uploading builds
AWS_PLUGIN_TARGET: TODO
ARTIFACT_NAME: dist
- # TODO: Remember to REMOVE binaries folder as soon as it is removed from the project
- PROVISIONING_BINARIES_FOLDER: binaries
on:
push:
@@ -104,7 +102,6 @@ jobs:
# This step performs the following:
# 1. Repackage the signed binary replaced in place by Gon (ignoring the output zip file)
# 2. Recalculate package checksum and replace it in the nnnnnn-checksums.txt file
- # TODO: Remember to REMOVE binaries folder as soon as it is removed from the project ({{.PROVISIONING_BINARIES_FOLDER}})
run: |
# GitHub's upload/download-artifact@v2 actions don't preserve file permissions,
# so we need to add execution permission back until the action is made to do this.
@@ -112,7 +109,7 @@ jobs:
TAG="${GITHUB_REF/refs\/tags\//}"
tar -czvf "${{ env.DIST_DIR }}/${{ env.PROJECT_NAME }}_${TAG}_macOS_64bit.tar.gz" \
-C ${{ env.DIST_DIR }}/${{ env.PROJECT_NAME }}_osx_darwin_amd64/ ${{ env.PROJECT_NAME }} \
- -C ../../ ${{ env.PROVISIONING_BINARIES_FOLDER }} LICENSE.txt
+ -C ../../ LICENSE.txt
CHECKSUM="$(shasum -a 256 ${{ env.DIST_DIR }}/${{ env.PROJECT_NAME }}_${TAG}_macOS_64bit.tar.gz | cut -d " " -f 1)"
perl \
-pi \
diff --git a/DistTasks.yml b/DistTasks.yml
index 0151ded1..70b95b68 100644
--- a/DistTasks.yml
+++ b/DistTasks.yml
@@ -39,7 +39,6 @@ tasks:
Windows_32bit:
desc: Builds Windows 32 bit binaries
dir: "{{.DIST_DIR}}"
- # TODO: Remember to REMOVE binaries folder as soon as it is removed from the project
cmds:
- |
docker run -v `pwd`/..:/home/build -w /home/build \
@@ -50,7 +49,7 @@ tasks:
cp {{.PLATFORM_DIR}}/{{.PROJECT_NAME}}.exe ../
cd ..
- zip -r {{.DIST_DIR}}/{{.PACKAGE_NAME}} {{.PROJECT_NAME}}.exe {{.PROVISIONING_BINARIES_FOLDER}} LICENSE.txt
+ zip -r {{.DIST_DIR}}/{{.PACKAGE_NAME}} {{.PROJECT_NAME}}.exe LICENSE.txt
cd {{.DIST_DIR}}
sha256sum {{.PACKAGE_NAME}} >> {{.CHECKSUM_FILE}}
@@ -75,7 +74,7 @@ tasks:
cp {{.PLATFORM_DIR}}/{{.PROJECT_NAME}}.exe ../
cd ..
- zip -r {{.DIST_DIR}}/{{.PACKAGE_NAME}} {{.PROJECT_NAME}}.exe {{.PROVISIONING_BINARIES_FOLDER}} LICENSE.txt
+ zip -r {{.DIST_DIR}}/{{.PACKAGE_NAME}} {{.PROJECT_NAME}}.exe LICENSE.txt
cd {{.DIST_DIR}}
sha256sum {{.PACKAGE_NAME}} >> {{.CHECKSUM_FILE}}
@@ -98,7 +97,7 @@ tasks:
--build-cmd "{{.BUILD_COMMAND}}" \
-p "{{.BUILD_PLATFORM}}"
- tar cz -C {{.PLATFORM_DIR}} {{.PROJECT_NAME}} -C ../.. {{.PROVISIONING_BINARIES_FOLDER}} LICENSE.txt -f {{.PACKAGE_NAME}}
+ tar cz -C {{.PLATFORM_DIR}} {{.PROJECT_NAME}} -C ../.. LICENSE.txt -f {{.PACKAGE_NAME}}
sha256sum {{.PACKAGE_NAME}} >> {{.CHECKSUM_FILE}}
vars:
@@ -120,7 +119,7 @@ tasks:
--build-cmd "{{.BUILD_COMMAND}}" \
-p "{{.BUILD_PLATFORM}}"
- tar cz -C {{.PLATFORM_DIR}} {{.PROJECT_NAME}} -C ../.. {{.PROVISIONING_BINARIES_FOLDER}} LICENSE.txt -f {{.PACKAGE_NAME}}
+ tar cz -C {{.PLATFORM_DIR}} {{.PROJECT_NAME}} -C ../.. LICENSE.txt -f {{.PACKAGE_NAME}}
sha256sum {{.PACKAGE_NAME}} >> {{.CHECKSUM_FILE}}
vars:
@@ -142,7 +141,7 @@ tasks:
--build-cmd "{{.BUILD_COMMAND}}" \
-p "{{.BUILD_PLATFORM}}"
- tar cz -C {{.PLATFORM_DIR}} {{.PROJECT_NAME}} -C ../.. {{.PROVISIONING_BINARIES_FOLDER}} LICENSE.txt -f {{.PACKAGE_NAME}}
+ tar cz -C {{.PLATFORM_DIR}} {{.PROJECT_NAME}} -C ../.. LICENSE.txt -f {{.PACKAGE_NAME}}
sha256sum {{.PACKAGE_NAME}} >> {{.CHECKSUM_FILE}}
vars:
@@ -164,7 +163,7 @@ tasks:
--build-cmd "{{.BUILD_COMMAND}}" \
-p "{{.BUILD_PLATFORM}}"
- tar cz -C {{.PLATFORM_DIR}} {{.PROJECT_NAME}} -C ../.. {{.PROVISIONING_BINARIES_FOLDER}} LICENSE.txt -f {{.PACKAGE_NAME}}
+ tar cz -C {{.PLATFORM_DIR}} {{.PROJECT_NAME}} -C ../.. LICENSE.txt -f {{.PACKAGE_NAME}}
sha256sum {{.PACKAGE_NAME}} >> {{.CHECKSUM_FILE}}
vars:
@@ -214,7 +213,7 @@ tasks:
--build-cmd "{{.BUILD_COMMAND}}" \
-p "{{.BUILD_PLATFORM}}"
- tar cz -C {{.PLATFORM_DIR}} {{.PROJECT_NAME}} -C ../.. {{.PROVISIONING_BINARIES_FOLDER}} LICENSE.txt -f {{.PACKAGE_NAME}}
+ tar cz -C {{.PLATFORM_DIR}} {{.PROJECT_NAME}} -C ../.. LICENSE.txt -f {{.PACKAGE_NAME}}
sha256sum {{.PACKAGE_NAME}} >> {{.CHECKSUM_FILE}}
vars:
@@ -236,7 +235,7 @@ tasks:
--build-cmd "{{.BUILD_COMMAND}}" \
-p "{{.BUILD_PLATFORM}}"
- tar cz -C {{.PLATFORM_DIR}} {{.PROJECT_NAME}} -C ../.. {{.PROVISIONING_BINARIES_FOLDER}} LICENSE.txt -f {{.PACKAGE_NAME}}
+ tar cz -C {{.PLATFORM_DIR}} {{.PROJECT_NAME}} -C ../.. LICENSE.txt -f {{.PACKAGE_NAME}}
sha256sum {{.PACKAGE_NAME}} >> {{.CHECKSUM_FILE}}
vars:
diff --git a/README.md b/README.md
index 8acd193a..4a040587 100644
--- a/README.md
+++ b/README.md
@@ -7,9 +7,8 @@ This code is licensed under the terms of the GNU Affero General Public License v
### Requirements
-This is all you need to use arduino-cloud-cli for device **provisioning**:
+This is all you need to use arduino-cloud-cli:
* A client ID and a secret ID, retrievable from the [cloud](https://create.arduino.cc/iot/integrations) by creating a new API key
- * The folder containing the precompiled provisioning firmwares (`binaries`) needs to be in the same location you run the command from
### Additional info
diff --git a/binaries/arduino.mbed_nano.nanorp2040connect.elf b/binaries/arduino.mbed_nano.nanorp2040connect.elf
deleted file mode 100755
index 5a445b78..00000000
Binary files a/binaries/arduino.mbed_nano.nanorp2040connect.elf and /dev/null differ
diff --git a/binaries/arduino.mbed_portenta.envie_m7.bin b/binaries/arduino.mbed_portenta.envie_m7.bin
deleted file mode 100755
index 17663d4b..00000000
Binary files a/binaries/arduino.mbed_portenta.envie_m7.bin and /dev/null differ
diff --git a/binaries/arduino.samd.mkr1000.bin b/binaries/arduino.samd.mkr1000.bin
deleted file mode 100755
index 9eb8d6ed..00000000
Binary files a/binaries/arduino.samd.mkr1000.bin and /dev/null differ
diff --git a/binaries/arduino.samd.mkrgsm1400.bin b/binaries/arduino.samd.mkrgsm1400.bin
deleted file mode 100755
index c7f8c6e9..00000000
Binary files a/binaries/arduino.samd.mkrgsm1400.bin and /dev/null differ
diff --git a/binaries/arduino.samd.mkrnb1500.bin b/binaries/arduino.samd.mkrnb1500.bin
deleted file mode 100755
index 95df1f4d..00000000
Binary files a/binaries/arduino.samd.mkrnb1500.bin and /dev/null differ
diff --git a/binaries/arduino.samd.mkrwifi1010.bin b/binaries/arduino.samd.mkrwifi1010.bin
deleted file mode 100755
index 3c3cb6b5..00000000
Binary files a/binaries/arduino.samd.mkrwifi1010.bin and /dev/null differ
diff --git a/binaries/arduino.samd.nano_33_iot.bin b/binaries/arduino.samd.nano_33_iot.bin
deleted file mode 100755
index 50c2881c..00000000
Binary files a/binaries/arduino.samd.nano_33_iot.bin and /dev/null differ
diff --git a/binaries/getdeveui.arduino.samd.mkrwan1300.bin b/binaries/getdeveui.arduino.samd.mkrwan1300.bin
deleted file mode 100755
index f8864ad0..00000000
Binary files a/binaries/getdeveui.arduino.samd.mkrwan1300.bin and /dev/null differ
diff --git a/binaries/getdeveui.arduino.samd.mkrwan1310.bin b/binaries/getdeveui.arduino.samd.mkrwan1310.bin
deleted file mode 100755
index 7e31365b..00000000
Binary files a/binaries/getdeveui.arduino.samd.mkrwan1310.bin and /dev/null differ
diff --git a/command/device/board.go b/command/device/board.go
new file mode 100644
index 00000000..f0eaf527
--- /dev/null
+++ b/command/device/board.go
@@ -0,0 +1,124 @@
+// This file is part of arduino-cloud-cli.
+//
+// Copyright (C) 2021 ARDUINO SA (http://www.arduino.cc/)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package device
+
+import (
+ "strings"
+
+ rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
+)
+
+var (
+ cryptoFQBN = []string{
+ "arduino:samd:nano_33_iot",
+ "arduino:samd:mkrwifi1010",
+ "arduino:mbed_nano:nanorp2040connect",
+ "arduino:mbed_portenta:envie_m7",
+ "arduino:samd:mkr1000",
+ "arduino:samd:mkrgsm1400",
+ "arduino:samd:mkrnb1500",
+ }
+ loraFQBN = []string{
+ "arduino:samd:mkrwan1310",
+ "arduino:samd:mkrwan1300",
+ }
+)
+
+// board contains details of a physical arduino board
+type board struct {
+ fqbn string
+ serial string
+ dType string
+ port string
+}
+
+// isCrypto checks if the board is a valid arduino board with a
+// supported crypto-chip
+func (b *board) isCrypto() bool {
+ for _, f := range cryptoFQBN {
+ if b.fqbn == f {
+ return true
+ }
+ }
+ return false
+}
+
+// isCrypto checks if the board is a valid LoRa arduino board
+func (b *board) isLora() bool {
+ for _, f := range loraFQBN {
+ if b.fqbn == f {
+ return true
+ }
+ }
+ return false
+}
+
+// boardFromPorts returns a board that matches all the criteria
+// passed in. If no criteria are passed, it returns the first board found.
+func boardFromPorts(ports []*rpc.DetectedPort, params *CreateParams) *board {
+ for _, port := range ports {
+ if portFilter(port, params) {
+ continue
+ }
+ boardFound := boardFilter(port.Boards, params)
+ if boardFound != nil {
+ b := &board{
+ fqbn: boardFound.Fqbn,
+ serial: port.SerialNumber,
+ dType: strings.Split(boardFound.Fqbn, ":")[2],
+ port: port.Address,
+ }
+ return b
+ }
+ }
+
+ return nil
+}
+
+// portFilter filters out the given port in the following cases:
+// - if the port parameter does not match the actual port address.
+// - if the the detected port does not contain any board.
+// It returns:
+// true -> to skip the port
+// false -> to keep the port
+func portFilter(port *rpc.DetectedPort, params *CreateParams) bool {
+ if len(port.Boards) == 0 {
+ return true
+ }
+ if params.Port != nil && *params.Port != port.Address {
+ return true
+ }
+ return false
+}
+
+// boardFilter looks for a board which has the same fqbn passed as parameter.
+// If fqbn parameter is nil, then the first board found is returned.
+// It returns:
+// - a board if it is found.
+// - nil if no board matching the fqbn parameter is found.
+func boardFilter(boards []*rpc.BoardListItem, params *CreateParams) (board *rpc.BoardListItem) {
+ if params.Fqbn == nil {
+ return boards[0]
+ }
+ for _, b := range boards {
+ if b.Fqbn == *params.Fqbn {
+ return b
+ }
+ }
+ return
+}
diff --git a/command/device/create_test.go b/command/device/board_test.go
similarity index 100%
rename from command/device/create_test.go
rename to command/device/board_test.go
diff --git a/command/device/create.go b/command/device/create.go
index c0c08f78..6042d93b 100644
--- a/command/device/create.go
+++ b/command/device/create.go
@@ -20,9 +20,7 @@ package device
import (
"errors"
"fmt"
- "strings"
- rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
"github.com/arduino/arduino-cloud-cli/arduino/cli"
"github.com/arduino/arduino-cloud-cli/internal/config"
"github.com/arduino/arduino-cloud-cli/internal/iot"
@@ -37,13 +35,6 @@ type CreateParams struct {
Fqbn *string // Board FQBN - Optional - If omitted then the first device found gets selected
}
-type board struct {
- fqbn string
- serial string
- dType string
- port string
-}
-
// Create command is used to provision a new arduino device
// and to add it to Arduino IoT Cloud.
func Create(params *CreateParams) (*DeviceInfo, error) {
@@ -62,6 +53,16 @@ func Create(params *CreateParams) (*DeviceInfo, error) {
return nil, err
}
+ if !board.isCrypto() {
+ return nil, fmt.Errorf(
+ "board with fqbn %s found at port %s is not a device with a supported crypto-chip.\n"+
+ "Try the 'create-lora' command instead if it's a LoRa device"+
+ " or 'create-generic' otherwise",
+ board.fqbn,
+ board.port,
+ )
+ }
+
conf, err := config.Retrieve()
if err != nil {
return nil, err
@@ -104,54 +105,3 @@ func Create(params *CreateParams) (*DeviceInfo, error) {
}
return devInfo, nil
}
-
-// boardFromPorts returns a board that matches all the criteria
-// passed in. If no criteria are passed, it returns the first board found.
-func boardFromPorts(ports []*rpc.DetectedPort, params *CreateParams) *board {
- for _, port := range ports {
- if portFilter(port, params) {
- continue
- }
- boardFound := boardFilter(port.Boards, params)
- if boardFound != nil {
- t := strings.Split(boardFound.Fqbn, ":")[2]
- b := &board{boardFound.Fqbn, port.SerialNumber, t, port.Address}
- return b
- }
- }
-
- return nil
-}
-
-// portFilter filters out the given port in the following cases:
-// - if the port parameter does not match the actual port address.
-// - if the the detected port does not contain any board.
-// It returns:
-// true -> to skip the port
-// false -> to keep the port
-func portFilter(port *rpc.DetectedPort, params *CreateParams) bool {
- if len(port.Boards) == 0 {
- return true
- }
- if params.Port != nil && *params.Port != port.Address {
- return true
- }
- return false
-}
-
-// boardFilter looks for a board which has the same fqbn passed as parameter.
-// If fqbn parameter is nil, then the first board found is returned.
-// It returns:
-// - a board if it is found.
-// - nil if no board matching the fqbn parameter is found.
-func boardFilter(boards []*rpc.BoardListItem, params *CreateParams) (board *rpc.BoardListItem) {
- if params.Fqbn == nil {
- return boards[0]
- }
- for _, b := range boards {
- if b.Fqbn == *params.Fqbn {
- return b
- }
- }
- return
-}
diff --git a/command/device/createlora.go b/command/device/createlora.go
index 4b8501b8..58e34590 100644
--- a/command/device/createlora.go
+++ b/command/device/createlora.go
@@ -1,11 +1,25 @@
+// This file is part of arduino-cloud-cli.
+//
+// Copyright (C) 2021 ARDUINO SA (http://www.arduino.cc/)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
package device
import (
"errors"
"fmt"
- "os"
- "path/filepath"
- "strings"
"time"
"github.com/arduino/arduino-cloud-cli/arduino/cli"
@@ -64,9 +78,19 @@ func CreateLora(params *CreateLoraParams) (*DeviceLoraInfo, error) {
return nil, err
}
- bin, err := deveuiBinary(board.fqbn)
+ if !board.isLora() {
+ return nil, fmt.Errorf(
+ "board with fqbn %s found at port %s is not a LoRa device."+
+ " Try the 'create' command instead if it's a device with a supported crypto-chip"+
+ " or 'create-generic' otherwise",
+ board.fqbn,
+ board.port,
+ )
+ }
+
+ bin, err := downloadProvisioningFile(board.fqbn)
if err != nil {
- return nil, fmt.Errorf("fqbn not supported for LoRa provisioning: %w", err)
+ return nil, err
}
logrus.Infof("%s", "Uploading deveui sketch on the LoRa board")
@@ -114,22 +138,6 @@ func CreateLora(params *CreateLoraParams) (*DeviceLoraInfo, error) {
return devInfo, nil
}
-// deveuiBinary gets the absolute path of the deveui binary corresponding to the
-// provisioned board's fqbn. It is contained in the local binaries folder.
-func deveuiBinary(fqbn string) (string, error) {
- // Use local binaries until they are uploaded online
- bin := filepath.Join("./binaries/", "getdeveui."+strings.ReplaceAll(fqbn, ":", ".")+".bin")
- bin, err := filepath.Abs(bin)
- if err != nil {
- return "", fmt.Errorf("getting the deveui binary: %w", err)
- }
- if _, err := os.Stat(bin); os.IsNotExist(err) {
- err = fmt.Errorf("%s: %w", "deveui binary not found", err)
- return "", err
- }
- return bin, nil
-}
-
// extractEUI extracts the EUI from the provisioned lora board.
func extractEUI(port string) (string, error) {
var ser serial.Port
diff --git a/command/device/provision.go b/command/device/provision.go
index 9baa8cbb..0cba536f 100644
--- a/command/device/provision.go
+++ b/command/device/provision.go
@@ -20,18 +20,51 @@ package device
import (
"encoding/hex"
"fmt"
- "os"
"path/filepath"
"strconv"
- "strings"
"time"
"github.com/arduino/arduino-cloud-cli/arduino"
+ "github.com/arduino/arduino-cloud-cli/internal/binary"
"github.com/arduino/arduino-cloud-cli/internal/iot"
"github.com/arduino/arduino-cloud-cli/internal/serial"
+ "github.com/arduino/go-paths-helper"
"github.com/sirupsen/logrus"
)
+// downloadProvisioningFile downloads and returns the absolute path
+// of the provisioning binary corresponding to the passed fqbn.
+func downloadProvisioningFile(fqbn string) (string, error) {
+ index, err := binary.LoadIndex()
+ if err != nil {
+ return "", err
+ }
+ bin := index.FindProvisionBin(fqbn)
+ if bin == nil {
+ return "", fmt.Errorf("provisioning binary for board %s not found", fqbn)
+ }
+ bytes, err := binary.Download(bin)
+ if err != nil {
+ return "", fmt.Errorf("downloading provisioning binary: %w", err)
+ }
+
+ // Save provision binary always in the same temporary folder to
+ // avoid wasting user's storage.
+ filename := filepath.Base(bin.URL)
+ path := paths.TempDir().Join("cloud-cli").Join(filename)
+ path.Parent().MkdirAll()
+ if err = path.WriteFile(bytes); err != nil {
+ return "", fmt.Errorf("writing provisioning binary: %w", err)
+ }
+ p, err := path.Abs()
+ if err != nil {
+ return "", fmt.Errorf("cannot retrieve absolute path of downloaded binary: %w", err)
+ }
+ return p.String(), nil
+}
+
+// provision is responsible for running the provisioning
+// procedures for boards with crypto-chip
type provision struct {
arduino.Commander
iot.Client
@@ -40,14 +73,7 @@ type provision struct {
id string
}
-type binFile struct {
- Bin string `json:"bin"`
- Filename string `json:"filename"`
- Fqbn string `json:"fqbn"`
- Name string `json:"name"`
- Sha256 string `json:"sha256"`
-}
-
+// run provisioning procedure for boards with crypto-chip
func (p provision) run() error {
bin, err := downloadProvisioningFile(p.board.fqbn)
if err != nil {
@@ -195,79 +221,6 @@ func (p provision) configBoard() error {
return nil
}
-func downloadProvisioningFile(fqbn string) (string, error) {
- // Use local binaries until they are uploaded online
- bin := filepath.Join("./binaries/", strings.ReplaceAll(fqbn, ":", ".")+".bin")
- bin, err := filepath.Abs(bin)
- if err != nil {
- return "", err
- }
- if _, err := os.Stat(bin); err == nil {
- return bin, nil
- }
-
- elf := filepath.Join("./binaries/", strings.ReplaceAll(fqbn, ":", ".")+".elf")
- elf, err = filepath.Abs(elf)
- if err != nil {
- return "", err
- }
- if _, err := os.Stat(elf); os.IsNotExist(err) {
- err = fmt.Errorf("%s: %w", "fqbn not supported", err)
- return "", err
- }
- return elf, nil
-
- // TODO: upload binaries on some arduino page and enable this flow
- //url := "https://api2.arduino.cc/iot/v2/binaries/provisioning?fqbn=" + fqbn
- //path, _ := filepath.Abs("./provisioning.bin")
-
- //cl := http.Client{
- //Timeout: time.Second * 3, // Timeout after 2 seconds
- //}
-
- //req, err := http.NewRequest(http.MethodGet, url, nil)
- //if err != nil {
- //err = fmt.Errorf("%s: %w", "request provisioning binary", err)
- //return "", err
- //}
- //res, err := cl.Do(req)
- //if err != nil {
- //err = fmt.Errorf("%s: %w", "request provisioning binary", err)
- //return "", err
- //}
-
- //if res.Body != nil {
- //defer res.Body.Close()
- //}
-
- //body, err := ioutil.ReadAll(res.Body)
- //if err != nil {
- //err = fmt.Errorf("%s: %w", "read provisioning request body", err)
- //return "", err
- //}
-
- //bin := binFile{}
- //err = json.Unmarshal(body, &bin)
- //if err != nil {
- //err = fmt.Errorf("%s: %w", "unmarshal provisioning binary", err)
- //return "", err
- //}
-
- //bytes, err := base64.StdEncoding.DecodeString(bin.Bin)
- //if err != nil {
- //err = fmt.Errorf("%s: %w", "decoding provisioning binary", err)
- //return "", err
- //}
-
- //err = ioutil.WriteFile(path, bytes, 0666)
- //if err != nil {
- //err = fmt.Errorf("%s: %w", "downloading provisioning binary", err)
- //return "", err
- //}
-
- //return path, nil
-}
-
func retry(tries int, sleep time.Duration, errMsg string, fun func() error) error {
var err error
for n := 0; n < tries; n++ {
diff --git a/go.mod b/go.mod
index 3ba2bd41..bea5d751 100644
--- a/go.mod
+++ b/go.mod
@@ -5,6 +5,7 @@ go 1.16
require (
github.com/antihax/optional v1.0.0
github.com/arduino/arduino-cli v0.0.0-20210607095659-16f41352eac3
+ github.com/arduino/arduino-fwuploader v0.0.0-20211202112845-b7f323ad97e2
github.com/arduino/go-paths-helper v1.6.1
github.com/arduino/go-win32-utils v0.0.0-20180330194947-ed041402e83b
github.com/arduino/iot-client-go v1.3.4-0.20211116175324-9a98dd4ad269
@@ -20,6 +21,7 @@ require (
github.com/spf13/viper v1.7.0
github.com/stretchr/testify v1.6.1
go.bug.st/serial v1.3.0
+ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8
golang.org/x/sys v0.0.0-20211209171907-798191bca915 // indirect
diff --git a/go.sum b/go.sum
index 327434ab..6745841e 100644
--- a/go.sum
+++ b/go.sum
@@ -46,12 +46,16 @@ github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
+github.com/arduino/arduino-cli v0.0.0-20210603144340-aef5a54882fa/go.mod h1:HNbHWr7qq+9M2rhzBUJIBIpCMRlB6+mptNDLMDZNlG0=
github.com/arduino/arduino-cli v0.0.0-20210607095659-16f41352eac3 h1:xiVTIUhB5Ewe2a9KYFc8BQDjDy8wXZ34kNiov7g0Ghs=
github.com/arduino/arduino-cli v0.0.0-20210607095659-16f41352eac3/go.mod h1:72+QkT2EYMLmAtoDk2SKlndl+KT/O9/wKCqUtsrIjTg=
+github.com/arduino/arduino-fwuploader v0.0.0-20211202112845-b7f323ad97e2 h1:ADkeoCxUZ5/uTy7d7e3XXsm11pXwVg2lvFOxmjDfKls=
+github.com/arduino/arduino-fwuploader v0.0.0-20211202112845-b7f323ad97e2/go.mod h1:t6n09kqbFg+AxuzXJFUZaC3i8VPczEZi2SyY7axs+Rs=
github.com/arduino/board-discovery v0.0.0-20180823133458-1ba29327fb0c h1:agh2JT96G8egU7FEb13L4dq3fnCN7lxXhJ86t69+W7s=
github.com/arduino/board-discovery v0.0.0-20180823133458-1ba29327fb0c/go.mod h1:HK7SpkEax/3P+0w78iRQx1sz1vCDYYw9RXwHjQTB5i8=
github.com/arduino/go-paths-helper v1.0.1/go.mod h1:HpxtKph+g238EJHq4geEPv9p+gl3v5YYu35Yb+w31Ck=
github.com/arduino/go-paths-helper v1.2.0/go.mod h1:HpxtKph+g238EJHq4geEPv9p+gl3v5YYu35Yb+w31Ck=
+github.com/arduino/go-paths-helper v1.5.0/go.mod h1:V82BWgAAp4IbmlybxQdk9Bpkz8M4Qyx+RAFKaG9NuvU=
github.com/arduino/go-paths-helper v1.6.0/go.mod h1:V82BWgAAp4IbmlybxQdk9Bpkz8M4Qyx+RAFKaG9NuvU=
github.com/arduino/go-paths-helper v1.6.1 h1:lha+/BuuBsx0qTZ3gy6IO1kU23lObWdQ/UItkzVWQ+0=
github.com/arduino/go-paths-helper v1.6.1/go.mod h1:V82BWgAAp4IbmlybxQdk9Bpkz8M4Qyx+RAFKaG9NuvU=
@@ -120,7 +124,6 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
-github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
@@ -279,13 +282,12 @@ github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYt
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
github.com/marcinbor85/gohex v0.0.0-20210308104911-55fb1c624d84/go.mod h1:Pb6XcsXyropB9LNHhnqaknG/vEwYztLkQzVCHv8sQ3M=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
-github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
+github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
-github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
@@ -572,8 +574,6 @@ golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6 h1:cdsMqa2nXzqlgs183pHxtvoVwU7CyzaCTAUOg94af4c=
-golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211209171907-798191bca915 h1:P+8mCzuEpyszAT6T42q0sxU+eveBAF/cJ2Kp0x6/8+0=
diff --git a/internal/binary/download.go b/internal/binary/download.go
new file mode 100644
index 00000000..714d092e
--- /dev/null
+++ b/internal/binary/download.go
@@ -0,0 +1,80 @@
+// This file is part of arduino-cloud-cli.
+//
+// Copyright (C) 2021 ARDUINO SA (http://www.arduino.cc/)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package binary
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "time"
+
+ fwuploader "github.com/arduino/arduino-fwuploader/indexes/download"
+)
+
+// Download a binary file contained in the binary index
+func Download(bin *IndexBin) ([]byte, error) {
+ b, err := download(bin.URL)
+ if err != nil {
+ return nil, fmt.Errorf("cannot download binary at %s: %w", bin.URL, err)
+ }
+
+ sz, err := bin.Size.Int64()
+ if err != nil {
+ return nil, fmt.Errorf("cannot retrieve binary size: %w", err)
+ }
+ if len(b) != int(sz) {
+ return nil, fmt.Errorf("download failed: invalid binary size, expected %d bytes but got %d", sz, len(b))
+ }
+
+ err = fwuploader.VerifyChecksum(bin.Checksum, bytes.NewReader(b))
+ if err != nil {
+ return nil, fmt.Errorf("verifying binary checksum: %w", err)
+ }
+
+ return b, nil
+}
+
+func download(url string) ([]byte, error) {
+ cl := http.Client{
+ Timeout: time.Second * 3,
+ }
+ req, err := http.NewRequest(http.MethodGet, url, nil)
+ if err != nil {
+ err = fmt.Errorf("%s: %w", "request url", err)
+ return nil, err
+ }
+ res, err := cl.Do(req)
+ if err != nil {
+ err = fmt.Errorf("%s: %w", "do request url", err)
+ return nil, err
+ }
+
+ if res.Body == nil {
+ return nil, errors.New("empty file downloaded")
+ }
+ defer res.Body.Close()
+
+ body, err := ioutil.ReadAll(res.Body)
+ if err != nil {
+ err = fmt.Errorf("%s: %w", "read request body", err)
+ return nil, err
+ }
+ return body, nil
+}
diff --git a/internal/binary/gpgkey/index_public.gpg.key b/internal/binary/gpgkey/index_public.gpg.key
new file mode 100644
index 00000000..5374b109
Binary files /dev/null and b/internal/binary/gpgkey/index_public.gpg.key differ
diff --git a/internal/binary/gpgkey/key.go b/internal/binary/gpgkey/key.go
new file mode 100644
index 00000000..e783d26a
--- /dev/null
+++ b/internal/binary/gpgkey/key.go
@@ -0,0 +1,27 @@
+// This file is part of arduino-cloud-cli.
+//
+// Copyright (C) 2021 ARDUINO SA (http://www.arduino.cc/)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package gpgkey
+
+import (
+ _ "embed"
+)
+
+var (
+ //go:embed index_public.gpg.key
+ IndexPublicKey []byte
+)
diff --git a/internal/binary/index.go b/internal/binary/index.go
new file mode 100644
index 00000000..ef67b795
--- /dev/null
+++ b/internal/binary/index.go
@@ -0,0 +1,106 @@
+// This file is part of arduino-cloud-cli.
+//
+// Copyright (C) 2021 ARDUINO SA (http://www.arduino.cc/)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package binary
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+
+ "compress/gzip"
+
+ "github.com/arduino/arduino-cloud-cli/internal/binary/gpgkey"
+ "golang.org/x/crypto/openpgp"
+)
+
+const (
+ // URL of cloud-team binary index
+ IndexGZURL = "https://cloud-downloads.arduino.cc/binaries/index.json.gz"
+ IndexSigURL = "https://cloud-downloads.arduino.cc/binaries/index.json.sig"
+)
+
+// Index contains details about all the binaries
+// loaded in 'cloud-downloads'
+type Index struct {
+ Boards []IndexBoard `json:"boards"`
+}
+
+// IndexBoard describes all the binaries available for a specific board
+type IndexBoard struct {
+ Fqbn string `json:"fqbn"`
+ Provision *IndexBin `json:"provision"`
+}
+
+// IndexBin contains the details needed to retrieve a binary file from the cloud
+type IndexBin struct {
+ URL string `json:"url"`
+ Checksum string `json:"checksum"`
+ Size json.Number `json:"size"`
+}
+
+// LoadIndex downloads and verifies the index of binaries
+// contained in 'cloud-downloads'.
+func LoadIndex() (*Index, error) {
+ indexGZ, err := download(IndexGZURL)
+ if err != nil {
+ return nil, fmt.Errorf("cannot download index: %w", err)
+ }
+
+ indexReader, err := gzip.NewReader(bytes.NewReader(indexGZ))
+ if err != nil {
+ return nil, fmt.Errorf("cannot decompress index: %w", err)
+ }
+ index, err := ioutil.ReadAll(indexReader)
+ if err != nil {
+ return nil, fmt.Errorf("cannot read downloaded index: %w", err)
+ }
+
+ sig, err := download(IndexSigURL)
+ if err != nil {
+ return nil, fmt.Errorf("cannot download index signature: %w", err)
+ }
+
+ keyRing, err := openpgp.ReadKeyRing(bytes.NewReader(gpgkey.IndexPublicKey))
+ if err != nil {
+ return nil, fmt.Errorf("cannot retrieve Arduino public GPG key: %w", err)
+ }
+
+ signer, err := openpgp.CheckDetachedSignature(keyRing, bytes.NewReader(index), bytes.NewReader(sig))
+ if signer == nil || err != nil {
+ return nil, fmt.Errorf("invalid signature for index downloaded from %s", IndexGZURL)
+ }
+
+ i := &Index{}
+ if err = json.Unmarshal(index, &i); err != nil {
+ return nil, fmt.Errorf("cannot unmarshal index json: %w", err)
+ }
+ return i, nil
+}
+
+// FindProvisionBin looks for the provisioning binary corresponding
+// to the passed fqbn in the index.
+// Returns nil if the binary is not found
+func (i *Index) FindProvisionBin(fqbn string) *IndexBin {
+ for _, b := range i.Boards {
+ if b.Fqbn == fqbn {
+ return b.Provision
+ }
+ }
+ return nil
+}
diff --git a/internal/binary/index_test.go b/internal/binary/index_test.go
new file mode 100644
index 00000000..b657263b
--- /dev/null
+++ b/internal/binary/index_test.go
@@ -0,0 +1,46 @@
+// This file is part of arduino-cloud-cli.
+//
+// Copyright (C) 2021 ARDUINO SA (http://www.arduino.cc/)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package binary
+
+import (
+ "testing"
+)
+
+func TestFindProvisionBin(t *testing.T) {
+ var (
+ fqbnOK1 = "arduino:samd:nano_33_iot"
+ fqbnOK2 = "arduino:samd:mkrwifi1010"
+ fqbnNotFound = "arduino:mbed_nano:nano33ble"
+ )
+ index := &Index{
+ Boards: []IndexBoard{
+ {Fqbn: fqbnOK1, Provision: &IndexBin{URL: "mkr"}},
+ {Fqbn: fqbnOK2, Provision: &IndexBin{URL: "nano"}},
+ },
+ }
+
+ bin := index.FindProvisionBin(fqbnOK2)
+ if bin == nil {
+ t.Fatal("provision binary not found")
+ }
+
+ bin = index.FindProvisionBin(fqbnNotFound)
+ if bin != nil {
+ t.Fatalf("provision binary should've not be found, but got: %v", bin)
+ }
+}