-
Notifications
You must be signed in to change notification settings - Fork 2
/
sbd.go
328 lines (291 loc) · 8.31 KB
/
sbd.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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
package sbd
import (
"bytes"
"encoding/binary"
"encoding/json"
"fmt"
"io"
"time"
)
type Orientation byte
type SessionStatus byte
type ElementID byte
const (
moHeaderID = ElementID(0x01)
moPayloadID = ElementID(0x02)
moLocationInformationID = ElementID(0x03)
moConfirmationID = ElementID(0x05)
mtHeaderID = ElementID(0x41)
mtPayloadID = ElementID(0x42)
mtConfirmationMsg = ElementID(0x44)
mtMessagePriority = ElementID(0x46)
// SBD Session Status
StCompleted = SessionStatus(0)
StMTTooLarge = SessionStatus(1)
StLocationUnacceptable = SessionStatus(2)
StTimeout = SessionStatus(10)
StIMEITooLarge = SessionStatus(12)
StRFLinkLoss = SessionStatus(13)
StIMEIProtocolAnomaly = SessionStatus(14)
StIMEIProhibitedGateway = SessionStatus(15)
NE = Orientation(0)
NW = Orientation(1)
SE = Orientation(2)
SW = Orientation(3)
protocolRevision = 1
)
func (o Orientation) LatLng(lat, lng float64) (float64, float64) {
switch o {
case NW:
return lat, -1 * lng
case SW:
return -1 * lat, -1 * lng
case SE:
return -1 * lat, lng
}
// this is NE
return lat, lng
}
func (eid ElementID) TargetType() interface{} {
switch eid {
case moPayloadID:
return &MOPayload{}
case moLocationInformationID:
return &MOLocationInformation{}
case moConfirmationID:
return &MOConfirmationMessage{}
case moHeaderID:
fallthrough
default:
return &MODirectIPHeader{}
}
}
// A MessageHeader defines the revision and the whole message length.
type MessageHeader struct {
ProtocolRevision byte
MessageLength uint16
}
// An Header is sent before every information element and
// specifies the ID (aka type) and length of the element.
type Header struct {
ID ElementID `json:"id"`
ElementLength uint16 `json:"elementlength"`
}
// An InformationElement contains a header and the data which can have
// different types. You have to inspect the header's ID field to
// get the type.
type InformationElement struct {
Header `json:"header"`
Data interface{} `json:"data"`
}
func (u *InformationElement) UnmarshalJSON(data []byte) error {
// a little bit inperformant, because we parse the header twice
// but hey .... who cares?
h := &struct {
Header `json:"header"`
}{
Header: u.Header,
}
if err := json.Unmarshal(data, &h); err != nil {
return err
}
buf := h.ID.TargetType()
ie := &struct {
Header `json:"header"`
Data interface{} `json:"data"`
}{
Header: u.Header,
Data: buf,
}
if err := json.Unmarshal(data, &ie); err != nil {
return err
}
u.Header = ie.Header
u.Data = ie.Data
return nil
}
// InformationElements is a wrapper type for the InformationElement's
// which are in a bundled bucket
type InformationBucket struct {
Header *MODirectIPHeader `json:"header"`
Payload []byte `json:"payload"`
Location *MOLocationInformation `json:"location"`
Position *Location `json:"position"`
}
// The MODirectIPHeader contains some information about the message
// itself.
type MODirectIPHeader struct {
CDRReference uint32 `json:"cdrreference"`
IMEI [15]byte `json:"imei"`
SessionStatus SessionStatus `json:"sessionstatus"`
MOMSN uint16 `json:"momsn"`
MTMSN uint16 `json:"mtmsn"`
TimeOfSession uint32 `json:"timeofsession"`
}
// GetTime returns the time which is specified by the TimeOfSession field
func (dih *MODirectIPHeader) GetTime() time.Time {
return time.Unix(int64(dih.TimeOfSession), 0)
}
// GetIMEI returns the imei as a string
func (dih *MODirectIPHeader) GetIMEI() string {
return string(dih.IMEI[:])
}
func (u *MODirectIPHeader) UnmarshalJSON(data []byte) error {
type Alias MODirectIPHeader
dat := &struct {
IMEI string `json:"imei"`
*Alias
}{
Alias: (*Alias)(u),
}
if err := json.Unmarshal(data, &dat); err != nil {
return err
}
copy(u.IMEI[:], dat.IMEI)
return nil
}
func (u *MODirectIPHeader) MarshalJSON() ([]byte, error) {
type Alias MODirectIPHeader
return json.Marshal(&struct {
IMEI string `json:"imei"`
*Alias
}{
Alias: (*Alias)(u),
IMEI: string(u.IMEI[:]),
})
}
// MOPayload is a wrapper around some blob data.
type MOPayload struct {
Payload []byte `json:"payload"`
}
// A MOConfirmationMessage contains the confirmation status.
type MOConfirmationMessage struct {
Status byte `json:"status"`
}
// Success checks if the confirmation was successfull.
func (ch *MOConfirmationMessage) Success() bool {
return ch.Status == 1
}
// MOLocationInformation contains location information and the
// cep radius in km.
type MOLocationInformation struct {
Position LocationData `json:"position"`
CEPRadius uint32 `json:"cepradius"`
}
// LocationData contains an orientation as well as the latitude and
// longitude in degree's and minutes.
type LocationData struct {
OrientationCode Orientation `json:"orientationcode"`
LatDegree byte `json:"latdegree"`
LatMinute uint16 `json:"latminute"`
LngDegree byte `json:"lngdegree"`
LngMinute uint16 `json:"lngminute"`
}
type Location struct {
Latitude float64
Longitude float64
}
// GetLatLng converts the location information to latitude/longitude
// values which can be used by other systems. The orientiation is
// used to convert the values to positive or negative vals.
func (loc *MOLocationInformation) GetLatLng() (float64, float64) {
la := float64(loc.Position.LatDegree) + float64(loc.Position.LatMinute)/1000.0/60.0
ln := float64(loc.Position.LngDegree) + float64(loc.Position.LngMinute)/1000.0/60.0
return loc.Position.OrientationCode.LatLng(la, ln)
}
// GetCEPRadius simply returns the radius as an int value
func (loc *MOLocationInformation) GetCEPRadius() int {
return int(loc.CEPRadius)
}
func parseMessageHeader(in io.Reader) (*MessageHeader, error) {
var res MessageHeader
if err := binary.Read(in, binary.BigEndian, &res); err != nil {
if err == io.EOF {
return nil, err
}
return nil, fmt.Errorf("cannot read message header: %v", err)
}
return &res, nil
}
func parseInformationElement(in io.Reader) (*InformationElement, error) {
var h Header
if err := binary.Read(in, binary.BigEndian, &h); err != nil {
if err == io.EOF {
return nil, err
}
return nil, fmt.Errorf("cannot read informationelement header: %v", err)
}
el, err := parseElementByType(&h, in)
if err != nil {
return nil, err
}
return &InformationElement{Header: h, Data: el}, nil
}
// GetElements parses the given stream and returns an array of found
// elements.
func GetElements(in io.Reader) (*InformationBucket, error) {
mh, err := parseMessageHeader(in)
if err != nil {
return nil, err
}
if mh.ProtocolRevision != protocolRevision {
return nil, fmt.Errorf("wrong protocol version: %d", mh.ProtocolRevision)
}
bbuf := make([]byte, mh.MessageLength)
_, err = io.ReadFull(in, bbuf)
if err != nil {
return nil, fmt.Errorf("cannot read bytes from message: %v", err)
}
buffer := bytes.NewBuffer(bbuf)
buck := new(InformationBucket)
for {
ie, err := parseInformationElement(buffer)
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
switch ie.ID {
case moPayloadID:
buck.Payload = ie.Data.(*MOPayload).Payload
case moHeaderID:
buck.Header = ie.Data.(*MODirectIPHeader)
case moLocationInformationID:
buck.Location = ie.Data.(*MOLocationInformation)
lat, lng := buck.Location.GetLatLng()
buck.Position = &Location{Latitude: lat, Longitude: lng}
}
}
return buck, nil
}
func parseElementByType(h *Header, in io.Reader) (interface{}, error) {
buf := h.ID.TargetType()
// we cannot read the payload struct with binary.Read because it has
// a byte-slice as field. so we must do it the ugly way here.
if h.ID == moPayloadID {
buf = make([]byte, h.ElementLength)
}
if err := binary.Read(in, binary.BigEndian, buf); err != nil {
if err == io.EOF {
return nil, err
}
return nil, fmt.Errorf("cannot read informationelement content: %v", err)
}
if h.ID == moPayloadID {
return &MOPayload{Payload: buf.([]byte)}, nil
}
return buf, nil
}
// NewPayload returns an element which contains the given bytes as payload.
func NewPayload(b []byte) *InformationElement {
return &InformationElement{
Header: Header{
ID: moPayloadID,
ElementLength: uint16(len(b)),
},
Data: &MOPayload{
Payload: b,
},
}
}