diff --git a/cli/firmware/firmware.go b/cli/firmware/firmware.go index 2b81a8a1..9bcf87ce 100644 --- a/cli/firmware/firmware.go +++ b/cli/firmware/firmware.go @@ -33,6 +33,7 @@ func NewCommand() *cobra.Command { Example: " " + os.Args[0] + " firmware ...", } + firmwareCmd.AddCommand(NewFlashCommand()) firmwareCmd.AddCommand(newListCommand()) return firmwareCmd } diff --git a/cli/firmware/flash.go b/cli/firmware/flash.go new file mode 100644 index 00000000..3d3f98fe --- /dev/null +++ b/cli/firmware/flash.go @@ -0,0 +1,209 @@ +/* + FirmwareUploader + Copyright (c) 2021 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package firmware + +import ( + "bytes" + "os" + "path/filepath" + "strings" + "time" + + "github.com/arduino/FirmwareUploader/flasher" + "github.com/arduino/FirmwareUploader/indexes" + "github.com/arduino/FirmwareUploader/indexes/download" + "github.com/arduino/FirmwareUploader/indexes/firmwareindex" + programmer "github.com/arduino/FirmwareUploader/programmers" + "github.com/arduino/arduino-cli/arduino/serialutils" + "github.com/arduino/arduino-cli/cli/errorcodes" + "github.com/arduino/arduino-cli/cli/feedback" + "github.com/arduino/go-properties-orderedmap" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +var ( + fqbn string + address string + module string +) + +// NewCommand created a new `version` command +func NewFlashCommand() *cobra.Command { + command := &cobra.Command{ + Use: "flash", + Short: "Flashes firmwares to board.", + Long: "Flashes specified module firmware to board at specified address. Module name and version can be omitted to install latest version.", + Example: "" + + " " + os.Args[0] + " flash --fqbn arduino:samd:mkr1000 --address COM10 --module WINC1500@19.5.2\n" + + " " + os.Args[0] + " flash -b arduino:samd:mkr1000 -a COM10 -m WINC15000\n" + + " " + os.Args[0] + " flash -b arduino:samd:mkr1000 -a COM10\n", + Args: cobra.NoArgs, + Run: run, + } + + command.Flags().StringVarP(&fqbn, "fqbn", "b", "", "Fully Qualified Board Name, e.g.: arduino:samd:mkr1000, arduino:mbed_nano:nanorp2040connect") + command.Flags().StringVarP(&address, "address", "a", "", "Upload port, e.g.: COM10, /dev/ttyACM0") + command.Flags().StringVarP(&module, "module", "m", "", "Firmware module ID, e.g.: WINC1500, NINA") + return command +} + +func run(cmd *cobra.Command, args []string) { + packageIndex, err := indexes.GetPackageIndex() + if err != nil { + feedback.Errorf("Can't load package index: %s", err) + os.Exit(errorcodes.ErrGeneric) + } + + firmwareIndex, err := indexes.GetFirmwareIndex() + if err != nil { + feedback.Errorf("Can't load firmware index: %s", err) + os.Exit(errorcodes.ErrGeneric) + } + + if fqbn == "" { + feedback.Errorf("Error during firmware flashing: missing board fqbn") + os.Exit(errorcodes.ErrBadArgument) + } + + if address == "" { + feedback.Errorf("Error during firmware flashing: missing board address") + os.Exit(errorcodes.ErrBadArgument) + } + + board := firmwareIndex.GetBoard(fqbn) + if board == nil { + feedback.Errorf("Can't find board with %s fqbn", fqbn) + os.Exit(errorcodes.ErrBadArgument) + } + + // Get module name if not specified + moduleName := "" + moduleVersion := "" + if module == "" { + moduleName = board.Module + } else { + moduleSplit := strings.Split(module, "@") + if len(moduleSplit) == 2 { + moduleName = moduleSplit[0] + moduleVersion = moduleSplit[1] + } else { + moduleName = module + } + } + // Normalize module name + moduleName = strings.ToUpper(moduleName) + + var firmware *firmwareindex.IndexFirmware + if moduleVersion == "" { + firmware = board.LatestFirmware + } else { + firmware = board.GetFirmware(moduleVersion) + } + if firmware == nil { + feedback.Errorf("Error getting firmware for board: %s", fqbn) + os.Exit(errorcodes.ErrGeneric) + } + + firmwareFile, err := download.DownloadFirmware(firmware) + if err != nil { + feedback.Errorf("Error downloading firmware from %s: %s", firmware.URL, err) + os.Exit(errorcodes.ErrGeneric) + } + + toolRelease := indexes.GetToolRelease(packageIndex, board.Uploader) + if toolRelease == nil { + feedback.Errorf("Error getting upload tool %s for board %s", board.Uploader, board.Fqbn) + os.Exit(errorcodes.ErrGeneric) + } + uploadToolDir, err := download.DownloadTool(toolRelease) + if err != nil { + feedback.Errorf("Error downloading tool %s: %s", board.Uploader, err) + os.Exit(errorcodes.ErrGeneric) + } + + loaderSketchPath, err := download.DownloadLoaderSketch(board.LoaderSketch) + if err != nil { + feedback.Errorf("Error downloading loader sketch from %s: %s", board.LoaderSketch.URL, err) + os.Exit(errorcodes.ErrGeneric) + } + + loaderSketch := strings.ReplaceAll(loaderSketchPath.String(), loaderSketchPath.Ext(), "") + + uploaderCommand := board.GetUploaderCommand() + uploaderCommand = strings.ReplaceAll(uploaderCommand, "{tool_dir}", filepath.FromSlash(uploadToolDir.String())) + uploaderCommand = strings.ReplaceAll(uploaderCommand, "{serial.port.file}", address) + uploaderCommand = strings.ReplaceAll(uploaderCommand, "{loader.sketch}", loaderSketch) + + commandLine, err := properties.SplitQuotedString(uploaderCommand, "\"", false) + if err != nil { + feedback.Errorf(`Error splitting command line "%s": %s`, uploaderCommand, err) + os.Exit(errorcodes.ErrGeneric) + } + + // Check if board needs a 1200bps touch for upload + if board.UploadTouch { + logrus.Info("Putting board into bootloader mode") + _, err := serialutils.Reset(address, board.UploadWait, nil) + if err != nil { + feedback.Errorf("Error during firmware flashing: missing board address") + os.Exit(errorcodes.ErrGeneric) + } + } + + // Flash loader Sketch + flashOut := new(bytes.Buffer) + flashErr := new(bytes.Buffer) + // var err error + if feedback.GetFormat() == feedback.JSON { + err = programmer.Flash(commandLine, flashOut, flashErr) + } else { + err = programmer.Flash(commandLine, os.Stdout, os.Stderr) + } + if err != nil { + feedback.Errorf("Error during firmware flashing: %s", err) + os.Exit(errorcodes.ErrGeneric) + } + + // Wait a bit after flashing the loader sketch for the board to become + // available again. + time.Sleep(1 * time.Second) + + // Get flasher depending on which module to use + var f flasher.Flasher + switch moduleName { + case "NINA": + f, err = flasher.NewNinaFlasher(address) + case "SARA": + f, err = flasher.NewSaraFlasher(address) + case "WINC": + f, err = flasher.NewWincFlasher(address) + } + if err != nil { + feedback.Errorf("Error during firmware flashing: %s", err) + os.Exit(errorcodes.ErrGeneric) + } + defer f.Close() + + if err := f.FlashFirmware(firmwareFile); err != nil { + feedback.Errorf("Error during firmware flashing: %s", err) + os.Exit(errorcodes.ErrGeneric) + } +} diff --git a/cli/firmware/list.go b/cli/firmware/list.go index e835f809..2399f2a1 100644 --- a/cli/firmware/list.go +++ b/cli/firmware/list.go @@ -71,7 +71,7 @@ func list(fqbn string) { BoardFQBN: board.Fqbn, Module: board.Module, FirmwareVersion: firmware.Version, - Latest: board.Latest == firmware, + Latest: board.LatestFirmware == firmware, }) } } diff --git a/flasher/certificate.go b/flasher/certificate.go new file mode 100644 index 00000000..f6821ef4 --- /dev/null +++ b/flasher/certificate.go @@ -0,0 +1,85 @@ +/* + FirmwareUploader + Copyright (c) 2021 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package flasher + +import ( + "bytes" + "crypto/rsa" + "crypto/sha1" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "encoding/binary" + "time" + + "github.com/sirupsen/logrus" +) + +func calculateNameSha1(cert *x509.Certificate) (b []byte, err error) { + nameSha1 := sha1.New() + + var subjectDistinguishedNameSequence pkix.RDNSequence + + if _, err = asn1.Unmarshal(cert.RawSubject, &subjectDistinguishedNameSequence); err != nil { + logrus.Error(err) + return nil, err + } + + for _, dn := range subjectDistinguishedNameSequence { + nameSha1.Write([]byte(dn[0].Value.(string))) + } + + b = nameSha1.Sum(nil) + + return +} + +func convertTime(time time.Time) ([]byte, error) { + asn1Bytes, err := asn1.Marshal(time) + if err != nil { + logrus.Error(err) + return nil, err + } + + b := bytes.Repeat([]byte{0x00}, 20) // value must be zero bytes + copy(b, asn1Bytes[2:]) // copy but drop the first two bytes + + return b, err +} + +func modulusN(publicKey rsa.PublicKey) []byte { + return publicKey.N.Bytes() +} + +func publicExponent(publicKey rsa.PublicKey) []byte { + b := make([]byte, 4) + binary.BigEndian.PutUint32(b, uint32(publicKey.E)) + // strip leading zeros + for b[0] == 0 { + b = b[1:] + } + return b +} + +func uint16ToBytes(i int) []byte { + b := make([]byte, 2) + binary.LittleEndian.PutUint16(b, uint16(i)) + return b +} diff --git a/flasher/flasher.go b/flasher/flasher.go new file mode 100644 index 00000000..7dae5c11 --- /dev/null +++ b/flasher/flasher.go @@ -0,0 +1,94 @@ +/* + FirmwareUploader + Copyright (c) 2021 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package flasher + +import ( + "fmt" + "time" + + "github.com/arduino/go-paths-helper" + "github.com/sirupsen/logrus" + "go.bug.st/serial" +) + +type CommandData struct { + Command byte + Address uint32 + Value uint32 + Payload []byte +} + +func (d CommandData) String() string { + return fmt.Sprintf("%+v, %+v, %+v, %+v", d.Command, d.Address, d.Value, d.Payload) +} + +type FlasherError struct { + err string +} + +func (e FlasherError) Error() string { + return e.err +} + +type Flasher interface { + FlashFirmware(firmwareFile *paths.Path) error + FlashCertificates(certificatePaths *paths.PathList, URLs []string) error + Close() error + + hello() error + write(address uint32, buffer []byte) error + flashChunk(offset int, buffer []byte) error + getMaximumPayloadSize() (uint16, error) + serialFillBuffer(buffer []byte) error + sendCommand(data CommandData) error +} + +// http://www.ni.com/product-documentation/54548/en/ +// Standard baud rates supported by most serial ports +var baudRates = []int{ + 115200, + 57600, + 56000, + 38400, +} + +func openSerial(portAddress string) (serial.Port, error) { + var lastError error + + for _, baudRate := range baudRates { + port, err := serial.Open(portAddress, &serial.Mode{BaudRate: baudRate}) + if err != nil { + lastError = err + // Try another baudrate + continue + } + logrus.Infof("Opened port %s at %d", portAddress, baudRate) + + if err := port.SetReadTimeout(30 * time.Second); err != nil { + err = fmt.Errorf("could not set timeout on serial port: %s", err) + logrus.Error(err) + return nil, err + } + + return port, nil + } + + return nil, lastError +} diff --git a/flasher/nina.go b/flasher/nina.go new file mode 100644 index 00000000..a9eb42cc --- /dev/null +++ b/flasher/nina.go @@ -0,0 +1,482 @@ +/* + FirmwareUploader + Copyright (c) 2021 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package flasher + +import ( + "bytes" + "crypto/md5" + "crypto/tls" + "crypto/x509" + "encoding/binary" + "encoding/pem" + "fmt" + "strconv" + "time" + + "github.com/arduino/go-paths-helper" + "github.com/sirupsen/logrus" + "go.bug.st/serial" +) + +// NewNinaFlasher creates an new instance of NinaFlasher +func NewNinaFlasher(portAddress string) (*NinaFlasher, error) { + port, err := openSerial(portAddress) + if err != nil { + logrus.Error(err) + return nil, err + } + time.Sleep(2 * time.Second) + f := &NinaFlasher{port: port} + payloadSize, err := f.getMaximumPayloadSize() + if err != nil { + logrus.Error(err) + return nil, err + } + if payloadSize < 1024 { + err = fmt.Errorf("programmer reports %d as maximum payload size (1024 is needed)", payloadSize) + logrus.Error(err) + return nil, err + } + f.payloadSize = int(payloadSize) + return f, nil +} + +type NinaFlasher struct { + port serial.Port + payloadSize int +} + +// FlashFirmware in board connected to port using data from firmwareFile +func (f *NinaFlasher) FlashFirmware(firmwareFile *paths.Path) error { + logrus.Infof("Flashing firmware %s", firmwareFile) + if err := f.hello(); err != nil { + logrus.Error(err) + return err + } + + logrus.Debugf("Reading file %s", firmwareFile) + data, err := firmwareFile.ReadFile() + if err != nil { + logrus.Error(err) + return err + } + + logrus.Debugf("Flashing chunks") + firmwareOffset := 0x0000 + if err := f.flashChunk(firmwareOffset, data); err != nil { + logrus.Error(err) + return err + } + + logrus.Debugf("Checking md5") + err = f.md5sum(data) + if err != nil { + logrus.Error(err) + return err + } + logrus.Debugf("Flashed all the things") + return nil +} + +func (f *NinaFlasher) FlashCertificates(certificatePaths *paths.PathList, URLs []string) error { + var certificatesData []byte + for _, certPath := range *certificatePaths { + logrus.Infof("Converting and flashing certificate %s", certPath) + + data, err := f.certificateFromFile(certPath) + if err != nil { + return err + } + certificatesData = append(certificatesData, data...) + } + + for _, URL := range URLs { + logrus.Infof("Converting and flashing certificate from %s", URL) + data, err := f.certificateFromURL(URL) + if err != nil { + return err + } + certificatesData = append(certificatesData, data...) + } + + certificatesDataLimit := 0x20000 + if len(certificatesData) > certificatesDataLimit { + err := fmt.Errorf("certificates data %d exceeds limit of %d bytes", len(certificatesData), certificatesDataLimit) + logrus.Error(err) + return err + } + + // Pad certificatesData to flash page + for len(certificatesData)%int(f.payloadSize) != 0 { + certificatesData = append(certificatesData, 0) + } + + certificatesOffset := 0x10000 + return f.flashChunk(certificatesOffset, certificatesData) +} + +func (f *NinaFlasher) certificateFromFile(certificateFile *paths.Path) ([]byte, error) { + data, err := certificateFile.ReadFile() + if err != nil { + logrus.Error(err) + return nil, err + } + + cert, err := x509.ParseCertificate(data) + if err != nil { + logrus.Error(err) + return nil, err + } + + return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}), nil +} + +func (f *NinaFlasher) certificateFromURL(URL string) ([]byte, error) { + config := &tls.Config{ + InsecureSkipVerify: true, + } + + conn, err := tls.Dial("tcp", URL, config) + if err != nil { + logrus.Error(err) + return nil, err + } + defer conn.Close() + + if err := conn.Handshake(); err != nil { + logrus.Error(err) + return nil, err + } + + peerCertificates := conn.ConnectionState().PeerCertificates + if len(peerCertificates) == 0 { + err = fmt.Errorf("no peer certificates found at %s", URL) + logrus.Error(err) + return nil, err + } + + rootCertificate := peerCertificates[len(peerCertificates)-1] + return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rootCertificate.Raw}), nil +} + +// Close the port used by this flasher +func (f *NinaFlasher) Close() error { + return f.port.Close() +} + +// Ping the programmer to see if it is alive. +// Also check if the version of the programmer protocol match the uploader +func (f *NinaFlasher) hello() error { + // "HELLO" command + err := f.sendCommand(CommandData{ + Command: 0x99, + Address: 0x11223344, + Value: 0x55667788, + Payload: nil, + }) + if err != nil { + logrus.Error(err) + return err + } + + // Wait a bit + time.Sleep(100 * time.Millisecond) + + // Receive response + res := make([]byte, 65535) + n, err := f.port.Read(res) + if err != nil { + logrus.Error(err) + return err + } + // flush eventual leftover from the rx buffer + if n >= 6 { + res = res[n-6 : n] + } + + if res[0] != 'v' { + err = FlasherError{err: "Programmer is not responding"} + logrus.Error(err) + return err + } + if string(res) != "v10000" { + // TODO: Do we really need this check? What is it trying to verify? + err = FlasherError{err: fmt.Sprintf("Programmer version mismatch, v10000 needed: %s", res)} + logrus.Error(err) + return err + } + return nil +} + +// flashChunk flashes a chunk of data +func (f *NinaFlasher) flashChunk(offset int, buffer []byte) error { + chunkSize := int(f.payloadSize) + bufferLength := len(buffer) + + if err := f.erase(uint32(offset), uint32(bufferLength)); err != nil { + logrus.Error(err) + return err + } + + for i := 0; i < bufferLength; i += chunkSize { + logrus.Debugf("Flashing chunk: %s%%", strconv.Itoa((i*100)/bufferLength)) + start := i + end := i + chunkSize + if end > bufferLength { + end = bufferLength + } + if err := f.write(uint32(offset+i), buffer[start:end]); err != nil { + logrus.Error(err) + return err + } + } + + return nil +} + +// getMaximumPayloadSize asks the board the maximum payload size +func (f *NinaFlasher) getMaximumPayloadSize() (uint16, error) { + // "MAX_PAYLOAD_SIZE" command + err := f.sendCommand(CommandData{ + Command: 0x50, + Address: 0, + Value: 0, + Payload: nil, + }) + if err != nil { + logrus.Error(err) + return 0, err + } + + // Receive response + res := make([]byte, 2) + if err := f.serialFillBuffer(res); err != nil { + logrus.Error(err) + return 0, err + } + return (uint16(res[0]) << 8) + uint16(res[1]), nil +} + +// serialFillBuffer fills buffer with data read from the serial port +func (f *NinaFlasher) serialFillBuffer(buffer []byte) error { + read := 0 + for read < len(buffer) { + n, err := f.port.Read(buffer[read:]) + if err != nil { + logrus.Error(err) + return err + } + if n == 0 { + err = FlasherError{err: "Serial port closed unexpectedly"} + logrus.Error(err) + return err + } + read += n + } + return nil +} + +// sendCommand sends the data over serial port to connected board +func (f *NinaFlasher) sendCommand(data CommandData) error { + logrus.Debugf("sending command data %s", data) + buff := new(bytes.Buffer) + if err := binary.Write(buff, binary.BigEndian, data.Command); err != nil { + err = fmt.Errorf("writing command: %s", err) + logrus.Error(err) + return err + } + if err := binary.Write(buff, binary.BigEndian, data.Address); err != nil { + err = fmt.Errorf("writing address: %s", err) + logrus.Error(err) + return err + } + if err := binary.Write(buff, binary.BigEndian, data.Value); err != nil { + err = fmt.Errorf("writing value: %s", err) + logrus.Error(err) + return err + } + var length uint16 + if data.Payload == nil { + length = 0 + } else { + length = uint16(len(data.Payload)) + } + if err := binary.Write(buff, binary.BigEndian, length); err != nil { + err = fmt.Errorf("writing payload length: %s", err) + logrus.Error(err) + return err + } + if data.Payload != nil { + buff.Write(data.Payload) + } + bufferData := buff.Bytes() + for { + sent, err := f.port.Write(bufferData) + if err != nil { + err = fmt.Errorf("writing data: %s", err) + logrus.Error(err) + return err + } + if sent == len(bufferData) { + break + } + logrus.Debugf("Sent %d bytes out of %d", sent, len(bufferData)) + bufferData = bufferData[sent:] + } + return nil +} + +// read a block of flash memory +func (f *NinaFlasher) read(address uint32, length uint32) ([]byte, error) { + // "FLASH_READ" command + err := f.sendCommand(CommandData{ + Command: 0x01, + Address: address, + Value: length, + Payload: nil, + }) + if err != nil { + logrus.Error(err) + return nil, err + } + + // Receive response + result := make([]byte, length) + if err := f.serialFillBuffer(result); err != nil { + logrus.Error(err) + return nil, err + } + ack := make([]byte, 2) + if err := f.serialFillBuffer(ack); err != nil { + logrus.Error(err) + return nil, err + } + if string(ack) != "OK" { + err = FlasherError{err: fmt.Sprintf("Missing ack on read: %s, result: %s", ack, result)} + logrus.Error(err) + return nil, err + } + return result, nil +} + +// write a block of flash memory +func (f *NinaFlasher) write(address uint32, buffer []byte) error { + // "FLASH_WRITE" command + err := f.sendCommand(CommandData{ + Command: 0x02, + Address: address, + Value: 0, + Payload: buffer, + }) + if err != nil { + logrus.Error(err) + return err + } + + // wait acknowledge + ack := make([]byte, 2) + if err := f.serialFillBuffer(ack); err != nil { + logrus.Error(err) + return err + } + if string(ack) != "OK" { + err = FlasherError{err: fmt.Sprintf("Missing ack on write: %s", ack)} + logrus.Error(err) + return err + } + return nil +} + +// erase a block of flash memory +func (f *NinaFlasher) erase(address uint32, length uint32) error { + // "FLASH_ERASE" command + err := f.sendCommand(CommandData{ + Command: 0x03, + Address: address, + Value: length, + Payload: nil, + }) + if err != nil { + logrus.Error(err) + return err + } + + // wait acknowledge + ack := make([]byte, 2) + if err := f.serialFillBuffer(ack); err != nil { + logrus.Error(err) + return err + } + if string(ack) != "OK" { + err = FlasherError{err: fmt.Sprintf("Missing ack on erase: %s", ack)} + logrus.Error(err) + return err + } + return nil +} + +func (f *NinaFlasher) md5sum(data []byte) error { + hasher := md5.New() + hasher.Write(data) + + // Get md5sum + err := f.sendCommand(CommandData{ + Command: 0x04, + Address: 0, + Value: uint32(len(data)), + Payload: nil, + }) + if err != nil { + logrus.Error(err) + return err + } + + // Wait acknowledge + ack := make([]byte, 2) + if err := f.serialFillBuffer(ack); err != nil { + logrus.Error(err) + return err + } + if string(ack) != "OK" { + err := FlasherError{err: fmt.Sprintf("Missing ack on md5sum: %s", ack)} + logrus.Error(err) + return err + } + + // Wait md5 + md5sumfromdevice := make([]byte, 16) + if err := f.serialFillBuffer(md5sumfromdevice); err != nil { + return err + } + + md5sum := hasher.Sum(nil) + logrus.Debugf("md5 read from device %s", md5sumfromdevice) + logrus.Debugf("md5 of data %s", md5sum) + + for i := 0; i < 16; i++ { + if md5sum[i] != md5sumfromdevice[i] { + err := FlasherError{err: "MD5sum failed"} + logrus.Error(err) + return err + } + } + + return nil +} diff --git a/flasher/sara.go b/flasher/sara.go new file mode 100644 index 00000000..876143b4 --- /dev/null +++ b/flasher/sara.go @@ -0,0 +1,226 @@ +/* + FirmwareUploader + Copyright (c) 2021 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package flasher + +import ( + "fmt" + "strconv" + "strings" + "time" + + "github.com/arduino/go-paths-helper" + "github.com/sirupsen/logrus" + "go.bug.st/serial" +) + +func NewSaraFlasher(portAddress string) (*SaraFlasher, error) { + port, err := openSerial(portAddress) + if err != nil { + logrus.Error(err) + return nil, err + } + // Magic numbers ¯\_(ツ)_/¯ + return &SaraFlasher{port: port, payloadSize: 128}, nil +} + +type SaraFlasher struct { + port serial.Port + payloadSize int +} + +func (f *SaraFlasher) FlashFirmware(firmwareFile *paths.Path) error { + logrus.Infof("Flashing firmware %s", firmwareFile) + data, err := firmwareFile.ReadFile() + if err != nil { + logrus.Error(err) + return err + } + + _, err = f.expectMinBytes("AT+ULSTFILE", "+ULSTFILE:", 1000, 0) + if err != nil { + logrus.Error(err) + return err + } + + _, err = f.expectMinBytes("AT+UDWNFILE=\"UPDATE.BIN\","+strconv.Itoa(len(data))+",\"FOAT\"", ">", 20000, 0) + if err != nil { + logrus.Error(err) + return err + } + + firmwareOffset := 0x0000 + err = f.flashChunk(firmwareOffset, data) + if err != nil { + logrus.Error(err) + return err + } + + time.Sleep(1 * time.Second) + + _, err = f.expectMinBytes("", "OK", 1000, 0) + if err != nil { + logrus.Error(err) + return err + } + + _, err = f.expectMinBytes("AT+UFWINSTALL", "OK", 60000, 0) + if err != nil { + logrus.Error(err) + return err + } + + time.Sleep(10 * time.Second) + + // wait up to 20 minutes trying to ping the module. After 20 minutes signal the error + start := time.Now() + for time.Since(start) < time.Minute*20 { + err = f.hello() + if err == nil { + return nil + } + time.Sleep(1 * time.Second) + } + + if err != nil { + logrus.Error(err) + } + return err +} + +func (f *SaraFlasher) FlashCertificates(certificatePaths *paths.PathList, URLs []string) error { + return fmt.Errorf("not supported by SaraFlasher") +} + +func (f *SaraFlasher) Close() error { + return f.port.Close() +} + +func (f *SaraFlasher) hello() error { + f.expectMinBytes("ATE0", "OK", 100, 0) + f.expectMinBytes("ATE0", "OK", 100, 0) + f.expectMinBytes("ATE0", "OK", 100, 0) + _, err := f.expectMinBytes("AT", "OK", 100, 0) + if err != nil { + logrus.Error(err) + } + return err +} + +func (f *SaraFlasher) write(address uint32, buffer []byte) error { + return f.sendCommand(CommandData{ + Payload: buffer, + }) +} + +func (f *SaraFlasher) flashChunk(offset int, buffer []byte) error { + bufferLength := len(buffer) + + for i := 0; i < bufferLength; i += f.payloadSize { + logrus.Debugf("Flashing chunk: %s%%", strconv.Itoa((i*100)/bufferLength)) + start := i + end := i + f.payloadSize + if end > bufferLength { + end = bufferLength + } + if err := f.write(uint32(offset+i), buffer[start:end]); err != nil { + logrus.Error(err) + return err + } + //time.Sleep(1 * time.Millisecond) + } + + return nil +} + +func (f *SaraFlasher) getMaximumPayloadSize() (uint16, error) { + return 0, fmt.Errorf("not supported by SaraFlasher") +} + +func (f *SaraFlasher) serialFillBuffer(buffer []byte) error { + read := 0 + for read < len(buffer) { + n, err := f.port.Read(buffer[read:]) + if err != nil { + logrus.Error(err) + return err + } + if n == 0 { + err = &FlasherError{err: "Serial port closed unexpectedly"} + logrus.Error(err) + return err + } + read += n + } + return nil +} + +func (f *SaraFlasher) sendCommand(data CommandData) error { + logrus.Debugf("sending command data %s", data) + if data.Payload != nil { + for { + if sent, err := f.port.Write(data.Payload); err != nil { + logrus.Error(err) + return err + } else if sent < len(data.Payload) { + data.Payload = data.Payload[sent:] + } else { + break + } + } + } + return nil +} + +func (f *SaraFlasher) expectMinBytes(buffer string, response string, timeout int, min_bytes int) (string, error) { + err := f.sendCommand(CommandData{ + Payload: []byte(buffer + "\r\n"), + }) + if err != nil { + logrus.Error(err) + return "", err + } + + // Receive response + var res []byte + n := 0 + + start := time.Now() + for (time.Since(start) < time.Duration(timeout)*time.Millisecond && !strings.Contains(string(res), response)) || (len(res) < min_bytes) { + data := 0 + partial := make([]byte, 65535) + data, err = f.port.Read(partial) + res = append(res, partial[:data]...) + n += data + if err != nil { + logrus.Error(err) + return "", err + } + } + + if !strings.Contains(string(res), response) { + err = FlasherError{err: fmt.Sprintf("Expected %s, got %s", response, res)} + logrus.Error(err) + return string(res), err + } + return string(res), nil +} +func (f *SaraFlasher) getFirmwareVersion() (string, error) { + return f.expectMinBytes("ATI9", "05.06,A.02.", 100, 25) +} diff --git a/flasher/winc.go b/flasher/winc.go new file mode 100644 index 00000000..4ef972a4 --- /dev/null +++ b/flasher/winc.go @@ -0,0 +1,448 @@ +/* + FirmwareUploader + Copyright (c) 2021 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package flasher + +import ( + "bytes" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "encoding/binary" + "errors" + "fmt" + "log" + "strconv" + "time" + + "github.com/arduino/go-paths-helper" + "github.com/sirupsen/logrus" + "go.bug.st/serial" +) + +func NewWincFlasher(portAddress string) (*WincFlasher, error) { + port, err := openSerial(portAddress) + if err != nil { + logrus.Error(err) + return nil, err + } + f := &WincFlasher{port: port} + payloadSize, err := f.getMaximumPayloadSize() + if err != nil { + logrus.Error(err) + return nil, err + } + if payloadSize < 1024 { + return nil, fmt.Errorf("programmer reports %d as maximum payload size (1024 is needed)", payloadSize) + } + f.payloadSize = int(payloadSize) + return f, nil +} + +type WincFlasher struct { + port serial.Port + payloadSize int +} + +func (f *WincFlasher) FlashFirmware(firmwareFile *paths.Path) error { + logrus.Infof("Flashing firmware %s", firmwareFile) + data, err := firmwareFile.ReadFile() + if err != nil { + logrus.Error(err) + return err + } + firmwareOffset := 0x0000 + return f.flashChunk(firmwareOffset, data) +} + +func (f *WincFlasher) FlashCertificates(certificatePaths *paths.PathList, URLs []string) error { + var certificatesData []byte + certificatesNumber := 0 + for _, certPath := range *certificatePaths { + logrus.Infof("Converting and flashing certificate %s", certPath) + + data, err := f.certificateFromFile(certPath) + if err != nil { + return err + } + certificatesData = append(certificatesData, data...) + certificatesNumber++ + } + + for _, URL := range URLs { + logrus.Infof("Converting and flashing certificate from %s", URL) + data, err := f.certificateFromURL(URL) + if err != nil { + return err + } + certificatesData = append(certificatesData, data...) + certificatesNumber++ + } + + certificatesOffset := 0x4000 + return f.flashChunk(certificatesOffset, certificatesData) +} + +func (f *WincFlasher) certificateFromFile(certificateFile *paths.Path) ([]byte, error) { + data, err := certificateFile.ReadFile() + if err != nil { + logrus.Error(err) + return nil, err + } + + cert, err := x509.ParseCertificate(data) + if err != nil { + logrus.Error(err) + return nil, err + } + + return f.getCertificateData(cert) +} + +func (f *WincFlasher) certificateFromURL(URL string) ([]byte, error) { + config := &tls.Config{ + InsecureSkipVerify: true, + } + + conn, err := tls.Dial("tcp", URL, config) + if err != nil { + logrus.Error(err) + return nil, err + } + defer conn.Close() + + if err := conn.Handshake(); err != nil { + logrus.Error(err) + return nil, err + } + + peerCertificates := conn.ConnectionState().PeerCertificates + if len(peerCertificates) == 0 { + err = fmt.Errorf("no peer certificates found at %s", URL) + logrus.Error(err) + return nil, err + } + rootCertificate := peerCertificates[len(peerCertificates)-1] + return f.getCertificateData(rootCertificate) +} + +func (f *WincFlasher) getCertificateData(cert *x509.Certificate) ([]byte, error) { + b := []byte{} + nameSHA1Bytes, err := calculateNameSha1(cert) + if err != nil { + return nil, err + } + + notBeforeBytes, err := convertTime(cert.NotBefore) + if err != nil { + return nil, err + } + + notAfterBytes, err := convertTime(cert.NotAfter) + if err != nil { + return nil, err + } + + rsaPublicKey := *cert.PublicKey.(*rsa.PublicKey) + + rsaModulusNBytes := modulusN(rsaPublicKey) + rsaPublicExponentBytes := publicExponent(rsaPublicKey) + + rsaModulusNLenBytes := uint16ToBytes(len(rsaModulusNBytes)) + rsaPublicExponentLenBytes := uint16ToBytes(len(rsaPublicExponentBytes)) + + b = append(b, nameSHA1Bytes...) + b = append(b, rsaModulusNLenBytes...) + b = append(b, rsaPublicExponentLenBytes...) + b = append(b, notBeforeBytes...) + b = append(b, notAfterBytes...) + b = append(b, rsaModulusNBytes...) + b = append(b, rsaPublicExponentBytes...) + for (len(b) & 3) != 0 { + b = append(b, 0xff) // padding + } + return b, nil +} + +func (f *WincFlasher) Close() error { + return f.port.Close() +} + +func (f *WincFlasher) hello() error { + // "HELLO" command + err := f.sendCommand(CommandData{ + Command: 0x99, + Address: 0x11223344, + Value: 0x55667788, + Payload: nil, + }) + if err != nil { + logrus.Error(err) + return err + } + + // Wait a bit + time.Sleep(100 * time.Millisecond) + + // Receive response + res := make([]byte, 65535) + n, err := f.port.Read(res) + if err != nil { + logrus.Error(err) + return err + } + // flush eventual leftover from the rx buffer + if n >= 6 { + res = res[n-6 : n] + } + + if res[0] != 'v' { + err = FlasherError{err: "Programmer is not responding"} + logrus.Error(err) + return err + } + if string(res) != "v10000" { + // TODO: Do we really need this check? What is it trying to verify? + err = FlasherError{err: fmt.Sprintf("Programmer version mismatch, v10000 needed: %s", res)} + logrus.Error(err) + return err + } + return nil +} + +func (f *WincFlasher) write(address uint32, buffer []byte) error { + // "FLASH_WRITE" command + err := f.sendCommand(CommandData{ + Command: 0x02, + Address: address, + Value: 0, + Payload: buffer, + }) + if err != nil { + logrus.Error(err) + return err + } + + // wait acknowledge + ack := make([]byte, 2) + if err := f.serialFillBuffer(ack); err != nil { + logrus.Error(err) + return err + } + if string(ack) != "OK" { + err = FlasherError{err: fmt.Sprintf("Missing ack on write: %s", ack)} + logrus.Error(err) + return err + } + return nil +} + +func (f *WincFlasher) flashChunk(offset int, buffer []byte) error { + bufferLength := len(buffer) + + if err := f.erase(uint32(offset), uint32(bufferLength)); err != nil { + logrus.Error(err) + return err + } + + for i := 0; i < bufferLength; i += f.payloadSize { + logrus.Debugf("Flashing chunk: %s%%", strconv.Itoa((i*100)/bufferLength)) + start := i + end := i + f.payloadSize + if end > bufferLength { + end = bufferLength + } + if err := f.write(uint32(offset+i), buffer[start:end]); err != nil { + logrus.Error(err) + return err + } + } + + var flashData []byte + for i := 0; i < bufferLength; i += f.payloadSize { + readLength := f.payloadSize + if (i + f.payloadSize) > bufferLength { + readLength = bufferLength % f.payloadSize + } + + data, err := f.read(uint32(offset+i), uint32(readLength)) + if err != nil { + logrus.Error(err) + return err + } + + flashData = append(flashData, data...) + } + + if !bytes.Equal(buffer, flashData) { + err := errors.New("flash data does not match written") + logrus.Error(err) + return err + } + + return nil +} + +func (f *WincFlasher) getMaximumPayloadSize() (uint16, error) { + // "MAX_PAYLOAD_SIZE" command + err := f.sendCommand(CommandData{ + Command: 0x50, + Address: 0, + Value: 0, + Payload: nil, + }) + if err != nil { + logrus.Error(err) + return 0, err + } + + // Receive response + res := make([]byte, 2) + if err := f.serialFillBuffer(res); err != nil { + logrus.Error(err) + return 0, err + } + return (uint16(res[0]) << 8) + uint16(res[1]), nil +} + +func (f *WincFlasher) serialFillBuffer(buffer []byte) error { + read := 0 + for read < len(buffer) { + n, err := f.port.Read(buffer[read:]) + if err != nil { + logrus.Error(err) + return err + } + if n == 0 { + err = FlasherError{err: "Serial port closed unexpectedly"} + logrus.Error(err) + return err + } + read += n + } + return nil +} + +func (f *WincFlasher) sendCommand(data CommandData) error { + logrus.Debugf("sending command data %s", data) + buff := new(bytes.Buffer) + if err := binary.Write(buff, binary.BigEndian, data.Command); err != nil { + logrus.Error(err) + return err + } + if err := binary.Write(buff, binary.BigEndian, data.Address); err != nil { + logrus.Error(err) + return err + } + if err := binary.Write(buff, binary.BigEndian, data.Value); err != nil { + logrus.Error(err) + return err + } + var length uint16 + if data.Payload == nil { + length = 0 + } else { + length = uint16(len(data.Payload)) + } + if err := binary.Write(buff, binary.BigEndian, length); err != nil { + logrus.Error(err) + return err + } + if data.Payload != nil { + buff.Write(data.Payload) + } + + bufferData := buff.Bytes() + for { + sent, err := f.port.Write(bufferData) + if err != nil { + logrus.Error(err) + return err + } + if sent == len(bufferData) { + break + } + logrus.Debugf("Sent %d bytes out of %d", sent, len(bufferData)) + bufferData = bufferData[sent:] + } + return nil +} + +// Read a block of flash memory +func (f *WincFlasher) read(address uint32, length uint32) ([]byte, error) { + // "FLASH_READ" command + err := f.sendCommand(CommandData{ + Command: 0x01, + Address: address, + Value: length, + Payload: nil, + }) + if err != nil { + logrus.Error(err) + return nil, err + } + + // Receive response + result := make([]byte, length) + if err := f.serialFillBuffer(result); err != nil { + logrus.Error(err) + return nil, err + } + ack := make([]byte, 2) + if err := f.serialFillBuffer(ack); err != nil { + logrus.Error(err) + return nil, err + } + if string(ack) != "OK" { + err = FlasherError{err: fmt.Sprintf("Missing ack on read: %s", ack)} + logrus.Error(err) + return nil, err + } + return result, nil +} + +// Erase a block of flash memory +func (f *WincFlasher) erase(address uint32, length uint32) error { + // "FLASH_ERASE" command + err := f.sendCommand(CommandData{ + Command: 0x03, + Address: address, + Value: length, + Payload: nil, + }) + if err != nil { + logrus.Error(err) + return err + } + + log.Printf("Erasing %d bytes from address 0x%X\n", length, address) + + // wait acknowledge + ack := make([]byte, 2) + if err := f.serialFillBuffer(ack); err != nil { + logrus.Error(err) + return err + } + if string(ack) != "OK" { + err = FlasherError{err: fmt.Sprintf("Missing ack on erase: %s", ack)} + logrus.Error(err) + return err + } + return nil +} diff --git a/go.mod b/go.mod index ce5af737..4909756d 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ replace go.bug.st/serial => github.com/cmaglie/go-serial v0.0.0-20200923162623-b require ( github.com/arduino/arduino-cli v0.0.0-20210603144340-aef5a54882fa github.com/arduino/go-paths-helper v1.6.0 + github.com/arduino/go-properties-orderedmap v1.3.0 // indirect github.com/cmaglie/go.rice v1.0.3 github.com/mattn/go-colorable v0.1.8 github.com/pkg/errors v0.9.1 diff --git a/indexes/firmwareindex/firmwareindex.go b/indexes/firmwareindex/firmwareindex.go index c071957b..0a68fb94 100644 --- a/indexes/firmwareindex/firmwareindex.go +++ b/indexes/firmwareindex/firmwareindex.go @@ -21,7 +21,6 @@ package firmwareindex import ( "encoding/json" - "fmt" "runtime" "github.com/arduino/arduino-cli/arduino/security" @@ -48,7 +47,7 @@ type IndexBoard struct { UploadTouch bool `json:"upload.use_1200bps_touch"` UploadWait bool `json:"upload.wait_for_upload_port"` UploaderCommand *IndexUploaderCommand `json:"uploader.command,required"` - Latest *IndexFirmware `json:"-"` + LatestFirmware *IndexFirmware `json:"-"` } type IndexUploaderCommand struct { @@ -89,7 +88,6 @@ func LoadIndex(jsonIndexFile *paths.Path) (*Index, error) { if err != nil { return nil, err } - trusted, _, err := security.VerifySignature(jsonIndexFile, jsonSignatureFile, key) if err != nil { logrus. @@ -123,12 +121,12 @@ func LoadIndexNoSign(jsonIndexFile *paths.Path) (*Index, error) { // Determine latest firmware for each board for _, board := range index.Boards { if board.Module == "SARA" { - // TODO implement?? by defualt you have to specify the version + // TODO implement?? by default you have to specify the version continue } for _, firmware := range board.Firmwares { - if board.Latest == nil || firmware.Version.GreaterThan(board.Latest.Version) { - board.Latest = firmware + if board.LatestFirmware == nil || firmware.Version.GreaterThan(board.LatestFirmware.Version) { + board.LatestFirmware = firmware } } } @@ -136,46 +134,6 @@ func LoadIndexNoSign(jsonIndexFile *paths.Path) (*Index, error) { return &index, nil } -// GetLatestFirmwareURL takes the fqbn as parameter and returns the URL of the latest available firmware. -// Not currently implemented for SARA, as the version for it's firmware is a bit strange -func (i *Index) GetLatestFirmwareURL(fqbn string) (string, error) { - board := i.GetBoard(fqbn) - if board == nil { - return "", fmt.Errorf("invalid FQBN: %s", fqbn) - } - - if board.Latest == nil { - return "", fmt.Errorf("cannot find latest version") - } - - return board.Latest.URL, nil -} - -// GetFirmwareURL will take the fqbn of the required board and the version of the firmware as parameters. -// It will return the URL of the required firmware -func (i *Index) GetFirmwareURL(fqbn, v string) (string, error) { - board := i.GetBoard(fqbn) - if board == nil { - return "", fmt.Errorf("invalid FQBN: %s", fqbn) - } - version := semver.ParseRelaxed(v) - for _, firmware := range board.Firmwares { - if firmware.Version.Equal(version) { - return firmware.URL, nil - } - } - return "", fmt.Errorf("version not found: %s", version) -} - -// GetLoaderSketchURL will take the board's fqbn and return the url of the loader sketch -func (i *Index) GetLoaderSketchURL(fqbn string) (string, error) { - board := i.GetBoard(fqbn) - if board == nil { - return "", fmt.Errorf("invalid FQBN: %s", fqbn) - } - return board.LoaderSketch.URL, nil -} - // GetBoard returns the IndexBoard for the given FQBN func (i *Index) GetBoard(fqbn string) *IndexBoard { for _, b := range i.Boards { @@ -186,6 +144,18 @@ func (i *Index) GetBoard(fqbn string) *IndexBoard { return nil } +// GetLatestFirmware returns the specified IndexFirmware version for this board. +// Returns nil if version is not found. +func (b *IndexBoard) GetFirmware(version string) *IndexFirmware { + v := semver.ParseRelaxed(version) + for _, firmware := range b.Firmwares { + if firmware.Version.Equal(v) { + return firmware + } + } + return nil +} + func (b *IndexBoard) GetUploaderCommand() string { if runtime.GOOS == "windows" && b.UploaderCommand.Windows != "" { return b.UploaderCommand.Linux @@ -195,13 +165,3 @@ func (b *IndexBoard) GetUploaderCommand() string { // The linux uploader command is considere to be the generic one return b.UploaderCommand.Linux } - -// GetModule will take the board's fqbn and return the name of the module -func (i *Index) GetModule(fqbn string) (string, error) { - for _, board := range i.Boards { - if board.Fqbn == fqbn { - return board.Module, nil - } - } - return "", fmt.Errorf("invalid FQBN: %s", fqbn) -} diff --git a/indexes/firmwareindex/firmwareindex_test.go b/indexes/firmwareindex/firmwareindex_test.go index 41b49073..e7326ece 100644 --- a/indexes/firmwareindex/firmwareindex_test.go +++ b/indexes/firmwareindex/firmwareindex_test.go @@ -6,7 +6,6 @@ modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. - This library 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 @@ -38,71 +37,49 @@ func TestIndexParsing(t *testing.T) { require.NotEmpty(t, index) } -func TestGetLatestFirmwareURL(t *testing.T) { - indexFile := paths.New("testdata/module_firmware_index.json") - t.Logf("testing with index: %s", indexFile) - index, e := LoadIndexNoSign(indexFile) - require.NoError(t, e) - require.NotEmpty(t, index) - - result, err := index.GetLatestFirmwareURL("arduino:samd:mkr1000") - require.NoError(t, err) - require.NotEmpty(t, result) - require.Equal(t, "https://downloads.arduino.cc/arduino-fwuploader/firmwares/WINC1500/19.6.1/m2m_aio_3a0.bin", result) - - result, err = index.GetLatestFirmwareURL("arduino:samd:mkr1001") - require.Error(t, err) - require.Empty(t, result) -} - -func TestGetFirmwareURL(t *testing.T) { +func TestGetBoard(t *testing.T) { indexFile := paths.New("testdata/module_firmware_index.json") t.Logf("testing with index: %s", indexFile) index, e := LoadIndexNoSign(indexFile) require.NoError(t, e) require.NotEmpty(t, index) - result, err := index.GetFirmwareURL("arduino:samd:mkr1000", "19.6.1") - require.NoError(t, err) - require.NotEmpty(t, result) + board := index.GetBoard("arduino:samd:mkr1000") + require.NotNil(t, board) + require.Equal(t, board.Fqbn, "arduino:samd:mkr1000") - result, err = index.GetFirmwareURL("arduino:samd:mkr1000", "0.0.0") - require.Error(t, err) - require.Empty(t, result) + board = index.GetBoard("arduino:samd:nano_33_iot") + require.NotNil(t, board) + require.Equal(t, board.Fqbn, "arduino:samd:nano_33_iot") - result, err = index.GetFirmwareURL("arduino:samd:mkr1001", "19.6.1") - require.Error(t, err) - require.Empty(t, result) + board = index.GetBoard("arduino:avr:nessuno") + require.Nil(t, board) } -func TestGetLoaderSketchURL(t *testing.T) { +func TestGetLatestFirmware(t *testing.T) { indexFile := paths.New("testdata/module_firmware_index.json") t.Logf("testing with index: %s", indexFile) index, e := LoadIndexNoSign(indexFile) require.NoError(t, e) require.NotEmpty(t, index) - result, err := index.GetLoaderSketchURL("arduino:samd:mkr1000") - require.NoError(t, err) - require.NotEmpty(t, result) - - result, err = index.GetLoaderSketchURL("arduino:samd:mkr1001") - require.Error(t, err) - require.Empty(t, result) + firmware := index.GetBoard("arduino:samd:mkr1000").LatestFirmware + require.Equal(t, firmware.Version.String(), "19.6.1") } -func TestGetModule(t *testing.T) { +func TestGetFirmware(t *testing.T) { indexFile := paths.New("testdata/module_firmware_index.json") t.Logf("testing with index: %s", indexFile) index, e := LoadIndexNoSign(indexFile) require.NoError(t, e) require.NotEmpty(t, index) - result, err := index.GetModule("arduino:samd:mkr1000") - require.NoError(t, err) - require.Equal(t, result, "WINC1500") + firmware := index.GetBoard("arduino:samd:mkr1000").GetFirmware("19.6.1") + require.Equal(t, firmware.Version.String(), "19.6.1") + + firmware = index.GetBoard("arduino:samd:mkr1000").GetFirmware("19.5.2") + require.Equal(t, firmware.Version.String(), "19.5.2") - result, err = index.GetModule("arduino:samd:mkr1001") - require.Error(t, err) - require.Empty(t, result) + firmware = index.GetBoard("arduino:samd:mkr1000").GetFirmware("0.0.0") + require.Nil(t, firmware) } diff --git a/programmers/programmer.go b/programmers/programmer.go new file mode 100644 index 00000000..29970a4b --- /dev/null +++ b/programmers/programmer.go @@ -0,0 +1,37 @@ +/* + FirmwareUploader + Copyright (c) 2021 Arduino LLC. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package programmer + +import ( + "io" + + "github.com/arduino/arduino-cli/executils" +) + +// Flash runs the upload command and outputs to outStream and errStream +func Flash(command []string, outStream, errStream io.Writer) error { + cmd, err := executils.NewProcess(command...) + if err != nil { + return err + } + cmd.RedirectStdoutTo(outStream) + cmd.RedirectStderrTo(errStream) + return cmd.Run() +}