github.com/arduino/arduino-cloud-cli@v0.0.0-20240517070944-e7a449561083/internal/ota/encoder.go (about) 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 "strconv" 26 27 "github.com/arduino/arduino-cloud-cli/internal/lzss" 28 ) 29 30 // Encoder writes a binary to an output stream in the ota format. 31 type Encoder struct { 32 // w is the stream where encoded bytes are written. 33 w io.Writer 34 35 // vendorID is the ID of the board vendor. 36 magicNumberPart1 string 37 38 // productID is the ID of the board model. 39 magicNumberPart2 string 40 } 41 42 // NewEncoder creates a new ota encoder. 43 func NewEncoder(w io.Writer, magicNumberPart1, magicNumberPart2 string) *Encoder { 44 return &Encoder{ 45 w: w, 46 magicNumberPart1: magicNumberPart1, 47 magicNumberPart2: magicNumberPart2, 48 } 49 } 50 51 // Encode compresses data using a lzss algorithm, encodes the result 52 // in ota format and writes it to e's underlying writer. 53 func (e *Encoder) Encode(data []byte) error { 54 // Compute the magic number (VID/PID) 55 magicNumber := make([]byte, 4) 56 magicNumberPart1, err := strconv.ParseUint(e.magicNumberPart1, 16, 16) 57 if err != nil { 58 return fmt.Errorf("cannot parse vendorID: %w", err) 59 } 60 magicNumberPart2, err := strconv.ParseUint(e.magicNumberPart2, 16, 16) 61 if err != nil { 62 return fmt.Errorf("cannot parse productID: %w", err) 63 } 64 65 binary.LittleEndian.PutUint16(magicNumber[0:2], uint16(magicNumberPart2)) 66 binary.LittleEndian.PutUint16(magicNumber[2:4], uint16(magicNumberPart1)) 67 68 // Version field (byte array of size 8) 69 version := Version{ 70 Compression: true, 71 } 72 73 compressed := lzss.Encode(data) 74 // Prepend magic number and version field to payload 75 var outData []byte 76 outData = append(outData, magicNumber...) 77 outData = append(outData, version.Bytes()...) 78 outData = append(outData, compressed...) 79 80 err = e.writeHeader(outData) 81 if err != nil { 82 return fmt.Errorf("cannot write data header to output stream: %w", err) 83 } 84 85 _, err = e.w.Write(outData) 86 if err != nil { 87 return fmt.Errorf("cannot write encoded data to output stream: %w", err) 88 } 89 90 return nil 91 } 92 93 func (e *Encoder) writeHeader(data []byte) error { 94 // Write the length of the content 95 lengthAsBytes := make([]byte, 4) 96 binary.LittleEndian.PutUint32(lengthAsBytes, uint32(len(data))) 97 _, err := e.w.Write(lengthAsBytes) 98 if err != nil { 99 return err 100 } 101 102 // Write the checksum uint32 value as 4 bytes 103 crc := crc32.ChecksumIEEE(data) 104 crcAsBytes := make([]byte, 4) 105 binary.LittleEndian.PutUint32(crcAsBytes, crc) 106 _, err = e.w.Write(crcAsBytes) 107 if err != nil { 108 return err 109 } 110 111 return nil 112 }