Skip to content

Commit d60dbd1

Browse files
committed
Added header decoder
1 parent 83d5253 commit d60dbd1

File tree

7 files changed

+227
-38
lines changed

7 files changed

+227
-38
lines changed

cli/ota/encode.go

+2-10
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,11 @@
1818
package ota
1919

2020
import (
21-
"context"
22-
"fmt"
2321
"os"
2422

2523
"github.com/arduino/arduino-cli/cli/errorcodes"
2624
"github.com/arduino/arduino-cli/cli/feedback"
2725
"github.com/arduino/arduino-cloud-cli/command/ota"
28-
"github.com/arduino/arduino-cloud-cli/config"
2926
"github.com/sirupsen/logrus"
3027
"github.com/spf13/cobra"
3128
)
@@ -57,17 +54,12 @@ func initEncodeBinaryCommand() *cobra.Command {
5754

5855
func runEncodeCommand(flags *encodeBinaryFlags) error {
5956
logrus.Infof("Encoding binary %s", flags.file)
60-
61-
cred, err := config.RetrieveCredentials()
62-
if err != nil {
63-
return fmt.Errorf("retrieving credentials: %w", err)
64-
}
65-
57+
6658
params := &ota.EncodeParams{
6759
FQBN: flags.FQBN,
6860
File: flags.file,
6961
}
70-
otafile, err := ota.Encode(context.TODO(), params, cred)
62+
otafile, err := ota.Encode(params)
7163
if err != nil {
7264
return err
7365
}

cli/ota/massupload.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ func initMassUploadCommand() *cobra.Command {
6565
massUploadCommand.Flags().StringVarP(&flags.file, "file", "", "", "Binary file (.bin) to be uploaded")
6666
massUploadCommand.Flags().BoolVar(&flags.deferred, "deferred", false, "Perform a deferred OTA. It can take up to 1 week.")
6767
massUploadCommand.Flags().StringVarP(&flags.fqbn, "fqbn", "b", "", "FQBN of the devices to update")
68-
massUploadCommand.Flags().BoolVar(&flags.doNotApplyHeader, "no-header", false, "Do not apply header and compression to binary file before upload.")
68+
massUploadCommand.Flags().BoolVar(&flags.doNotApplyHeader, "no-header", false, "Do not apply header and compression to binary file before upload")
6969
massUploadCommand.MarkFlagRequired("file")
7070
massUploadCommand.MarkFlagRequired("fqbn")
7171
return massUploadCommand

cli/ota/upload.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ func initUploadCommand() *cobra.Command {
5353
uploadCommand.Flags().StringVarP(&flags.deviceID, "device-id", "d", "", "Device ID")
5454
uploadCommand.Flags().StringVarP(&flags.file, "file", "", "", "Binary file (.bin) to be uploaded")
5555
uploadCommand.Flags().BoolVar(&flags.deferred, "deferred", false, "Perform a deferred OTA. It can take up to 1 week.")
56-
uploadCommand.Flags().BoolVar(&flags.doNotApplyHeader, "no-header", false, "Do not apply header and compression to binary file before upload.")
56+
uploadCommand.Flags().BoolVar(&flags.doNotApplyHeader, "no-header", false, "Do not apply header and compression to binary file before upload")
5757
uploadCommand.MarkFlagRequired("device-id")
5858
uploadCommand.MarkFlagRequired("file")
5959
return uploadCommand

command/ota/encode.go

+1-4
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,8 @@
1818
package ota
1919

2020
import (
21-
"context"
2221
"fmt"
2322
"os"
24-
25-
"github.com/arduino/arduino-cloud-cli/config"
2623
)
2724

2825
type EncodeParams struct {
@@ -31,7 +28,7 @@ type EncodeParams struct {
3128
}
3229

3330
// Encode command is used to encode a firmware OTA
34-
func Encode(ctx context.Context, params *EncodeParams, cred *config.Credentials) (*string, error) {
31+
func Encode(params *EncodeParams) (*string, error) {
3532
otaFile := fmt.Sprintf("%s.ota", params.File)
3633
_, err := os.Stat(otaFile)
3734
if err == nil {

command/ota/generate.go

+4-22
Original file line numberDiff line numberDiff line change
@@ -26,24 +26,6 @@ import (
2626
inota "github.com/arduino/arduino-cloud-cli/internal/ota"
2727
)
2828

29-
var (
30-
arduinoVendorID = "2341"
31-
arduinoFqbnToPID = map[string]string{
32-
"arduino:samd:nano_33_iot": "8057",
33-
"arduino:samd:mkr1000": "804E",
34-
"arduino:samd:mkrgsm1400": "8052",
35-
"arduino:samd:mkrnb1500": "8055",
36-
"arduino:samd:mkrwifi1010": "8054",
37-
"arduino:mbed_nano:nanorp2040connect": "005E",
38-
"arduino:mbed_portenta:envie_m7": "025B",
39-
"arduino:mbed_nicla:nicla_vision": "025F",
40-
"arduino:mbed_opta:opta": "0064",
41-
"arduino:mbed_giga:giga": "0266",
42-
}
43-
esp32MagicNumberPart1 = "4553"
44-
esp32MagicNumberPart2 = "5033"
45-
)
46-
4729
// Generate takes a .bin file and generates a .ota file.
4830
func Generate(binFile string, outFile string, fqbn string) error {
4931

@@ -55,12 +37,12 @@ func Generate(binFile string, outFile string, fqbn string) error {
5537
// Esp32 boards have a wide range of vid and pid, we don't map all of them
5638
// If the fqbn is the one of an ESP32 board, we force a default magic number that matches the same default expected on the fw side
5739
if strings.HasPrefix(fqbn, "esp32") {
58-
magicNumberPart1 = esp32MagicNumberPart1
59-
magicNumberPart2 = esp32MagicNumberPart2
40+
magicNumberPart1 = inota.Esp32MagicNumberPart1
41+
magicNumberPart2 = inota.Esp32MagicNumberPart2
6042
} else {
6143
//For Arduino Boards we use vendorId and productID to form the magic number
62-
magicNumberPart1 = arduinoVendorID
63-
productID, ok := arduinoFqbnToPID[fqbn]
44+
magicNumberPart1 = inota.ArduinoVendorID
45+
productID, ok := inota.ArduinoFqbnToPID[fqbn]
6446
if !ok {
6547
return errors.New("fqbn not valid")
6648
}

internal/ota/boadpids.go

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// This file is part of arduino-cloud-cli.
2+
//
3+
// Copyright (C) 2021 ARDUINO SA (http://www.arduino.cc/)
4+
//
5+
// This program is free software: you can redistribute it and/or modify
6+
// it under the terms of the GNU Affero General Public License as published
7+
// by the Free Software Foundation, either version 3 of the License, or
8+
// (at your option) any later version.
9+
//
10+
// This program is distributed in the hope that it will be useful,
11+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
// GNU Affero General Public License for more details.
14+
//
15+
// You should have received a copy of the GNU Affero General Public License
16+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
18+
package ota
19+
20+
var (
21+
BoardTypes = map[uint32]string{
22+
0x45535033: "ESP32",
23+
0x23418054: "MKR_WIFI_1010",
24+
0x23418057: "NANO_33_IOT",
25+
0x2341025B: "PORTENTA_H7_M7",
26+
0x2341005E: "NANO_RP2040_CONNECT",
27+
0x2341025F: "NICLA_VISION",
28+
0x23410064: "OPTA",
29+
0x23410266: "GIGA",
30+
0x23410070: "NANO_ESP32",
31+
0x23411002: "UNOR4WIFI",
32+
}
33+
34+
ArduinoPidToFQBN = map[string]string{
35+
"8057": "arduino:samd:nano_33_iot",
36+
"804E": "arduino:samd:mkr1000",
37+
"8052": "arduino:samd:mkrgsm1400",
38+
"8055": "arduino:samd:mkrnb1500",
39+
"8054": "arduino:samd:mkrwifi1010",
40+
"005E": "arduino:mbed_nano:nanorp2040connect",
41+
"025B": "arduino:mbed_portenta:envie_m7",
42+
"025F": "arduino:mbed_nicla:nicla_vision",
43+
"0064": "arduino:mbed_opta:opta",
44+
"0266": "arduino:mbed_giga:giga",
45+
}
46+
47+
ArduinoVendorID = "2341"
48+
49+
ArduinoFqbnToPID = map[string]string{
50+
"arduino:samd:nano_33_iot": "8057",
51+
"arduino:samd:mkr1000": "804E",
52+
"arduino:samd:mkrgsm1400": "8052",
53+
"arduino:samd:mkrnb1500": "8055",
54+
"arduino:samd:mkrwifi1010": "8054",
55+
"arduino:mbed_nano:nanorp2040connect": "005E",
56+
"arduino:mbed_portenta:envie_m7": "025B",
57+
"arduino:mbed_nicla:nicla_vision": "025F",
58+
"arduino:mbed_opta:opta": "0064",
59+
"arduino:mbed_giga:giga": "0266",
60+
}
61+
62+
Esp32MagicNumberPart1 = "4553"
63+
Esp32MagicNumberPart2 = "5033"
64+
)

internal/ota/decoder.go

+154
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
// This file is part of arduino-cloud-cli.
2+
//
3+
// Copyright (C) 2021 ARDUINO SA (http://www.arduino.cc/)
4+
//
5+
// This program is free software: you can redistribute it and/or modify
6+
// it under the terms of the GNU Affero General Public License as published
7+
// by the Free Software Foundation, either version 3 of the License, or
8+
// (at your option) any later version.
9+
//
10+
// This program is distributed in the hope that it will be useful,
11+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
// GNU Affero General Public License for more details.
14+
//
15+
// You should have received a copy of the GNU Affero General Public License
16+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
18+
package ota
19+
20+
import (
21+
"encoding/binary"
22+
"fmt"
23+
"hash/crc32"
24+
"io"
25+
"os"
26+
"strconv"
27+
"strings"
28+
)
29+
30+
var (
31+
ErrCRC32Mismatch = fmt.Errorf("CRC32 mismatch")
32+
)
33+
34+
type OtaFirmwareHeader struct {
35+
Length uint32
36+
CRC32 uint32
37+
MagicNumber uint32
38+
BoardType string
39+
FQBN *string
40+
VID string
41+
PID string
42+
IsArduinoBoard bool
43+
}
44+
45+
func readBytes(file *os.File, length int, offset int64) ([]byte, error) {
46+
bytes := make([]byte, length)
47+
_, err := file.ReadAt(bytes, offset)
48+
if err != nil {
49+
return nil, err
50+
}
51+
return bytes, nil
52+
}
53+
54+
func crc32Buffered(file *os.File) (uint32, error) {
55+
h := crc32.NewIEEE()
56+
// Discard first 8 bytes
57+
file.Seek(8, 0)
58+
// Read file in chunks and compute CRC32
59+
buf := make([]byte, 4096)
60+
for {
61+
n, err := file.Read(buf)
62+
if err != nil && err != io.EOF {
63+
return 0, err
64+
}
65+
if n == 0 {
66+
break
67+
}
68+
h.Write(buf[:n])
69+
}
70+
return h.Sum32(), nil
71+
}
72+
73+
func extractXID(buff []byte) string {
74+
xid := strconv.FormatUint(uint64(binary.LittleEndian.Uint16(buff)), 16)
75+
return strings.ToUpper(xid)
76+
}
77+
78+
// DecodeOtaFirmwareHeader decodes the OTA firmware header from a binary file.
79+
// File is composed by an header and a payload (optionally lzss compressed).
80+
// Method is also checking CRC32 of the file, verifying that file is not corrupted.
81+
// See: https://arduino.atlassian.net/wiki/spaces/RFC/pages/1616871540/OTA+header+structure
82+
func DecodeOtaFirmwareHeader(binaryFilePath string) (*OtaFirmwareHeader, error) {
83+
// Check if file exists
84+
if _, err := os.Stat(binaryFilePath); err != nil {
85+
return nil, err
86+
}
87+
// Open file
88+
if otafileptr, err := os.Open(binaryFilePath); err != nil {
89+
return nil, err
90+
} else {
91+
defer otafileptr.Close()
92+
93+
// Get length (payload + header without lenght and CRC32 bytes)
94+
buff, err := readBytes(otafileptr, 4, 0)
95+
if err != nil {
96+
return nil, err
97+
}
98+
lenghtInt := binary.LittleEndian.Uint32(buff)
99+
100+
// Get CRC32 (uint32)
101+
buff, err = readBytes(otafileptr, 4, 4)
102+
if err != nil {
103+
return nil, err
104+
}
105+
readsum := binary.LittleEndian.Uint32(buff)
106+
107+
// Read full binary file (buffered), starting from 8th byte (magic number)
108+
computedsum, err := crc32Buffered(otafileptr)
109+
if err != nil {
110+
return nil, err
111+
}
112+
if computedsum != readsum {
113+
return nil, ErrCRC32Mismatch
114+
}
115+
116+
// Get PID+VID (uint32)
117+
buff, err = readBytes(otafileptr, 8, 8)
118+
if err != nil {
119+
return nil, err
120+
}
121+
completeMagicNumber := binary.LittleEndian.Uint32(buff)
122+
123+
//Extract VID and PID
124+
pid := extractXID(buff[:2])
125+
vid := extractXID(buff[2:])
126+
127+
boardType, fqbn, isArduino := getBoardType(completeMagicNumber, pid)
128+
129+
return &OtaFirmwareHeader{
130+
Length: lenghtInt,
131+
CRC32: computedsum,
132+
BoardType: boardType,
133+
MagicNumber: completeMagicNumber,
134+
IsArduinoBoard: isArduino,
135+
PID: pid,
136+
VID: vid,
137+
FQBN: fqbn,
138+
}, nil
139+
}
140+
}
141+
142+
func getBoardType(magicNumber uint32, pid string) (string, *string, bool) {
143+
baordType := "UNKNOWN"
144+
if t, ok := BoardTypes[magicNumber]; ok {
145+
baordType = t
146+
}
147+
isArduino := baordType != "UNKNOWN" && baordType != "ESP32"
148+
var fqbn *string
149+
if t, ok := ArduinoPidToFQBN[pid]; ok {
150+
fqbn = &t
151+
}
152+
153+
return baordType, fqbn, isArduino
154+
}

0 commit comments

Comments
 (0)