Skip to content

Wrap serial #9

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand Down
53 changes: 53 additions & 0 deletions internal/serial/protocol.go
Original file line number Diff line number Diff line change
@@ -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
)
155 changes: 155 additions & 0 deletions internal/serial/serial.go
Original file line number Diff line number Diff line change
@@ -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
}