-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmessage.go
160 lines (146 loc) · 4.93 KB
/
message.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
// Copyright 2017 Jacob Hesch
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package integra
// eISCP: Integra Serial Control Protocol over Ethernet
// https://www.google.com/search?q=eiscp
//
// eISCP protocol notes:
//
// - The data segment of a packet is fixed at 16 bytes (maxDataSize)
// while the size of the data in the segment is variable and
// determined by the byte at dataSizeIndex.
//
// - The first two bytes in the data segment of a packet are the start
// character '!' and the unit type character ('1' for receiver).
//
// - The end of a packet received from the Integra device is marked
// with 0x1a, while the end of a packet sent to the device is marked
// with 0x0a.
import (
"bytes"
"errors"
"fmt"
)
const (
headerSize byte = 16
maxDataSize byte = 16
packetSize byte = headerSize + maxDataSize
headerSizeIndex byte = 7
endOfPacketSize byte = 1
dataStartSize byte = 2 // data start: "!1"
dataOverhead byte = dataStartSize + endOfPacketSize
dataSizeIndex byte = 11
maxMessageSize byte = maxDataSize - dataOverhead
messageOffset byte = headerSize + dataStartSize
endOfPacketTx byte = 0x0a
endOfPacketRx byte = 0x1a
)
// eISCPPacket contains the bytes that make up a message sent to or
// received from an Integra device over Ethernet.
type eISCPPacket []byte
func newEISCPPacket() eISCPPacket {
return eISCPPacket{
0x49, 0x53, 0x43, 0x50, // I S C P
0x00, 0x00, 0x00, 0x10, // 16 - header size
0x00, 0x00, 0x00, 0x00, // 0 - data size
0x01, 0x00, 0x00, 0x00, // 1 - ISCP version
0x21, 0x31, 0x00, 0x00, // ! 1 - data start
0x00, 0x00, 0x00, 0x00, // }
0x00, 0x00, 0x00, 0x00, // } Empty message
0x00, 0x00, 0x00, 0x00, // }
}
}
// init initializes an outbound eISCPPacket that was created with
// newEISCPPacket with the given message.
func (p eISCPPacket) init(message string) error {
if len(message) > int(maxMessageSize) {
return fmt.Errorf("Message '%v' too long", message)
}
p[dataSizeIndex] = byte(len(message)) + dataOverhead
index := messageOffset
for i := range message {
p[index] = message[i]
index++
}
p[index] = endOfPacketTx
for i := index + 1; i < packetSize; i++ {
p[i] = 0x00
}
return nil
}
// message extracts the ISCP message from packet. The check method
// should be called to verify the packet's integrity before invoking
// message.
func (p eISCPPacket) message() (*Message, error) {
messageSize := p[dataSizeIndex] - dataOverhead
m, err := NewMessage(p[messageOffset : messageOffset+messageSize])
if err != nil {
return nil, err
}
return m, nil
}
// check performs an integrity check on the packet.
func (p eISCPPacket) check(endOfPacket byte) error {
switch {
case p[0] != 0x49 || p[1] != 0x53 || p[2] != 0x43 || p[3] != 0x50:
return errors.New("first 4 header bytes do not match ISCP")
case p[headerSize] != 0x21 || p[headerSize+1] != 0x31:
return errors.New("first 2 data bytes do not match !1")
case p[headerSizeIndex] != headerSize:
return fmt.Errorf("header size %#02x is not expected size %#02x",
p[headerSizeIndex], headerSize)
case p[dataSizeIndex] > maxDataSize:
return fmt.Errorf("data size %#02x greater than max size %#02x",
p[dataSizeIndex], maxDataSize)
case p[headerSize+p[dataSizeIndex]-1] != endOfPacket:
return fmt.Errorf("end of packet %#02x did not match expected value %#02x",
p[headerSize+p[dataSizeIndex]-1], endOfPacket)
}
return nil
}
// debugString returns a multi-line, hex-formated string of packet's
// bytes. Note that it can be printed in a single line using the fmt
// package's %q format verb.
func (p eISCPPacket) debugString() string {
buffer := bytes.Buffer{}
buffer.WriteString(fmt.Sprintf("\n"))
for i, b := range p {
buffer.WriteString(fmt.Sprintf("%#02x", b))
if i%4 == 3 {
buffer.WriteString(fmt.Sprintf("\n"))
} else {
buffer.WriteString(fmt.Sprint(" "))
}
}
return buffer.String()
}
// A Message is an ISCP message.
type Message struct {
Command string
Parameter string
}
// String returns the message as a string.
func (m *Message) String() string {
return m.Command + m.Parameter
}
// NewMessage returns a new Message from the given byte slice making
// up the message's command and parameter.
func NewMessage(m []byte) (*Message, error) {
if len(m) < 4 {
return nil, errors.New("message is too short")
}
// Command is always the first three bytes of
// message. Parameter is the remainder (variable length).
return &Message{string(m[:3]), string(m[3:])}, nil
}