From acd718a61bf7b341ac9a86854f7831caf6c73215 Mon Sep 17 00:00:00 2001 From: Paolo Calao Date: Tue, 7 Sep 2021 16:55:40 +0200 Subject: [PATCH 1/9] Import ota generator --- go.mod | 4 +- go.sum | 5 +- internal/lzss/README.md | 0 internal/lzss/lzss.c | 194 ++++++++++++++++++++++++++++++ internal/lzss/lzss.go | 26 ++++ internal/lzss/lzss.h | 6 + internal/lzss/testdata/lorem.lzss | Bin 0 -> 1745 bytes internal/lzss/testdata/lorem.txt | 9 ++ internal/ota/encoder.go | 181 ++++++++++++++++++++++++++++ internal/ota/encoder_test.go | 57 +++++++++ internal/ota/testdata/lorem.lzss | Bin 0 -> 1745 bytes internal/ota/testdata/lorem.txt | 9 ++ internal/ota/version.go | 30 +++++ internal/ota/version_test.go | 34 ++++++ 14 files changed, 553 insertions(+), 2 deletions(-) create mode 100644 internal/lzss/README.md create mode 100644 internal/lzss/lzss.c create mode 100644 internal/lzss/lzss.go create mode 100644 internal/lzss/lzss.h create mode 100644 internal/lzss/testdata/lorem.lzss create mode 100644 internal/lzss/testdata/lorem.txt create mode 100644 internal/ota/encoder.go create mode 100644 internal/ota/encoder_test.go create mode 100644 internal/ota/testdata/lorem.lzss create mode 100644 internal/ota/testdata/lorem.txt create mode 100644 internal/ota/version.go create mode 100644 internal/ota/version_test.go diff --git a/go.mod b/go.mod index d272dbff..b62092bf 100644 --- a/go.mod +++ b/go.mod @@ -5,9 +5,10 @@ go 1.15 require ( github.com/antihax/optional v1.0.0 github.com/arduino/arduino-cli v0.0.0-20210607095659-16f41352eac3 - github.com/arduino/go-paths-helper v1.6.0 + github.com/arduino/go-paths-helper v1.6.1 github.com/arduino/iot-client-go v1.3.4-0.20210902151346-1cd63fb0c784 github.com/howeyc/crc16 v0.0.0-20171223171357-2b2a61e366a6 + github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5 github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v1.1.3 github.com/spf13/viper v1.7.0 @@ -21,4 +22,5 @@ require ( google.golang.org/grpc v1.39.0 google.golang.org/protobuf v1.27.1 // indirect gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c + gotest.tools v2.2.0+incompatible ) diff --git a/go.sum b/go.sum index ad9e7e37..cd2a3411 100644 --- a/go.sum +++ b/go.sum @@ -52,8 +52,9 @@ github.com/arduino/board-discovery v0.0.0-20180823133458-1ba29327fb0c h1:agh2JT9 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.6.0 h1:S7/d7DqB9XlnvF9KrgSiGmo2oWKmYW6O/DTjj3Bijx4= 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= github.com/arduino/go-properties-orderedmap v1.3.0 h1:4No/vQopB36e7WUIk6H6TxiSEJPiMrVOCZylYmua39o= github.com/arduino/go-properties-orderedmap v1.3.0/go.mod h1:DKjD2VXY/NZmlingh4lSFMEYCVubfeArCsGPGDwb2yk= github.com/arduino/go-timeutils v0.0.0-20171220113728-d1dd9e313b1b/go.mod h1:uwGy5PpN4lqW97FiLnbcx+xx8jly5YuPMJWfVwwjJiQ= @@ -739,6 +740,8 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/lzss/README.md b/internal/lzss/README.md new file mode 100644 index 00000000..e69de29b diff --git a/internal/lzss/lzss.c b/internal/lzss/lzss.c new file mode 100644 index 00000000..782a65e6 --- /dev/null +++ b/internal/lzss/lzss.c @@ -0,0 +1,194 @@ +/* LZSS encoder-decoder (Haruhiko Okumura; public domain) */ + +#include +#include + +#define EI 11 /* typically 10..13 */ +#define EJ 4 /* typically 4..5 */ +#define P 1 /* If match length <= P then output one character */ +#define N (1 << EI) /* buffer size */ +#define F ((1 << EJ) + 1) /* lookahead buffer size */ + +int bit_buffer = 0, bit_mask = 128; +unsigned long codecount = 0, textcount = 0; +unsigned char buffer[N * 2]; +FILE *infile, *outfile; + +void error(void) +{ + printf("Output error\n"); exit(1); +} + +void putbit1(void) +{ + bit_buffer |= bit_mask; + if ((bit_mask >>= 1) == 0) { + if (fputc(bit_buffer, outfile) == EOF) error(); + bit_buffer = 0; bit_mask = 128; codecount++; + } +} + +void putbit0(void) +{ + if ((bit_mask >>= 1) == 0) { + if (fputc(bit_buffer, outfile) == EOF) error(); + bit_buffer = 0; bit_mask = 128; codecount++; + } +} + +void flush_bit_buffer(void) +{ + if (bit_mask != 128) { + if (fputc(bit_buffer, outfile) == EOF) error(); + codecount++; + } +} + +void output1(int c) +{ + int mask; + + putbit1(); + mask = 256; + while (mask >>= 1) { + if (c & mask) putbit1(); + else putbit0(); + } +} + +void output2(int x, int y) +{ + int mask; + + putbit0(); + mask = N; + while (mask >>= 1) { + if (x & mask) putbit1(); + else putbit0(); + } + mask = (1 << EJ); + while (mask >>= 1) { + if (y & mask) putbit1(); + else putbit0(); + } +} + +void encode(void) +{ + int i, j, f1, x, y, r, s, bufferend, c; + + for (i = 0; i < N - F; i++) buffer[i] = ' '; + for (i = N - F; i < N * 2; i++) { + if ((c = fgetc(infile)) == EOF) break; + buffer[i] = c; textcount++; + } + bufferend = i; r = N - F; s = 0; + while (r < bufferend) { + f1 = (F <= bufferend - r) ? F : bufferend - r; + x = 0; y = 1; c = buffer[r]; + for (i = r - 1; i >= s; i--) + if (buffer[i] == c) { + for (j = 1; j < f1; j++) + if (buffer[i + j] != buffer[r + j]) break; + if (j > y) { + x = i; y = j; + } + } + if (y <= P) { y = 1; output1(c); } + else output2(x & (N - 1), y - 2); + r += y; s += y; + if (r >= N * 2 - F) { + for (i = 0; i < N; i++) buffer[i] = buffer[i + N]; + bufferend -= N; r -= N; s -= N; + while (bufferend < N * 2) { + if ((c = fgetc(infile)) == EOF) break; + buffer[bufferend++] = c; textcount++; + } + } + } + flush_bit_buffer(); +// printf("text: %ld bytes\n", textcount); +// printf("code: %ld bytes (%ld%%)\n", +// codecount, (codecount * 100) / textcount); +} + +int getbit(int n) /* get n bits */ +{ + int i, x; + static int buf, mask = 0; + + x = 0; + for (i = 0; i < n; i++) { + if (mask == 0) { + if ((buf = fgetc(infile)) == EOF) return EOF; + mask = 128; + } + x <<= 1; + if (buf & mask) x++; + mask >>= 1; + } + return x; +} + +void decode(void) +{ + int i, j, k, r, c; + + for (i = 0; i < N - F; i++) buffer[i] = ' '; + r = N - F; + while ((c = getbit(1)) != EOF) { + if (c) { + if ((c = getbit(8)) == EOF) break; + fputc(c, outfile); + buffer[r++] = c; r &= (N - 1); + } else { + if ((i = getbit(EI)) == EOF) break; + if ((j = getbit(EJ)) == EOF) break; + for (k = 0; k <= j + 1; k++) { + c = buffer[(i + k) & (N - 1)]; + fputc(c, outfile); + buffer[r++] = c; r &= (N - 1); + } + } + } +} + +int encode_file(char const * in, char const * out) +{ + // reset counters + bit_buffer = 0, bit_mask = 128; + codecount = 0, textcount = 0; + + infile = fopen(in, "rb"); + if (infile == NULL) return 0; + + outfile = fopen(out, "wb"); + if (outfile == NULL) return 0; + + encode(); + + fclose(infile); + fclose(outfile); + + return 0; +} + +int decode_file(char const * in, char const * out) +{ + // reset counters + bit_buffer = 0, bit_mask = 128; + codecount = 0, textcount = 0; + + infile = fopen(in, "rb"); + if (infile == NULL) return 0; + + outfile = fopen(out, "wb"); + if (outfile == NULL) return 0; + + decode(); + + fclose(infile); + fclose(outfile); + + return 0; +} diff --git a/internal/lzss/lzss.go b/internal/lzss/lzss.go new file mode 100644 index 00000000..51ea3c2d --- /dev/null +++ b/internal/lzss/lzss.go @@ -0,0 +1,26 @@ +package lzss + +// #cgo CFLAGS: -g -Wall +// #include +// #include "lzss.h" +import "C" +import ( + // "fmt" + "sync" + "unsafe" +) + +func Encode(source, destination string) { + + var mutex sync.Mutex + + src := C.CString(source) + defer C.free(unsafe.Pointer(src)) + + dst := C.CString(destination) + defer C.free(unsafe.Pointer(dst)) + + mutex.Lock() + C.encode_file(src, dst) + mutex.Unlock() +} diff --git a/internal/lzss/lzss.h b/internal/lzss/lzss.h new file mode 100644 index 00000000..a4a44622 --- /dev/null +++ b/internal/lzss/lzss.h @@ -0,0 +1,6 @@ +#ifndef _LZSS_H +#define _LZSS_H + +int encode_file(char const * in, char const * out); + +#endif \ No newline at end of file diff --git a/internal/lzss/testdata/lorem.lzss b/internal/lzss/testdata/lorem.lzss new file mode 100644 index 0000000000000000000000000000000000000000..07d0070c2dc5f4a6a09e9873e8f75abbbb122670 GIT binary patch literal 1745 zcmV;?1}^!gTkcj{Y=YBpx!qsPESCFY_ymIIX}U-(Hrp-ISs=07?bkakj_3g4b${@% z*&oy`H#;4s-Di+lZGZ$4=><;fbGTV;z%Umu4haAeg`Vqdu)okTmdL7AR+(&599!ZAbb}$`~Wln9RL#mMGJ5wkS&G2<^;h(1|YcIKokME zK}-QK0e}IpE&%=j<`j@>3q^*;5yN5zP_Ti4Tmaw!PypmXi~*P}w=jtsa6JvU0|G&G zf8YyY00)p;Y#=BAUJ?tCLV#v~J&m%MF<9H0>DGqLlkV{+5%7k zDi$Ew5YZ4#fsg^M0TBT;3n15pplt+l0A&D1gfRpJgjR%SmdRllP_YOAiueaP51yg@i2t zH3W+QIKXYhk(hX1&L_ck9Iy%|ClLzu1#}a!BajI~0YMJ38lxPbC;%xzB7!7#BY?IO zXbv!lvJ~))c$63l*pJB-m=T~4uoY+y2$czuCfJ3gul#$2#~-_ zMAraV?3;k4#3n%CKwcKaZombi1iC#;4dg`7K1dq@awR1M2NSh7kS~BeAUv@v2n|;` z${>b2)i|IKa|8J}!4J>|5jLSUVLj(UASs|g5kb%p*EX>=ASmEBPyq2kp$lXX!9IpH zlRpMSK}JzGVF%MlfkAUWfwH6E3R@G&q*#vOj13%{5%3|9A-EOLBX9~JpirQRB}x>b zKdK`;q4Xnxrr{*v3fmBvBG3$gq{*Qmjf*0KD61SWAV8=X6L^Xf2h13dskje zc~*ERt9k(RMMg-z#N?`a1k}p(Mi56DhXy8|rI2DSi%e8mTxZlG$FKZxC#1JPm~6U9IH#gJs(gB62S zA;mC7rkP%$Ih|OYido}HsSHh)pyR**AosxgLc;)|;B{u4&~+&PB>7+hfX4!#CLm=0 z!sNy%FnQBK60s`L7xA|l@f+!(fvXfTj2J2?QKf_x3J?PWU=&3Kkd~Q%2?a4j7eRELmh4Rr}TS@poAK^F<+l_g0D-LVbqRb*6bVR_6g$*qX2 zz{7-@fIk2?5X(gB(02&7h_%F*#JIq^L{x;(1igTs1SN!bzYw0fR6}!fb&?mk-V9Elq*>jRBOokh~CUsXob+%h3ZA%EWgM? z5V4I`5pd9Q8HiZ}$o`xu!7)J1$oWla$y$(%g)#taWhKpZmDR|T*c4GVfWyf4K`KBS zMKnU#kLa3?NuY@=lY||R+emuqprp|V_Y#Mu|1Q@wQYL|fR3=93rYK$RMC$d3mCTGt nOfI-g>FhUTd?+pMC+wMSwPHJB4yd4L^TvSalZK;WCa|pP7H`TY literal 0 HcmV?d00001 diff --git a/internal/lzss/testdata/lorem.txt b/internal/lzss/testdata/lorem.txt new file mode 100644 index 00000000..c942efc7 --- /dev/null +++ b/internal/lzss/testdata/lorem.txt @@ -0,0 +1,9 @@ +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse ligula dui, imperdiet ut vulputate semper, sollicitudin ut eros. Aliquam erat volutpat. In hac habitasse platea dictumst. Nam non tortor sit amet mauris rutrum eleifend. Pellentesque vel justo nibh. Vivamus sem risus, pharetra eget egestas eget, venenatis a ipsum. Cras ultrices aliquam sagittis. Donec lacinia urna ac orci congue ut adipiscing dolor fringilla. Nullam nibh magna, bibendum vulputate ornare id, hendrerit et metus. Nullam dapibus neque quis mi laoreet molestie. Mauris et dui lacus, sit amet egestas purus. + +Donec accumsan elementum accumsan. Nullam gravida dictum diam non semper. Curabitur vel magna in velit accumsan pulvinar eget in lorem. Duis vitae ante velit, at hendrerit nibh. Pellentesque lacus urna, cursus ac semper sagittis, viverra at sem. Quisque ullamcorper odio dolor. In quis pretium lacus. Maecenas lacinia urna id massa congue blandit. Suspendisse dapibus eros sit amet neque fermentum imperdiet. Cras interdum pulvinar eleifend. Suspendisse molestie neque a risus imperdiet convallis. In interdum dignissim pharetra. Morbi lectus tortor, pulvinar quis eleifend in, placerat at risus. Sed aliquam diam at metus adipiscing blandit. + +Integer tristique metus vel ipsum pulvinar dignissim quis vel quam. Donec auctor aliquet bibendum. Morbi aliquet malesuada ultrices. Vivamus ac leo odio. Nam tristique eros non arcu porttitor non volutpat mauris tempus. Proin vestibulum suscipit pretium. Etiam elit tortor, dictum a gravida porta, congue id dolor. Duis eget est vitae elit facilisis blandit. Proin tincidunt felis et ipsum pharetra tempor. Fusce imperdiet vulputate magna, vel lacinia neque volutpat a. Vivamus a elit dolor. Aliquam sollicitudin dui et leo elementum mattis. Quisque suscipit, lorem id eleifend imperdiet, ipsum lorem pharetra purus, vel tempus lectus ligula id tortor. Morbi eget eros vel sapien scelerisque aliquam pellentesque sed turpis. Duis vel lorem non eros semper fringilla vitae vitae erat. + +Vivamus porttitor pulvinar tristique. Proin sed elit ipsum. Phasellus faucibus pulvinar dapibus. Praesent quis sem in purus ultrices imperdiet. Aenean ut nulla urna. In tristique tincidunt urna, nec adipiscing velit laoreet ut. Curabitur et ante sed libero tristique pellentesque. Quisque porttitor sodales ipsum ut rhoncus. Nunc vitae diam gravida orci aliquam cursus vitae ut sapien. Proin ullamcorper felis eu nulla dapibus nec faucibus odio hendrerit. Aenean lorem magna, fermentum in tristique sit amet, accumsan ut massa. Fusce tristique, lectus rhoncus commodo sagittis, ligula felis consequat arcu, id pretium enim dolor id mi. Donec facilisis pulvinar luctus. Pellentesque vitae condimentum risus. Nam quis elit a orci adipiscing bibendum. + +Ut quis felis lorem, dignissim varius turpis. Sed convallis dui semper mauris fermentum porta. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam erat volutpat. Proin lorem felis, scelerisque nec commodo non, porta eu metus. Nulla id augue a turpis mollis pellentesque. Aenean in lectus et leo tincidunt auctor eu interdum est. Nulla varius, lorem congue laoreet laoreet, felis quam ullamcorper ligula, non pellentesque dui ipsum quis odio. Etiam sit amet blandit leo. Aenean venenatis molestie eros, in fermentum ipsum dictum eget. Donec ultricies feugiat nisl, non molestie mi congue quis. Quisque mattis augue nec neque fringilla varius. Proin sollicitudin risus et elit pretium congue. Sed consequat eros sit amet felis pulvinar pulvinar. Morbi in turpis eu nulla cursus venenatis at ut urna. Donec vel lectus quis nisi aliquam varius. \ No newline at end of file diff --git a/internal/ota/encoder.go b/internal/ota/encoder.go new file mode 100644 index 00000000..b91c33df --- /dev/null +++ b/internal/ota/encoder.go @@ -0,0 +1,181 @@ +package ota + +import ( + "bufio" + "encoding/binary" + "hash/crc32" + "io" + "io/ioutil" + "log" + "os" + "strconv" + + "github.com/arduino/iot-cloud-cli/internal/lzss" + "github.com/juju/errors" +) + +// A writer is a buffered, flushable writer. +type writer interface { + io.Writer + Flush() error +} + +// encoder encodes a binary into an .ota file. +type encoder struct { + // w is the writer that compressed bytes are written to. + w writer + + // vendorID is the ID of the board vendor + vendorID string + + // is the ID of the board vendor is the ID of the board model + productID string +} + +// NewWriter creates a new `WriteCloser` for the the given VID/PID. +func NewWriter(w io.Writer, vendorID, productID string) io.WriteCloser { + bw, ok := w.(writer) + if !ok { + bw = bufio.NewWriter(w) + } + return &encoder{ + w: bw, + vendorID: vendorID, + productID: productID, + } +} + +// Write writes a compressed representation of p to e's underlying writer. +func (e *encoder) Write(binaryData []byte) (int, error) { + //log.Println("original binaryData is", len(binaryData), "bytes length") + + // Magic number (VID/PID) + magicNumber := make([]byte, 4) + vid, err := strconv.ParseUint(e.vendorID, 16, 16) + if err != nil { + return 0, errors.Annotate(err, "OTA encoder: failed to parse vendorID") + } + pid, err := strconv.ParseUint(e.productID, 16, 16) + if err != nil { + return 0, errors.Annotate(err, "OTA encoder: failed to parse productID") + } + + binary.LittleEndian.PutUint16(magicNumber[0:2], uint16(pid)) + binary.LittleEndian.PutUint16(magicNumber[2:4], uint16(vid)) + + // Version field (byte array of size 8) + version := Version{ + Compression: true, + } + + // Compress the compiled binary + compressed, err := e.compress(&binaryData) + if err != nil { + return 0, err + } + + // Prepend magic number and version field to payload + var binDataComplete []byte + binDataComplete = append(binDataComplete, magicNumber...) + binDataComplete = append(binDataComplete, version.AsBytes()...) + binDataComplete = append(binDataComplete, compressed...) + //log.Println("binDataComplete is", len(binDataComplete), "bytes length") + + headerSize, err := e.writeHeader(&binDataComplete) + if err != nil { + return headerSize, err + } + + payloadSize, err := e.writePayload(&binDataComplete) + if err != nil { + return payloadSize, err + } + + return headerSize + payloadSize, nil +} + +// Close closes the encoder, flushing any pending output. It does not close or +// flush e's underlying writer. +func (e *encoder) Close() error { + return e.w.Flush() +} + +func (e *encoder) writeHeader(binDataComplete *[]byte) (int, error) { + + // + // Write the length of the content + // + lengthAsBytes := make([]byte, 4) + binary.LittleEndian.PutUint32(lengthAsBytes, uint32(len(*binDataComplete))) + + n, err := e.w.Write(lengthAsBytes) + if err != nil { + return n, err + } + + // + // Calculate the checksum for binDataComplete + // + crc := crc32.ChecksumIEEE(*binDataComplete) + + // encode the checksum uint32 value as 4 bytes + crcAsBytes := make([]byte, 4) + binary.LittleEndian.PutUint32(crcAsBytes, crc) + + n, err = e.w.Write(crcAsBytes) + if err != nil { + return n, err + } + + return len(lengthAsBytes) + len(crcAsBytes), nil +} + +func (e *encoder) writePayload(data *[]byte) (int, error) { + + // write the payload + payloadSize, err := e.w.Write(*data) + if err != nil { + return payloadSize, err + } + + return payloadSize, nil +} + +func (e *encoder) compress(data *[]byte) ([]byte, error) { + + // create a tmp file for input + inputFile, err := ioutil.TempFile("", "ota-lzss-input") + if err != nil { + log.Fatal(err) + return nil, err + } + defer os.Remove(inputFile.Name()) + + // create a tmp file for output + outputFile, err := ioutil.TempFile("", "ota-lzss-output") + if err != nil { + log.Fatal(err) + return nil, err + } + defer os.Remove(outputFile.Name()) + + // write data in the input file + ioutil.WriteFile(inputFile.Name(), *data, 644) + if err != nil { + log.Fatal(err) + return nil, err + } + + // Compress the binary data using LZSS + lzss.Encode(inputFile.Name(), outputFile.Name()) + + // reads compressed data from output file and write it into + // the writer + compressed, err := ioutil.ReadFile(outputFile.Name()) + if err != nil { + log.Fatal(err) + return nil, err + } + + return compressed, nil +} diff --git a/internal/ota/encoder_test.go b/internal/ota/encoder_test.go new file mode 100644 index 00000000..a165c9ad --- /dev/null +++ b/internal/ota/encoder_test.go @@ -0,0 +1,57 @@ +package ota + +import ( + "bytes" + "encoding/hex" + "log" + + "fmt" + "hash/crc32" + "testing" + + "gotest.tools/assert" +) + +func TestComputeCrc32Checksum(t *testing.T) { + + data, _ := hex.DecodeString("DEADBEEF") + crc := crc32.ChecksumIEEE(data) + + assert.Equal(t, crc, uint32(2090640218)) +} + +func TestEncoderWrite(t *testing.T) { + + // Setup test data + data, _ := hex.DecodeString("DEADBEEF") // uncompressed, or 'ef 6b 77 de f0' (compressed w/ LZSS) + + var w bytes.Buffer + vendorID := "2341" // Arduino + productID := "8054" // MRK Wifi 1010 + + otaWriter := NewWriter(&w, vendorID, productID) + defer otaWriter.Close() + + n, err := otaWriter.Write(data) + if err != nil { + t.Error(err) + t.Fail() + } + log.Println("written ota of", n, "bytes length") + + otaWriter.Close() + actual := w.Bytes() + + // You can get the expected result creating an `.ota` file using Alex's tools: + // https://github.com/arduino-libraries/ArduinoIoTCloud/tree/master/extras/tools + expected, _ := hex.DecodeString("11000000a1744bd4548041230000000000000040ef6b77def0") + + res := bytes.Compare(expected, actual) + + if res != 0 { + fmt.Println("expected:", hex.Dump(expected), len(expected), "bytes") + fmt.Println("actual:", hex.Dump(actual), len(actual), "bytes") + } + + assert.Assert(t, res == 0) // 0 means equal +} diff --git a/internal/ota/testdata/lorem.lzss b/internal/ota/testdata/lorem.lzss new file mode 100644 index 0000000000000000000000000000000000000000..07d0070c2dc5f4a6a09e9873e8f75abbbb122670 GIT binary patch literal 1745 zcmV;?1}^!gTkcj{Y=YBpx!qsPESCFY_ymIIX}U-(Hrp-ISs=07?bkakj_3g4b${@% z*&oy`H#;4s-Di+lZGZ$4=><;fbGTV;z%Umu4haAeg`Vqdu)okTmdL7AR+(&599!ZAbb}$`~Wln9RL#mMGJ5wkS&G2<^;h(1|YcIKokME zK}-QK0e}IpE&%=j<`j@>3q^*;5yN5zP_Ti4Tmaw!PypmXi~*P}w=jtsa6JvU0|G&G zf8YyY00)p;Y#=BAUJ?tCLV#v~J&m%MF<9H0>DGqLlkV{+5%7k zDi$Ew5YZ4#fsg^M0TBT;3n15pplt+l0A&D1gfRpJgjR%SmdRllP_YOAiueaP51yg@i2t zH3W+QIKXYhk(hX1&L_ck9Iy%|ClLzu1#}a!BajI~0YMJ38lxPbC;%xzB7!7#BY?IO zXbv!lvJ~))c$63l*pJB-m=T~4uoY+y2$czuCfJ3gul#$2#~-_ zMAraV?3;k4#3n%CKwcKaZombi1iC#;4dg`7K1dq@awR1M2NSh7kS~BeAUv@v2n|;` z${>b2)i|IKa|8J}!4J>|5jLSUVLj(UASs|g5kb%p*EX>=ASmEBPyq2kp$lXX!9IpH zlRpMSK}JzGVF%MlfkAUWfwH6E3R@G&q*#vOj13%{5%3|9A-EOLBX9~JpirQRB}x>b zKdK`;q4Xnxrr{*v3fmBvBG3$gq{*Qmjf*0KD61SWAV8=X6L^Xf2h13dskje zc~*ERt9k(RMMg-z#N?`a1k}p(Mi56DhXy8|rI2DSi%e8mTxZlG$FKZxC#1JPm~6U9IH#gJs(gB62S zA;mC7rkP%$Ih|OYido}HsSHh)pyR**AosxgLc;)|;B{u4&~+&PB>7+hfX4!#CLm=0 z!sNy%FnQBK60s`L7xA|l@f+!(fvXfTj2J2?QKf_x3J?PWU=&3Kkd~Q%2?a4j7eRELmh4Rr}TS@poAK^F<+l_g0D-LVbqRb*6bVR_6g$*qX2 zz{7-@fIk2?5X(gB(02&7h_%F*#JIq^L{x;(1igTs1SN!bzYw0fR6}!fb&?mk-V9Elq*>jRBOokh~CUsXob+%h3ZA%EWgM? z5V4I`5pd9Q8HiZ}$o`xu!7)J1$oWla$y$(%g)#taWhKpZmDR|T*c4GVfWyf4K`KBS zMKnU#kLa3?NuY@=lY||R+emuqprp|V_Y#Mu|1Q@wQYL|fR3=93rYK$RMC$d3mCTGt nOfI-g>FhUTd?+pMC+wMSwPHJB4yd4L^TvSalZK;WCa|pP7H`TY literal 0 HcmV?d00001 diff --git a/internal/ota/testdata/lorem.txt b/internal/ota/testdata/lorem.txt new file mode 100644 index 00000000..c942efc7 --- /dev/null +++ b/internal/ota/testdata/lorem.txt @@ -0,0 +1,9 @@ +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse ligula dui, imperdiet ut vulputate semper, sollicitudin ut eros. Aliquam erat volutpat. In hac habitasse platea dictumst. Nam non tortor sit amet mauris rutrum eleifend. Pellentesque vel justo nibh. Vivamus sem risus, pharetra eget egestas eget, venenatis a ipsum. Cras ultrices aliquam sagittis. Donec lacinia urna ac orci congue ut adipiscing dolor fringilla. Nullam nibh magna, bibendum vulputate ornare id, hendrerit et metus. Nullam dapibus neque quis mi laoreet molestie. Mauris et dui lacus, sit amet egestas purus. + +Donec accumsan elementum accumsan. Nullam gravida dictum diam non semper. Curabitur vel magna in velit accumsan pulvinar eget in lorem. Duis vitae ante velit, at hendrerit nibh. Pellentesque lacus urna, cursus ac semper sagittis, viverra at sem. Quisque ullamcorper odio dolor. In quis pretium lacus. Maecenas lacinia urna id massa congue blandit. Suspendisse dapibus eros sit amet neque fermentum imperdiet. Cras interdum pulvinar eleifend. Suspendisse molestie neque a risus imperdiet convallis. In interdum dignissim pharetra. Morbi lectus tortor, pulvinar quis eleifend in, placerat at risus. Sed aliquam diam at metus adipiscing blandit. + +Integer tristique metus vel ipsum pulvinar dignissim quis vel quam. Donec auctor aliquet bibendum. Morbi aliquet malesuada ultrices. Vivamus ac leo odio. Nam tristique eros non arcu porttitor non volutpat mauris tempus. Proin vestibulum suscipit pretium. Etiam elit tortor, dictum a gravida porta, congue id dolor. Duis eget est vitae elit facilisis blandit. Proin tincidunt felis et ipsum pharetra tempor. Fusce imperdiet vulputate magna, vel lacinia neque volutpat a. Vivamus a elit dolor. Aliquam sollicitudin dui et leo elementum mattis. Quisque suscipit, lorem id eleifend imperdiet, ipsum lorem pharetra purus, vel tempus lectus ligula id tortor. Morbi eget eros vel sapien scelerisque aliquam pellentesque sed turpis. Duis vel lorem non eros semper fringilla vitae vitae erat. + +Vivamus porttitor pulvinar tristique. Proin sed elit ipsum. Phasellus faucibus pulvinar dapibus. Praesent quis sem in purus ultrices imperdiet. Aenean ut nulla urna. In tristique tincidunt urna, nec adipiscing velit laoreet ut. Curabitur et ante sed libero tristique pellentesque. Quisque porttitor sodales ipsum ut rhoncus. Nunc vitae diam gravida orci aliquam cursus vitae ut sapien. Proin ullamcorper felis eu nulla dapibus nec faucibus odio hendrerit. Aenean lorem magna, fermentum in tristique sit amet, accumsan ut massa. Fusce tristique, lectus rhoncus commodo sagittis, ligula felis consequat arcu, id pretium enim dolor id mi. Donec facilisis pulvinar luctus. Pellentesque vitae condimentum risus. Nam quis elit a orci adipiscing bibendum. + +Ut quis felis lorem, dignissim varius turpis. Sed convallis dui semper mauris fermentum porta. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam erat volutpat. Proin lorem felis, scelerisque nec commodo non, porta eu metus. Nulla id augue a turpis mollis pellentesque. Aenean in lectus et leo tincidunt auctor eu interdum est. Nulla varius, lorem congue laoreet laoreet, felis quam ullamcorper ligula, non pellentesque dui ipsum quis odio. Etiam sit amet blandit leo. Aenean venenatis molestie eros, in fermentum ipsum dictum eget. Donec ultricies feugiat nisl, non molestie mi congue quis. Quisque mattis augue nec neque fringilla varius. Proin sollicitudin risus et elit pretium congue. Sed consequat eros sit amet felis pulvinar pulvinar. Morbi in turpis eu nulla cursus venenatis at ut urna. Donec vel lectus quis nisi aliquam varius. \ No newline at end of file diff --git a/internal/ota/version.go b/internal/ota/version.go new file mode 100644 index 00000000..8fe68007 --- /dev/null +++ b/internal/ota/version.go @@ -0,0 +1,30 @@ +package ota + +// Version contains all the OTA header information +// Check out https://arduino.atlassian.net/wiki/spaces/RFC/pages/1616871540/OTA+header+structure for more +// information on the OTA header specs. +type Version struct { + HeaderVersion uint8 + Compression bool + Signature bool + Spare uint8 + PayloadTarget uint8 + PayloadMayor uint8 + PayloadMinor uint8 + PayloadPatch uint8 + PayloadBuildNum uint32 +} + +// AsBytes builds a 8 byte length representation of the Version Struct for the OTA update. +func (v *Version) AsBytes() []byte { + version := []byte{0, 0, 0, 0, 0, 0, 0, 0} + + // Set compression + if v.Compression { + version[7] = 0x40 + } + + // Other field are currently not implemented ¯\_(ツ)_/¯ + + return version +} diff --git a/internal/ota/version_test.go b/internal/ota/version_test.go new file mode 100644 index 00000000..5ef6858b --- /dev/null +++ b/internal/ota/version_test.go @@ -0,0 +1,34 @@ +package ota + +import ( + "bytes" + "fmt" + "os" + "testing" + "text/tabwriter" + + "gotest.tools/assert" +) + +func TestVersionWithCompressionEnabled(t *testing.T) { + + version := Version{ + Compression: true, + } + + expected := []byte{0, 0, 0, 0, 0, 0, 0, 0x40} + actual := version.AsBytes() + + // create a tabwriter for formatting the output + w := new(tabwriter.Writer) + + // Format in tab-separated columns with a tab stop of 8. + w.Init(os.Stdout, 0, 8, 0, '\t', 0) + + fmt.Fprintf(w, "Binary:\t%0.8bb (expected)\n", expected) + fmt.Fprintf(w, "Binary:\t%0.8bb (actual)\n", actual) + w.Flush() + + res := bytes.Compare(expected, actual) + assert.Assert(t, res == 0) // 0 means equal +} From 6d032deef12ca54a42e1a16258b9ef849153a9f0 Mon Sep 17 00:00:00 2001 From: Paolo Calao Date: Tue, 7 Sep 2021 17:43:34 +0200 Subject: [PATCH 2/9] Implement ota generation --- command/ota/generate.go | 51 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 command/ota/generate.go diff --git a/command/ota/generate.go b/command/ota/generate.go new file mode 100644 index 00000000..079f7dee --- /dev/null +++ b/command/ota/generate.go @@ -0,0 +1,51 @@ +package ota + +import ( + "bytes" + "errors" + "io/ioutil" + "os" + + inota "github.com/arduino/iot-cloud-cli/internal/ota" +) + +var ( + arduinoVendorID = "2341" + fqbnToPID = map[string]string{ + "arduino:samd:nano_33_iot": "8057", + "arduino:samd:mkr1000": "804E", + "arduino:samd:mkrgsm1400": "8052", + "arduino:samd:mkrnb1500": "8055", + "arduino:samd:mkrwifi1010": "8054", + "arduino:mbed_nano:nanorp2040connect": "005E", + "arduino:mbed_portenta:envie_m7": "025B", + } +) + +// Generate takes a .bin file and generate a .ota file. +func Generate(binFile, outFile string, fqbn string) error { + productID, ok := fqbnToPID[fqbn] + if !ok { + return errors.New("fqbn not valid") + } + + data, err := ioutil.ReadFile(binFile) + if err != nil { + return err + } + + var w bytes.Buffer + otaWriter := inota.NewWriter(&w, arduinoVendorID, productID) + _, err = otaWriter.Write(data) + if err != nil { + return err + } + otaWriter.Close() + + err = ioutil.WriteFile(outFile, w.Bytes(), os.FileMode(0644)) + if err != nil { + return err + } + + return nil +} From b86b4c6ee48615a6e9e4dc4dfbe8cc3f3579ef6a Mon Sep 17 00:00:00 2001 From: Paolo Calao Date: Wed, 8 Sep 2021 09:26:22 +0200 Subject: [PATCH 3/9] Add ota upload command --- cli/ota/ota.go | 17 ++++++++++++ cli/ota/upload.go | 45 +++++++++++++++++++++++++++++++ command/ota/upload.go | 60 ++++++++++++++++++++++++++++++++++++++++++ internal/iot/client.go | 16 +++++++++++ 4 files changed, 138 insertions(+) create mode 100644 cli/ota/ota.go create mode 100644 cli/ota/upload.go create mode 100644 command/ota/upload.go diff --git a/cli/ota/ota.go b/cli/ota/ota.go new file mode 100644 index 00000000..18e6a7c7 --- /dev/null +++ b/cli/ota/ota.go @@ -0,0 +1,17 @@ +package ota + +import ( + "github.com/spf13/cobra" +) + +func NewCommand() *cobra.Command { + otaCommand := &cobra.Command{ + Use: "ota", + Short: "Over The Air.", + Long: "Over The Air firmware update.", + } + + otaCommand.AddCommand(initUploadCommand()) + + return otaCommand +} diff --git a/cli/ota/upload.go b/cli/ota/upload.go new file mode 100644 index 00000000..49d1d70e --- /dev/null +++ b/cli/ota/upload.go @@ -0,0 +1,45 @@ +package ota + +import ( + "fmt" + + "github.com/arduino/iot-cloud-cli/command/ota" + "github.com/spf13/cobra" +) + +var uploadFlags struct { + deviceID string + file string +} + +func initUploadCommand() *cobra.Command { + uploadCommand := &cobra.Command{ + Use: "upload", + Short: "OTA upload", + Long: "OTA upload on a device of Arduino IoT Cloud", + RunE: runUploadCommand, + } + + uploadCommand.Flags().StringVarP(&uploadFlags.deviceID, "device-id", "d", "", "Device ID") + uploadCommand.Flags().StringVarP(&uploadFlags.file, "file", "", "", "OTA file") + + uploadCommand.MarkFlagRequired("device-id") + uploadCommand.MarkFlagRequired("file") + return uploadCommand +} + +func runUploadCommand(cmd *cobra.Command, args []string) error { + fmt.Printf("Uploading binary %s to device %s\n", uploadFlags.file, uploadFlags.deviceID) + + params := &ota.UploadParams{ + DeviceID: uploadFlags.deviceID, + File: uploadFlags.file, + } + err := ota.Upload(params) + if err != nil { + return err + } + + fmt.Println("Upload successfully started") + return nil +} diff --git a/command/ota/upload.go b/command/ota/upload.go new file mode 100644 index 00000000..94e92757 --- /dev/null +++ b/command/ota/upload.go @@ -0,0 +1,60 @@ +package ota + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + + "github.com/arduino/iot-cloud-cli/internal/config" + "github.com/arduino/iot-cloud-cli/internal/iot" +) + +// UploadParams contains the parameters needed to +// perform an OTA upload. +type UploadParams struct { + DeviceID string + File string +} + +// Upload command is used to upload a firmware OTA, +// on a device of Arduino IoT Cloud. +func Upload(params *UploadParams) error { + conf, err := config.Retrieve() + if err != nil { + return err + } + iotClient, err := iot.NewClient(conf.Client, conf.Secret) + if err != nil { + return err + } + + dev, err := iotClient.DeviceShow(params.DeviceID) + if err != nil { + return err + } + + otaDir, err := ioutil.TempDir("", "") + if err != nil { + return fmt.Errorf("%s: %w", "cannot create temporary folder", err) + } + otaFile := filepath.Join(otaDir, "temp.ota") + defer os.RemoveAll(otaDir) + + err = Generate(params.File, otaFile, dev.Fqbn) + if err != nil { + return fmt.Errorf("%s: %w", "cannot generate .ota file", err) + } + + file, err := os.Open(otaFile) + if err != nil { + return fmt.Errorf("%s: %w", "cannot open ota file", err) + } + + err = iotClient.DeviceOTA(params.DeviceID, file) + if err != nil { + return err + } + + return nil +} diff --git a/internal/iot/client.go b/internal/iot/client.go index 8f52f07c..41cbe198 100644 --- a/internal/iot/client.go +++ b/internal/iot/client.go @@ -3,6 +3,7 @@ package iot import ( "context" "fmt" + "os" "github.com/antihax/optional" iotclient "github.com/arduino/iot-client-go" @@ -14,6 +15,7 @@ type Client interface { DeviceDelete(id string) error DeviceList() ([]iotclient.ArduinoDevicev2, error) DeviceShow(id string) (*iotclient.ArduinoDevicev2, error) + DeviceOTA(id string, file *os.File) error CertificateCreate(id, csr string) (*iotclient.ArduinoCompressedv2, error) ThingCreate(thing *iotclient.Thing, force bool) (*iotclient.ArduinoThing, error) ThingUpdate(id string, thing *iotclient.Thing, force bool) error @@ -89,6 +91,20 @@ func (cl *client) DeviceShow(id string) (*iotclient.ArduinoDevicev2, error) { return &dev, nil } +// DeviceOTA performs an OTA upload request to Arduino IoT Cloud, passing +// the ID of the device to be updated and the actual file containing the OTA firmware. +func (cl *client) DeviceOTA(id string, file *os.File) error { + opt := &iotclient.DevicesV2OtaUploadOpts{ + ExpireInMins: optional.NewInt32(5), + } + _, err := cl.api.DevicesV2OtaApi.DevicesV2OtaUpload(cl.ctx, id, file, opt) + if err != nil { + err = fmt.Errorf("listing devices: %w", errorDetail(err)) + return err + } + return nil +} + // CertificateCreate allows to upload a certificate on Arduino IoT Cloud. // It returns the certificate parameters populated by the cloud. func (cl *client) CertificateCreate(id, csr string) (*iotclient.ArduinoCompressedv2, error) { From 58ec5ef66d80257bc03018f3d14d6c3c44bbb149 Mon Sep 17 00:00:00 2001 From: Paolo Calao Date: Mon, 13 Sep 2021 10:46:27 +0200 Subject: [PATCH 4/9] Adapt to format-output changes --- cli/cli.go | 2 ++ cli/ota/upload.go | 17 ++++++++++------- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/cli/cli.go b/cli/cli.go index afb1ddf5..555bce3c 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -10,6 +10,7 @@ import ( "github.com/arduino/arduino-cli/cli/feedback" "github.com/arduino/iot-cloud-cli/cli/config" "github.com/arduino/iot-cloud-cli/cli/device" + "github.com/arduino/iot-cloud-cli/cli/ota" "github.com/arduino/iot-cloud-cli/cli/thing" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -31,6 +32,7 @@ func Execute() { cli.AddCommand(config.NewCommand()) cli.AddCommand(device.NewCommand()) cli.AddCommand(thing.NewCommand()) + cli.AddCommand(ota.NewCommand()) cli.PersistentFlags().BoolVarP(&cliFlags.verbose, "verbose", "v", false, "Print the logs on the standard output.") cli.PersistentFlags().StringVar(&cliFlags.outputFormat, "format", "text", "The output format, can be {text|json}.") diff --git a/cli/ota/upload.go b/cli/ota/upload.go index 49d1d70e..26c8500a 100644 --- a/cli/ota/upload.go +++ b/cli/ota/upload.go @@ -1,9 +1,12 @@ package ota import ( - "fmt" + "os" + "github.com/arduino/arduino-cli/cli/errorcodes" + "github.com/arduino/arduino-cli/cli/feedback" "github.com/arduino/iot-cloud-cli/command/ota" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -17,7 +20,7 @@ func initUploadCommand() *cobra.Command { Use: "upload", Short: "OTA upload", Long: "OTA upload on a device of Arduino IoT Cloud", - RunE: runUploadCommand, + Run: runUploadCommand, } uploadCommand.Flags().StringVarP(&uploadFlags.deviceID, "device-id", "d", "", "Device ID") @@ -28,8 +31,8 @@ func initUploadCommand() *cobra.Command { return uploadCommand } -func runUploadCommand(cmd *cobra.Command, args []string) error { - fmt.Printf("Uploading binary %s to device %s\n", uploadFlags.file, uploadFlags.deviceID) +func runUploadCommand(cmd *cobra.Command, args []string) { + logrus.Infof("Uploading binary %s to device %s", uploadFlags.file, uploadFlags.deviceID) params := &ota.UploadParams{ DeviceID: uploadFlags.deviceID, @@ -37,9 +40,9 @@ func runUploadCommand(cmd *cobra.Command, args []string) error { } err := ota.Upload(params) if err != nil { - return err + feedback.Errorf("Error during ota upload: %v", err) + os.Exit(errorcodes.ErrGeneric) } - fmt.Println("Upload successfully started") - return nil + logrus.Info("Upload successfully started") } From 3c6bb55e45cecfacaac6133f9d22776f4f55ea21 Mon Sep 17 00:00:00 2001 From: Paolo Calao Date: Thu, 9 Sep 2021 10:53:59 +0200 Subject: [PATCH 5/9] Fix typos --- command/ota/generate.go | 4 ++-- internal/iot/client.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/command/ota/generate.go b/command/ota/generate.go index 079f7dee..3f36fae7 100644 --- a/command/ota/generate.go +++ b/command/ota/generate.go @@ -22,8 +22,8 @@ var ( } ) -// Generate takes a .bin file and generate a .ota file. -func Generate(binFile, outFile string, fqbn string) error { +// Generate takes a .bin file and generates a .ota file. +func Generate(binFile string, outFile string, fqbn string) error { productID, ok := fqbnToPID[fqbn] if !ok { return errors.New("fqbn not valid") diff --git a/internal/iot/client.go b/internal/iot/client.go index 41cbe198..2a291b3a 100644 --- a/internal/iot/client.go +++ b/internal/iot/client.go @@ -99,7 +99,7 @@ func (cl *client) DeviceOTA(id string, file *os.File) error { } _, err := cl.api.DevicesV2OtaApi.DevicesV2OtaUpload(cl.ctx, id, file, opt) if err != nil { - err = fmt.Errorf("listing devices: %w", errorDetail(err)) + err = fmt.Errorf("uploading device ota: %w", errorDetail(err)) return err } return nil From 6a844772e6e8a14635a08ffb995f0d82cc5774c2 Mon Sep 17 00:00:00 2001 From: Paolo Calao Date: Fri, 10 Sep 2021 10:03:11 +0200 Subject: [PATCH 6/9] Improve ota upload help --- cli/ota/upload.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/ota/upload.go b/cli/ota/upload.go index 26c8500a..5caa7219 100644 --- a/cli/ota/upload.go +++ b/cli/ota/upload.go @@ -24,7 +24,7 @@ func initUploadCommand() *cobra.Command { } uploadCommand.Flags().StringVarP(&uploadFlags.deviceID, "device-id", "d", "", "Device ID") - uploadCommand.Flags().StringVarP(&uploadFlags.file, "file", "", "", "OTA file") + uploadCommand.Flags().StringVarP(&uploadFlags.file, "file", "", "", "Binary file (.bin) to be uploaded") uploadCommand.MarkFlagRequired("device-id") uploadCommand.MarkFlagRequired("file") From d19303a0ebc9f84c1234e0017b4aa201c1a9d84a Mon Sep 17 00:00:00 2001 From: Paolo Calao Date: Fri, 10 Sep 2021 20:31:31 +0200 Subject: [PATCH 7/9] Add deferred option --- cli/ota/upload.go | 2 ++ command/ota/upload.go | 13 ++++++++++++- internal/iot/client.go | 6 +++--- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/cli/ota/upload.go b/cli/ota/upload.go index 5caa7219..3805319e 100644 --- a/cli/ota/upload.go +++ b/cli/ota/upload.go @@ -13,6 +13,7 @@ import ( var uploadFlags struct { deviceID string file string + deferred bool } func initUploadCommand() *cobra.Command { @@ -25,6 +26,7 @@ func initUploadCommand() *cobra.Command { uploadCommand.Flags().StringVarP(&uploadFlags.deviceID, "device-id", "d", "", "Device ID") uploadCommand.Flags().StringVarP(&uploadFlags.file, "file", "", "", "Binary file (.bin) to be uploaded") + uploadCommand.Flags().BoolVar(&uploadFlags.deferred, "deferred", false, "Perform a deferred OTA. It can take up to 1 week.") uploadCommand.MarkFlagRequired("device-id") uploadCommand.MarkFlagRequired("file") diff --git a/command/ota/upload.go b/command/ota/upload.go index 94e92757..223a4fbb 100644 --- a/command/ota/upload.go +++ b/command/ota/upload.go @@ -10,11 +10,17 @@ import ( "github.com/arduino/iot-cloud-cli/internal/iot" ) +const ( + otaExpirationMins = 10 + otaDeferredExpirationMins = 10000 +) + // UploadParams contains the parameters needed to // perform an OTA upload. type UploadParams struct { DeviceID string File string + Deferred bool } // Upload command is used to upload a firmware OTA, @@ -51,7 +57,12 @@ func Upload(params *UploadParams) error { return fmt.Errorf("%s: %w", "cannot open ota file", err) } - err = iotClient.DeviceOTA(params.DeviceID, file) + expiration := otaExpirationMins + if params.Deferred { + expiration = otaDeferredExpirationMins + } + + err = iotClient.DeviceOTA(params.DeviceID, file, expiration) if err != nil { return err } diff --git a/internal/iot/client.go b/internal/iot/client.go index 2a291b3a..50ec7471 100644 --- a/internal/iot/client.go +++ b/internal/iot/client.go @@ -15,7 +15,7 @@ type Client interface { DeviceDelete(id string) error DeviceList() ([]iotclient.ArduinoDevicev2, error) DeviceShow(id string) (*iotclient.ArduinoDevicev2, error) - DeviceOTA(id string, file *os.File) error + DeviceOTA(id string, file *os.File, expireMins int) error CertificateCreate(id, csr string) (*iotclient.ArduinoCompressedv2, error) ThingCreate(thing *iotclient.Thing, force bool) (*iotclient.ArduinoThing, error) ThingUpdate(id string, thing *iotclient.Thing, force bool) error @@ -93,9 +93,9 @@ func (cl *client) DeviceShow(id string) (*iotclient.ArduinoDevicev2, error) { // DeviceOTA performs an OTA upload request to Arduino IoT Cloud, passing // the ID of the device to be updated and the actual file containing the OTA firmware. -func (cl *client) DeviceOTA(id string, file *os.File) error { +func (cl *client) DeviceOTA(id string, file *os.File, expireMins int) error { opt := &iotclient.DevicesV2OtaUploadOpts{ - ExpireInMins: optional.NewInt32(5), + ExpireInMins: optional.NewInt32(int32(expireMins)), } _, err := cl.api.DevicesV2OtaApi.DevicesV2OtaUpload(cl.ctx, id, file, opt) if err != nil { From 9e8ddb8ec93395263a8d02ccaeb9dfb4317adcfa Mon Sep 17 00:00:00 2001 From: Paolo Calao Date: Mon, 13 Sep 2021 09:46:39 +0200 Subject: [PATCH 8/9] Improve deferred ota --- command/ota/upload.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/command/ota/upload.go b/command/ota/upload.go index 223a4fbb..00c048c8 100644 --- a/command/ota/upload.go +++ b/command/ota/upload.go @@ -11,8 +11,10 @@ import ( ) const ( - otaExpirationMins = 10 - otaDeferredExpirationMins = 10000 + // default ota should complete in 10 mins + otaExpirationMins = 10 + // deferred ota can take up to 1 week (equal to 10080 minutes) + otaDeferredExpirationMins = 10080 ) // UploadParams contains the parameters needed to From 890be53158e74750731fbb20e29b591a5b39f1c0 Mon Sep 17 00:00:00 2001 From: Paolo Calao Date: Mon, 13 Sep 2021 10:04:47 +0200 Subject: [PATCH 9/9] Update readme --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 7c28a89d..b12cd434 100644 --- a/README.md +++ b/README.md @@ -79,3 +79,12 @@ Extract a template from an existing thing. The template can be saved in two form Bind a thing to an existing device: `$ iot-cloud-cli thing bind --id --device-id ` + +## Ota commands + +Perform an OTA firmware update. Note that the binary file (`.bin`) should be compiled using an arduino core that supports the specified device. +The default OTA upload should complete in 10 minutes. Use `--deferred` flag to extend this time to one week. + +`$ iot-cloud-cli ota upload --device-id --file ` + +`$ iot-cloud-cli ota upload --device-id --file --deferred`