-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathpage.go
232 lines (212 loc) · 6.5 KB
/
page.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
package dexcom
import (
"bytes"
"errors"
"fmt"
)
// PageType specifies a record page type stored by the Dexcom G4 receiver.
type PageType byte
//go:generate stringer -type PageType
// Types of CGM records stored by the Dexcom G4 receiver.
const (
ManufacturingData PageType = 0
FirmwareData PageType = 1
SoftwareData PageType = 2
SensorData PageType = 3
EGVData PageType = 4
CalibrationData PageType = 5
DeviationData PageType = 6
InsertionTimeData PageType = 7
ReceiverLogData PageType = 8
ReceiverErrorData PageType = 9
MeterData PageType = 10
UserEventData PageType = 11
UserSettingData PageType = 12
FirstPageType = ManufacturingData
LastPageType = UserSettingData
InvalidPage PageType = 0xFF
)
// Lengths of fixed-size records (including 2-byte CRC),
// or 0 if veriable-length (terminated by null bytes).
var recordLength = map[PageType]int{
ManufacturingData: 0,
FirmwareData: 0,
SoftwareData: 0,
SensorData: 20,
EGVData: 13,
CalibrationData: 249, // except for non-Share receivers, see below
InsertionTimeData: 15,
MeterData: 16,
}
// ReadPageRange returns the starting and ending page for a given PageType.
// The page numbers can be -1 if there are no entries (for example, USER_EVENT_DATA).
func (cgm *CGM) ReadPageRange(pageType PageType) (int, int) {
v := cgm.Cmd(ReadDatabasePageRange, byte(pageType))
if cgm.Error() != nil {
return -1, -1
}
return int(unmarshalInt32(v[:4])), int(unmarshalInt32(v[4:]))
}
// CRCError indicates that a CRC error was detected.
type CRCError struct {
Kind string
Received, Computed uint16
PageType PageType
PageNumber int
Data []byte
}
func (e CRCError) Error() string {
if e.PageType == InvalidPage {
return fmt.Sprintf("bad %s CRC (received %02X, computed %02X); data = % X", e.Kind, e.Received, e.Computed, e.Data)
}
return fmt.Sprintf("bad %s CRC (received %02X, computed %02X) for %v page %d; data = % X", e.Kind, e.Received, e.Computed, e.PageType, e.PageNumber, e.Data)
}
// ReadPage reads the specified page.
func (cgm *CGM) ReadPage(pageType PageType, pageNumber int) []byte {
buf := bytes.Buffer{}
buf.WriteByte(byte(pageType))
buf.Write(marshalInt32(int32(pageNumber)))
buf.WriteByte(1)
return cgm.Cmd(ReadDatabasePages, buf.Bytes()...)
}
// PageInfo represents a page of raw records.
type PageInfo struct {
Type PageType
Number int
Records [][]byte
}
// ReadRawRecords reads the specified page and returns its records as raw byte slices.
func (cgm *CGM) ReadRawRecords(pageType PageType, pageNumber int) [][]byte {
v := cgm.ReadPage(pageType, pageNumber)
if cgm.Error() != nil {
return nil
}
page, err := UnmarshalPage(v)
if err != nil {
if page == nil {
cgm.SetError(err)
return nil
}
err = fmt.Errorf("%v page %d: %v", page.Type, page.Number, err)
} else if page.Type != pageType {
err = fmt.Errorf("%v page %d: unexpected page type (%d)", pageType, pageNumber, page.Type)
} else if page.Number != pageNumber {
err = fmt.Errorf("%v page %d: unexpected page number (%d)", pageType, pageNumber, page.Number)
}
cgm.SetError(err)
return page.Records
}
// ReadRecords reads the specified page and returns its records.
func (cgm *CGM) ReadRecords(pageType PageType, pageNumber int) Records {
data := cgm.ReadRawRecords(pageType, pageNumber)
if cgm.Error() != nil {
return nil
}
records, err := UnmarshalRecords(pageType, data)
if err != nil {
cgm.SetError(fmt.Errorf("%v page %d: %v", pageType, pageNumber, err))
}
return records
}
const (
headerSize = 28
oldCalRecordRev = 2
oldCalRecordSize = 148
)
// UnmarshalPage validates the CRC of the given page data and
// uses the page type to slice the data into raw records.
func UnmarshalPage(v []byte) (*PageInfo, error) {
if len(v) < headerSize {
return nil, fmt.Errorf("invalid page length (%d)", len(v))
}
h := v[:headerSize]
v = v[headerSize:]
crc := unmarshalUint16(h[headerSize-2:])
calc := crc16(h[:headerSize-2])
if crc != calc {
return nil, CRCError{
Kind: "page",
Received: crc,
Computed: calc,
Data: h,
}
}
// firstIndex := int(unmarshalInt32(h[0:4]))
numRecords := int(unmarshalInt32(h[4:8]))
pageType := PageType(h[8])
rev := h[9]
pageNumber := int(unmarshalInt32(h[10:14]))
// r1 := unmarshalInt32(h[14:18])
// r2 := unmarshalInt32(h[18:22])
// r3 := unmarshalInt32(h[22:26])
page := PageInfo{Type: pageType, Number: pageNumber}
recordLen := recordLength[pageType]
if pageType == CalibrationData && rev <= oldCalRecordRev {
recordLen = oldCalRecordSize
}
if recordLen == 0 {
if numRecords != 1 {
return &page, fmt.Errorf("unexpected number of records (%d)", numRecords)
}
recordLen = len(v)
}
page.Records = make([][]byte, 0, numRecords)
// Collect records in reverse chronological order.
for i := numRecords - 1; i >= 0; i-- {
rec := v[i*recordLen : (i+1)*recordLen]
crc := unmarshalUint16(rec[recordLen-2 : recordLen])
rec = rec[:recordLen-2]
calc := crc16(rec)
if crc != calc {
return &page, CRCError{
Kind: "record",
Received: crc,
Computed: calc,
PageType: pageType,
PageNumber: pageNumber,
Data: rec,
}
}
page.Records = append(page.Records, rec)
}
return &page, nil
}
// UnmarshalRecords unmarshals raw records into records of the appropriate type.
func UnmarshalRecords(pageType PageType, data [][]byte) (Records, error) {
records := make(Records, 0, len(data))
var err error
for _, rec := range data {
r := Record{}
err = r.unmarshal(pageType, rec)
if err != nil {
break
}
records = append(records, r)
}
return records, err
}
// RecordFunc represents a function that IterRecords applies to each record.
type RecordFunc func(Record) error
// IterationDone can be returned by a RecordFunc to indicate that
// iteration is complete and no further records need to be processed.
var IterationDone = errors.New("iteration done") // nolint
// IterRecords reads the specified page range and applies recordFn to each
// record in each page. Pages are visited in reverse order to facilitate
// scanning for recent records.
func (cgm *CGM) IterRecords(pageType PageType, firstPage, lastPage int, recordFn RecordFunc) {
for n := lastPage; n >= firstPage; n-- {
records := cgm.ReadRecords(pageType, n)
if cgm.Error() != nil {
return
}
for _, r := range records {
err := recordFn(r)
if err != nil {
if err != IterationDone {
cgm.SetError(fmt.Errorf("%v page %d: %v", pageType, n, err))
}
return
}
}
}
}