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  }