From 3112875cabfad586dde39e281286e2d3695c2f5a Mon Sep 17 00:00:00 2001 From: Paolo Calao Date: Fri, 9 Jul 2021 18:48:58 +0200 Subject: [PATCH 1/2] Wrap serial --- go.mod | 2 + go.sum | 6 +++ serial/protocol.go | 37 ++++++++++++++ serial/serial.go | 122 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 167 insertions(+) create mode 100644 serial/protocol.go create mode 100644 serial/serial.go diff --git a/go.mod b/go.mod index 6849dc56..a1db159a 100644 --- a/go.mod +++ b/go.mod @@ -7,9 +7,11 @@ require ( github.com/arduino/iot-client-go v1.3.3 github.com/bcmi-labs/oniudra-cli v0.15.8 github.com/eclipse/paho.mqtt.golang v1.3.2 + github.com/howeyc/crc16 v0.0.0-20171223171357-2b2a61e366a6 github.com/juju/errors v0.0.0-20200330140219-3fe23663418f github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v1.1.3 + go.bug.st/serial v1.3.0 golang.org/x/net v0.0.0-20210505024714-0287a6fb4125 // indirect golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914 golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6 // indirect diff --git a/go.sum b/go.sum index 632ab011..bf23225e 100644 --- a/go.sum +++ b/go.sum @@ -136,6 +136,8 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7 github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/goselect v0.1.1/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY= +github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0= +github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E= @@ -295,6 +297,8 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/howeyc/crc16 v0.0.0-20171223171357-2b2a61e366a6 h1:IIVxLyDUYErC950b8kecjoqDet8P5S4lcVRUOM6rdkU= +github.com/howeyc/crc16 v0.0.0-20171223171357-2b2a61e366a6/go.mod h1:JslaLRrzGsOKJgFEPBP65Whn+rdwDQSk0I0MCRFe2Zw= github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -501,6 +505,8 @@ go.bug.st/cleanup v1.0.0/go.mod h1:EqVmTg2IBk4znLbPD28xne3abjsJftMdqqJEjhn70bk= go.bug.st/downloader/v2 v2.1.1/go.mod h1:VZW2V1iGKV8rJL2ZEGIDzzBeKowYv34AedJz13RzVII= go.bug.st/relaxed-semver v0.0.0-20190922224835-391e10178d18/go.mod h1:Cx1VqMtEhE9pIkEyUj3LVVVPkv89dgW8aCKrRPDR/uE= go.bug.st/serial v1.1.2/go.mod h1:VmYBeyJWp5BnJ0tw2NUJHZdJTGl2ecBGABHlzRK1knY= +go.bug.st/serial v1.3.0 h1:liPN6f/Xk0qaUByg0H2LOSns+2RuAuNXmXZyQOLVwVE= +go.bug.st/serial v1.3.0/go.mod h1:8TT7u/SwwNIpJ8QaG4s+HTjFt9ReXs2cdOU7ZEk50Dk= go.bug.st/serial.v1 v0.0.0-20180827123349-5f7892a7bb45/go.mod h1:dRSl/CVCTf56CkXgJMDOdSwNfo2g1orOGE/gBGdvjZw= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.mozilla.org/gopgagent v0.0.0-20170926210634-4d7ea76ff71a/go.mod h1:YDKUvO0b//78PaaEro6CAPH6NqohCmL2Cwju5XI2HoE= diff --git a/serial/protocol.go b/serial/protocol.go new file mode 100644 index 00000000..8e8888a5 --- /dev/null +++ b/serial/protocol.go @@ -0,0 +1,37 @@ +package serial + +var ( + msgStart = [2]byte{0x55, 0xAA} + msgEnd = [2]byte{0xAA, 0x55} +) + +type MsgType byte + +const ( + None MsgType = iota + Cmd + Data + Response +) + +type Command byte + +const ( + SketchInfo Command = iota + 1 + CSR + Locked + GetLocked + WriteCrypto + BeginStorage + SetDeviceID + SetYear + SetMonth + SetDay + SetHour + SetValidity + SetCertSerial + SetAuthKey + SetSignature + EndStorage + ReconstructCert +) diff --git a/serial/serial.go b/serial/serial.go new file mode 100644 index 00000000..42ff104d --- /dev/null +++ b/serial/serial.go @@ -0,0 +1,122 @@ +package serial + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "time" + + "github.com/howeyc/crc16" + "go.bug.st/serial" +) + +type Serial struct { + port serial.Port +} + +func NewSerial() *Serial { + s := &Serial{} + return s +} + +func (s *Serial) Connect(address string) error { + mode := &serial.Mode{ + BaudRate: 57600, + } + port, err := serial.Open(address, mode) + if err != nil { + err = fmt.Errorf("%s: %w", "connecting to serial port", err) + return err + } + s.port = port + + s.port.SetReadTimeout(time.Millisecond * 2000) + return nil +} + +func (s *Serial) Send(cmd Command, payload []byte) error { + payload = append([]byte{byte(cmd)}, payload...) + msg := encode(Cmd, payload) + + _, err := s.port.Write(msg) + if err != nil { + err = fmt.Errorf("%s: %w", "sending message through serial", err) + return err + } + + return nil +} + +func (s *Serial) SendReceive(cmd Command, payload []byte) ([]byte, error) { + err := s.Send(cmd, payload) + if err != nil { + return nil, err + } + return s.Receive() +} + +func (s *Serial) Receive() ([]byte, error) { + buff := make([]byte, 1000) + var resp []byte + + received := 0 + packetLen := 0 + for received < packetLen+5+2 { + n, err := s.port.Read(buff) + if err != nil { + err = fmt.Errorf("%s: %w", "receiving from serial", err) + return nil, err + } + if n == 0 { + break + } + received += n + resp = append(resp, buff[:n]...) + + if packetLen == 0 && received >= 5 { + packetLen = int(binary.BigEndian.Uint16(resp[3:5])) + } + } + + if received == 0 { + err := errors.New("receiving from serial: timeout, nothing received") + return nil, err + } + + if !bytes.Equal(resp[received-2:], msgEnd[:]) { + err := errors.New("receiving from serial: end of message (0xAA, 0x55) not found") + return nil, err + } + + payload := resp[5 : packetLen+5-2] + ch := crc16.Checksum(payload, crc16.CCITTTable) + cp := binary.BigEndian.Uint16(resp[packetLen+5-2 : packetLen+5]) + if ch != cp { + err := errors.New("receiving from serial: signature of received message is not valid") + return nil, err + } + + return payload, nil +} + +func (s *Serial) Close() error { + return s.port.Close() +} + +func encode(mType MsgType, msg []byte) []byte { + packet := append(msgStart[:], byte(mType)) + + bLen := make([]byte, 2) + binary.BigEndian.PutUint16(bLen, (uint16(len(msg) + 2))) + packet = append(packet, bLen...) + + ch := crc16.Checksum(msg, crc16.CCITTTable) + checksum := make([]byte, 2) + binary.BigEndian.PutUint16(checksum, ch) + packet = append(packet, msg...) + packet = append(packet, checksum...) + + packet = append(packet, msgEnd[:]...) + return packet +} From 50bf4cd087461c445cdbddda95f3e679389c4622 Mon Sep 17 00:00:00 2001 From: Paolo Calao Date: Fri, 16 Jul 2021 14:14:37 +0200 Subject: [PATCH 2/2] Refactor serial wrapper as internal package - Move serial into internal package - Add comments - Do not export 'retrieve' function - Transform magic numbers into readable constants --- internal/serial/protocol.go | 53 ++++++++++++ internal/serial/serial.go | 155 ++++++++++++++++++++++++++++++++++++ serial/protocol.go | 37 --------- serial/serial.go | 122 ---------------------------- 4 files changed, 208 insertions(+), 159 deletions(-) create mode 100644 internal/serial/protocol.go create mode 100644 internal/serial/serial.go delete mode 100644 serial/protocol.go delete mode 100644 serial/serial.go diff --git a/internal/serial/protocol.go b/internal/serial/protocol.go new file mode 100644 index 00000000..06dd7e25 --- /dev/null +++ b/internal/serial/protocol.go @@ -0,0 +1,53 @@ +package serial + +var ( + // msgStart is the initial byte sequence of every packet + msgStart = [2]byte{0x55, 0xAA} + // msgEnd is the final byte sequence of every packet + msgEnd = [2]byte{0xAA, 0x55} +) + +const ( + // Position of payload field + payloadField = 5 + // Position of payload length field + payloadLenField = 3 + // Length of payload length field + payloadLenFieldLen = 2 + // Length of the signature field + crcFieldLen = 2 +) + +// MsgType indicates the type of the packet +type MsgType byte + +const ( + None MsgType = iota + Cmd + Data + Response +) + +// Command indicates the command that should be +// executed on the board to be provisioned. +type Command byte + +const ( + SketchInfo Command = iota + 1 + CSR + Locked + GetLocked + WriteCrypto + BeginStorage + SetDeviceID + SetYear + SetMonth + SetDay + SetHour + SetValidity + SetCertSerial + SetAuthKey + SetSignature + EndStorage + ReconstructCert +) diff --git a/internal/serial/serial.go b/internal/serial/serial.go new file mode 100644 index 00000000..6b95c831 --- /dev/null +++ b/internal/serial/serial.go @@ -0,0 +1,155 @@ +package serial + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "time" + + "github.com/howeyc/crc16" + "go.bug.st/serial" +) + +// Serial is a wrapper of serial port interface that +// features specific functions to send provisioning +// commands through the serial port to an arduino device. +type Serial struct { + port serial.Port +} + +// NewSerial instantiate and returns a Serial instance. +// The Serial Connect method should be called before using +// its send/receive functions. +func NewSerial() *Serial { + s := &Serial{} + return s +} + +// Connect tries to connect Serial to a specific serial port. +func (s *Serial) Connect(address string) error { + mode := &serial.Mode{ + BaudRate: 57600, + } + port, err := serial.Open(address, mode) + if err != nil { + err = fmt.Errorf("%s: %w", "connecting to serial port", err) + return err + } + s.port = port + + s.port.SetReadTimeout(time.Millisecond * 2000) + return nil +} + +// Send allows to send a provisioning command to a connected arduino device. +func (s *Serial) Send(cmd Command, payload []byte) error { + payload = append([]byte{byte(cmd)}, payload...) + msg := encode(Cmd, payload) + + _, err := s.port.Write(msg) + if err != nil { + err = fmt.Errorf("%s: %w", "sending message through serial", err) + return err + } + + return nil +} + +// SendReceive allows to send a provisioning command to a connected arduino device. +// Then, it waits for a response from the device and, if any, returns it. +// If no response is received after 2 seconds, an error is returned. +func (s *Serial) SendReceive(cmd Command, payload []byte) ([]byte, error) { + err := s.Send(cmd, payload) + if err != nil { + return nil, err + } + return s.receive() +} + +// Close should be used when the Serial connection isn't used anymore. +// After that, Serial could Connect again to any port. +func (s *Serial) Close() error { + return s.port.Close() +} + +// receive allows to wait for a response from an arduino device under provisioning. +// Its timeout is set to 2 seconds. It returns an error if the response is not valid +// or if the timeout expires. +// TODO: consider refactoring using a more explicit procedure: +// start := s.Read(buff, MsgStartLength) +// payloadLen := s.Read(buff, payloadFieldLen) +func (s *Serial) receive() ([]byte, error) { + buff := make([]byte, 1000) + var resp []byte + + received := 0 + payloadLen := 0 + // Wait to receive the entire packet that is long as the preamble (from msgStart to payload length field) + // plus the actual payload length plus the length of the ending sequence. + for received < (payloadLenField+payloadLenFieldLen)+payloadLen+len(msgEnd) { + n, err := s.port.Read(buff) + if err != nil { + err = fmt.Errorf("%s: %w", "receiving from serial", err) + return nil, err + } + if n == 0 { + break + } + received += n + resp = append(resp, buff[:n]...) + + // Update the payload length as soon as it is received. + if payloadLen == 0 && received >= (payloadLenField+payloadLenFieldLen) { + payloadLen = int(binary.BigEndian.Uint16(resp[payloadLenField:(payloadLenField + payloadLenFieldLen)])) + // TODO: return error if payloadLen is too large. + } + } + + if received == 0 { + err := errors.New("receiving from serial: timeout, nothing received") + return nil, err + } + + // TODO: check if msgStart is present + + if !bytes.Equal(resp[received-len(msgEnd):], msgEnd[:]) { + err := errors.New("receiving from serial: end of message (0xAA, 0x55) not found") + return nil, err + } + + payload := resp[payloadField : payloadField+payloadLen-crcFieldLen] + ch := crc16.Checksum(payload, crc16.CCITTTable) + // crc is contained in the last bytes of the payload + cp := binary.BigEndian.Uint16(resp[payloadField+payloadLen-crcFieldLen : payloadField+payloadLen]) + if ch != cp { + err := errors.New("receiving from serial: signature of received message is not valid") + return nil, err + } + + return payload, nil +} + +// encode is internally used to create a valid provisioning packet +func encode(mType MsgType, msg []byte) []byte { + // Insert the preamble sequence followed by the message type + packet := append(msgStart[:], byte(mType)) + + // Append the packet length + bLen := make([]byte, payloadLenFieldLen) + binary.BigEndian.PutUint16(bLen, (uint16(len(msg) + crcFieldLen))) + packet = append(packet, bLen...) + + // Append the message payload + packet = append(packet, msg...) + + // Calculate and append the message signature + ch := crc16.Checksum(msg, crc16.CCITTTable) + checksum := make([]byte, crcFieldLen) + binary.BigEndian.PutUint16(checksum, ch) + packet = append(packet, checksum...) + + // Append final byte sequence + packet = append(packet, msgEnd[:]...) + return packet +} diff --git a/serial/protocol.go b/serial/protocol.go deleted file mode 100644 index 8e8888a5..00000000 --- a/serial/protocol.go +++ /dev/null @@ -1,37 +0,0 @@ -package serial - -var ( - msgStart = [2]byte{0x55, 0xAA} - msgEnd = [2]byte{0xAA, 0x55} -) - -type MsgType byte - -const ( - None MsgType = iota - Cmd - Data - Response -) - -type Command byte - -const ( - SketchInfo Command = iota + 1 - CSR - Locked - GetLocked - WriteCrypto - BeginStorage - SetDeviceID - SetYear - SetMonth - SetDay - SetHour - SetValidity - SetCertSerial - SetAuthKey - SetSignature - EndStorage - ReconstructCert -) diff --git a/serial/serial.go b/serial/serial.go deleted file mode 100644 index 42ff104d..00000000 --- a/serial/serial.go +++ /dev/null @@ -1,122 +0,0 @@ -package serial - -import ( - "bytes" - "encoding/binary" - "errors" - "fmt" - "time" - - "github.com/howeyc/crc16" - "go.bug.st/serial" -) - -type Serial struct { - port serial.Port -} - -func NewSerial() *Serial { - s := &Serial{} - return s -} - -func (s *Serial) Connect(address string) error { - mode := &serial.Mode{ - BaudRate: 57600, - } - port, err := serial.Open(address, mode) - if err != nil { - err = fmt.Errorf("%s: %w", "connecting to serial port", err) - return err - } - s.port = port - - s.port.SetReadTimeout(time.Millisecond * 2000) - return nil -} - -func (s *Serial) Send(cmd Command, payload []byte) error { - payload = append([]byte{byte(cmd)}, payload...) - msg := encode(Cmd, payload) - - _, err := s.port.Write(msg) - if err != nil { - err = fmt.Errorf("%s: %w", "sending message through serial", err) - return err - } - - return nil -} - -func (s *Serial) SendReceive(cmd Command, payload []byte) ([]byte, error) { - err := s.Send(cmd, payload) - if err != nil { - return nil, err - } - return s.Receive() -} - -func (s *Serial) Receive() ([]byte, error) { - buff := make([]byte, 1000) - var resp []byte - - received := 0 - packetLen := 0 - for received < packetLen+5+2 { - n, err := s.port.Read(buff) - if err != nil { - err = fmt.Errorf("%s: %w", "receiving from serial", err) - return nil, err - } - if n == 0 { - break - } - received += n - resp = append(resp, buff[:n]...) - - if packetLen == 0 && received >= 5 { - packetLen = int(binary.BigEndian.Uint16(resp[3:5])) - } - } - - if received == 0 { - err := errors.New("receiving from serial: timeout, nothing received") - return nil, err - } - - if !bytes.Equal(resp[received-2:], msgEnd[:]) { - err := errors.New("receiving from serial: end of message (0xAA, 0x55) not found") - return nil, err - } - - payload := resp[5 : packetLen+5-2] - ch := crc16.Checksum(payload, crc16.CCITTTable) - cp := binary.BigEndian.Uint16(resp[packetLen+5-2 : packetLen+5]) - if ch != cp { - err := errors.New("receiving from serial: signature of received message is not valid") - return nil, err - } - - return payload, nil -} - -func (s *Serial) Close() error { - return s.port.Close() -} - -func encode(mType MsgType, msg []byte) []byte { - packet := append(msgStart[:], byte(mType)) - - bLen := make([]byte, 2) - binary.BigEndian.PutUint16(bLen, (uint16(len(msg) + 2))) - packet = append(packet, bLen...) - - ch := crc16.Checksum(msg, crc16.CCITTTable) - checksum := make([]byte, 2) - binary.BigEndian.PutUint16(checksum, ch) - packet = append(packet, msg...) - packet = append(packet, checksum...) - - packet = append(packet, msgEnd[:]...) - return packet -}