Skip to content

Commit 6ed7371

Browse files
Paolo Calaopolldo
Paolo Calao
authored andcommitted
Wrap serial (#9)
Introduce a wrapper of serial (https://github.com/bugst/go-serial) for sending commands to arduino devices connected to the PC through the serial port. Commands and responses follow the protocol defined by the provisioning firmware (https://github.com/ubidefeo/Arduino-CLI-provisioning/tree/main/ArduinoIoTCloud-CryptoConfig). * Wrap serial * Refactor serial wrapper as internal package - Move serial into internal package - Add comments - Do not export 'retrieve' function - Transform magic numbers into readable constants
1 parent 24bf810 commit 6ed7371

File tree

4 files changed

+216
-0
lines changed

4 files changed

+216
-0
lines changed

go.mod

+2
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ require (
77
github.com/arduino/iot-client-go v1.3.3
88
github.com/bcmi-labs/oniudra-cli v0.15.8
99
github.com/eclipse/paho.mqtt.golang v1.3.2
10+
github.com/howeyc/crc16 v0.0.0-20171223171357-2b2a61e366a6
1011
github.com/juju/errors v0.0.0-20200330140219-3fe23663418f
1112
github.com/sirupsen/logrus v1.8.1
1213
github.com/spf13/cobra v1.1.3
14+
go.bug.st/serial v1.3.0
1315
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125 // indirect
1416
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914
1517
golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6 // indirect

go.sum

+6
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7
138138
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
139139
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
140140
github.com/creack/goselect v0.1.1/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY=
141+
github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0=
142+
github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY=
141143
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
142144
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
143145
github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E=
@@ -297,6 +299,8 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
297299
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
298300
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
299301
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
302+
github.com/howeyc/crc16 v0.0.0-20171223171357-2b2a61e366a6 h1:IIVxLyDUYErC950b8kecjoqDet8P5S4lcVRUOM6rdkU=
303+
github.com/howeyc/crc16 v0.0.0-20171223171357-2b2a61e366a6/go.mod h1:JslaLRrzGsOKJgFEPBP65Whn+rdwDQSk0I0MCRFe2Zw=
300304
github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs=
301305
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
302306
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
@@ -503,6 +507,8 @@ go.bug.st/cleanup v1.0.0/go.mod h1:EqVmTg2IBk4znLbPD28xne3abjsJftMdqqJEjhn70bk=
503507
go.bug.st/downloader/v2 v2.1.1/go.mod h1:VZW2V1iGKV8rJL2ZEGIDzzBeKowYv34AedJz13RzVII=
504508
go.bug.st/relaxed-semver v0.0.0-20190922224835-391e10178d18/go.mod h1:Cx1VqMtEhE9pIkEyUj3LVVVPkv89dgW8aCKrRPDR/uE=
505509
go.bug.st/serial v1.1.2/go.mod h1:VmYBeyJWp5BnJ0tw2NUJHZdJTGl2ecBGABHlzRK1knY=
510+
go.bug.st/serial v1.3.0 h1:liPN6f/Xk0qaUByg0H2LOSns+2RuAuNXmXZyQOLVwVE=
511+
go.bug.st/serial v1.3.0/go.mod h1:8TT7u/SwwNIpJ8QaG4s+HTjFt9ReXs2cdOU7ZEk50Dk=
506512
go.bug.st/serial.v1 v0.0.0-20180827123349-5f7892a7bb45/go.mod h1:dRSl/CVCTf56CkXgJMDOdSwNfo2g1orOGE/gBGdvjZw=
507513
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
508514
go.mozilla.org/gopgagent v0.0.0-20170926210634-4d7ea76ff71a/go.mod h1:YDKUvO0b//78PaaEro6CAPH6NqohCmL2Cwju5XI2HoE=

internal/serial/protocol.go

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package serial
2+
3+
var (
4+
// msgStart is the initial byte sequence of every packet
5+
msgStart = [2]byte{0x55, 0xAA}
6+
// msgEnd is the final byte sequence of every packet
7+
msgEnd = [2]byte{0xAA, 0x55}
8+
)
9+
10+
const (
11+
// Position of payload field
12+
payloadField = 5
13+
// Position of payload length field
14+
payloadLenField = 3
15+
// Length of payload length field
16+
payloadLenFieldLen = 2
17+
// Length of the signature field
18+
crcFieldLen = 2
19+
)
20+
21+
// MsgType indicates the type of the packet
22+
type MsgType byte
23+
24+
const (
25+
None MsgType = iota
26+
Cmd
27+
Data
28+
Response
29+
)
30+
31+
// Command indicates the command that should be
32+
// executed on the board to be provisioned.
33+
type Command byte
34+
35+
const (
36+
SketchInfo Command = iota + 1
37+
CSR
38+
Locked
39+
GetLocked
40+
WriteCrypto
41+
BeginStorage
42+
SetDeviceID
43+
SetYear
44+
SetMonth
45+
SetDay
46+
SetHour
47+
SetValidity
48+
SetCertSerial
49+
SetAuthKey
50+
SetSignature
51+
EndStorage
52+
ReconstructCert
53+
)

internal/serial/serial.go

+155
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package serial
2+
3+
import (
4+
"bytes"
5+
"encoding/binary"
6+
"errors"
7+
"fmt"
8+
"time"
9+
10+
"github.com/howeyc/crc16"
11+
"go.bug.st/serial"
12+
)
13+
14+
// Serial is a wrapper of serial port interface that
15+
// features specific functions to send provisioning
16+
// commands through the serial port to an arduino device.
17+
type Serial struct {
18+
port serial.Port
19+
}
20+
21+
// NewSerial instantiate and returns a Serial instance.
22+
// The Serial Connect method should be called before using
23+
// its send/receive functions.
24+
func NewSerial() *Serial {
25+
s := &Serial{}
26+
return s
27+
}
28+
29+
// Connect tries to connect Serial to a specific serial port.
30+
func (s *Serial) Connect(address string) error {
31+
mode := &serial.Mode{
32+
BaudRate: 57600,
33+
}
34+
port, err := serial.Open(address, mode)
35+
if err != nil {
36+
err = fmt.Errorf("%s: %w", "connecting to serial port", err)
37+
return err
38+
}
39+
s.port = port
40+
41+
s.port.SetReadTimeout(time.Millisecond * 2000)
42+
return nil
43+
}
44+
45+
// Send allows to send a provisioning command to a connected arduino device.
46+
func (s *Serial) Send(cmd Command, payload []byte) error {
47+
payload = append([]byte{byte(cmd)}, payload...)
48+
msg := encode(Cmd, payload)
49+
50+
_, err := s.port.Write(msg)
51+
if err != nil {
52+
err = fmt.Errorf("%s: %w", "sending message through serial", err)
53+
return err
54+
}
55+
56+
return nil
57+
}
58+
59+
// SendReceive allows to send a provisioning command to a connected arduino device.
60+
// Then, it waits for a response from the device and, if any, returns it.
61+
// If no response is received after 2 seconds, an error is returned.
62+
func (s *Serial) SendReceive(cmd Command, payload []byte) ([]byte, error) {
63+
err := s.Send(cmd, payload)
64+
if err != nil {
65+
return nil, err
66+
}
67+
return s.receive()
68+
}
69+
70+
// Close should be used when the Serial connection isn't used anymore.
71+
// After that, Serial could Connect again to any port.
72+
func (s *Serial) Close() error {
73+
return s.port.Close()
74+
}
75+
76+
// receive allows to wait for a response from an arduino device under provisioning.
77+
// Its timeout is set to 2 seconds. It returns an error if the response is not valid
78+
// or if the timeout expires.
79+
// TODO: consider refactoring using a more explicit procedure:
80+
// start := s.Read(buff, MsgStartLength)
81+
// payloadLen := s.Read(buff, payloadFieldLen)
82+
func (s *Serial) receive() ([]byte, error) {
83+
buff := make([]byte, 1000)
84+
var resp []byte
85+
86+
received := 0
87+
payloadLen := 0
88+
// Wait to receive the entire packet that is long as the preamble (from msgStart to payload length field)
89+
// plus the actual payload length plus the length of the ending sequence.
90+
for received < (payloadLenField+payloadLenFieldLen)+payloadLen+len(msgEnd) {
91+
n, err := s.port.Read(buff)
92+
if err != nil {
93+
err = fmt.Errorf("%s: %w", "receiving from serial", err)
94+
return nil, err
95+
}
96+
if n == 0 {
97+
break
98+
}
99+
received += n
100+
resp = append(resp, buff[:n]...)
101+
102+
// Update the payload length as soon as it is received.
103+
if payloadLen == 0 && received >= (payloadLenField+payloadLenFieldLen) {
104+
payloadLen = int(binary.BigEndian.Uint16(resp[payloadLenField:(payloadLenField + payloadLenFieldLen)]))
105+
// TODO: return error if payloadLen is too large.
106+
}
107+
}
108+
109+
if received == 0 {
110+
err := errors.New("receiving from serial: timeout, nothing received")
111+
return nil, err
112+
}
113+
114+
// TODO: check if msgStart is present
115+
116+
if !bytes.Equal(resp[received-len(msgEnd):], msgEnd[:]) {
117+
err := errors.New("receiving from serial: end of message (0xAA, 0x55) not found")
118+
return nil, err
119+
}
120+
121+
payload := resp[payloadField : payloadField+payloadLen-crcFieldLen]
122+
ch := crc16.Checksum(payload, crc16.CCITTTable)
123+
// crc is contained in the last bytes of the payload
124+
cp := binary.BigEndian.Uint16(resp[payloadField+payloadLen-crcFieldLen : payloadField+payloadLen])
125+
if ch != cp {
126+
err := errors.New("receiving from serial: signature of received message is not valid")
127+
return nil, err
128+
}
129+
130+
return payload, nil
131+
}
132+
133+
// encode is internally used to create a valid provisioning packet
134+
func encode(mType MsgType, msg []byte) []byte {
135+
// Insert the preamble sequence followed by the message type
136+
packet := append(msgStart[:], byte(mType))
137+
138+
// Append the packet length
139+
bLen := make([]byte, payloadLenFieldLen)
140+
binary.BigEndian.PutUint16(bLen, (uint16(len(msg) + crcFieldLen)))
141+
packet = append(packet, bLen...)
142+
143+
// Append the message payload
144+
packet = append(packet, msg...)
145+
146+
// Calculate and append the message signature
147+
ch := crc16.Checksum(msg, crc16.CCITTTable)
148+
checksum := make([]byte, crcFieldLen)
149+
binary.BigEndian.PutUint16(checksum, ch)
150+
packet = append(packet, checksum...)
151+
152+
// Append final byte sequence
153+
packet = append(packet, msgEnd[:]...)
154+
return packet
155+
}

0 commit comments

Comments
 (0)