Skip to content

Commit 5d67341

Browse files
KristianKarlwader
authored andcommitted
WIP
1 parent a7d54ff commit 5d67341

File tree

4 files changed

+333
-0
lines changed

4 files changed

+333
-0
lines changed

format/all/all.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
_ "github.com/wader/fq/format/dns"
2222
_ "github.com/wader/fq/format/elf"
2323
_ "github.com/wader/fq/format/fairplay"
24+
_ "github.com/wader/fq/format/fit"
2425
_ "github.com/wader/fq/format/flac"
2526
_ "github.com/wader/fq/format/gif"
2627
_ "github.com/wader/fq/format/gzip"

format/fit/fit.go

Lines changed: 331 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,331 @@
1+
package fit
2+
3+
// TODO: chained files
4+
// TODO: developer message?
5+
// TODO: filed number mapping, xls file?
6+
// TODO: Compressed Timestamp Header
7+
8+
// https://developer.garmin.com/fit/protocol/
9+
import (
10+
"github.com/wader/fq/format"
11+
"github.com/wader/fq/pkg/decode"
12+
"github.com/wader/fq/pkg/interp"
13+
"github.com/wader/fq/pkg/scalar"
14+
)
15+
16+
func init() {
17+
interp.RegisterFormat(
18+
format.FIT,
19+
&decode.Format{
20+
Description: "Flexible and Interoperable Data Transfer Protocol",
21+
Groups: []*decode.Group{format.Probe},
22+
DecodeFn: decodeFIT,
23+
})
24+
}
25+
26+
const (
27+
architectureTypeLittleEndian = 0
28+
architectureTypeBigEndian = 1
29+
)
30+
31+
var architectureTypeMap = scalar.UintMapSymStr{
32+
architectureTypeLittleEndian: "little_endian",
33+
architectureTypeBigEndian: "big_endian",
34+
}
35+
36+
const (
37+
messageTypeData = 0
38+
messageTypeDefinition = 1
39+
)
40+
41+
var messageTypeMap = scalar.UintMapSymStr{
42+
messageTypeData: "data",
43+
messageTypeDefinition: "definition",
44+
}
45+
46+
// Base Type # Endian Ability Base Type Field Type Name Invalid Value Size (Bytes) Comment
47+
// 0 0 0x00 enum 0xFF 1
48+
// 1 0 0x01 sint8 0x7F 1 2’s complement format
49+
// 2 0 0x02 uint8 0xFF 1
50+
// 3 1 0x83 sint16 0x7FFF 2 2’s complement format
51+
// 4 1 0x84 uint16 0xFFFF 2
52+
// 5 1 0x85 sint32 0x7FFFFFFF 4 2’s complement format
53+
// 6 1 0x86 uint32 0xFFFFFFFF 4
54+
// 7 0 0x07 string 0x00 1 Null terminated string encoded in UTF-8 format
55+
// 8 1 0x88 float32 0xFFFFFFFF 4
56+
// 9 1 0x89 float64 0xFFFFFFFFFFFFFFFF 8
57+
// 10 0 0x0A uint8z 0x00 1
58+
// 11 1 0x8B uint16z 0x0000 2
59+
// 12 1 0x8C uint32z 0x00000000 4
60+
// 13 0 0x0D byte 0xFF 1 Array of bytes. Field is invalid if all bytes are invalid.
61+
// 14 1 0x8E sint64 0x7FFFFFFFFFFFFFFF 8 2’s complement format
62+
// 15 1 0x8F uint64 0xFFFFFFFFFFFFFFFF 8
63+
// 16 1 0x90 uint64z 0x0000000000000000 8
64+
65+
const (
66+
baseTypeEnum = 0
67+
baseTypeSint8 = 1
68+
baseTypeUint8 = 2
69+
baseTypeSint16 = 3
70+
baseTypeUint16 = 4
71+
baseTypeSint32 = 5
72+
baseTypeUint32 = 6
73+
baseTypeString = 7
74+
baseTypeFloat32 = 8
75+
baseTypeFloat64 = 9
76+
baseTypeUint8z = 10
77+
baseTypeUint16z = 11
78+
baseTypeUint32z = 12
79+
baseTypeByte = 13
80+
baseTypeSint64 = 14
81+
baseTypeUint64 = 15
82+
baseTypeUint64z = 16
83+
)
84+
85+
var baseTypeMap = scalar.UintMapSymStr{
86+
baseTypeEnum: "enum",
87+
baseTypeSint8: "sint8",
88+
baseTypeUint8: "uint8",
89+
baseTypeSint16: "sint16",
90+
baseTypeUint16: "uint16",
91+
baseTypeSint32: "sint32",
92+
baseTypeUint32: "uint32",
93+
baseTypeString: "string",
94+
baseTypeFloat32: "float32",
95+
baseTypeFloat64: "float64",
96+
baseTypeUint8z: "uint8z",
97+
baseTypeUint16z: "uint16z",
98+
baseTypeUint32z: "uint32z",
99+
baseTypeByte: "byte",
100+
baseTypeSint64: "sint64",
101+
baseTypeUint64: "uint64",
102+
baseTypeUint64z: "uint64z",
103+
}
104+
105+
var baseTypeSize = map[int]int{
106+
baseTypeEnum: 1,
107+
baseTypeSint8: 1,
108+
baseTypeUint8: 1,
109+
baseTypeSint16: 2,
110+
baseTypeUint16: 2,
111+
baseTypeSint32: 4,
112+
baseTypeUint32: 4,
113+
baseTypeString: 1,
114+
baseTypeFloat32: 4,
115+
baseTypeFloat64: 8,
116+
baseTypeUint8z: 1,
117+
baseTypeUint16z: 2,
118+
baseTypeUint32z: 4,
119+
baseTypeByte: 1,
120+
baseTypeSint64: 8,
121+
baseTypeUint64: 8,
122+
baseTypeUint64z: 8,
123+
}
124+
125+
type field struct {
126+
number uint8
127+
size uint8
128+
endianAbility uint8
129+
baseType uint8
130+
}
131+
132+
type developerField struct {
133+
number uint8
134+
size uint8
135+
developerIndex uint8
136+
}
137+
138+
type definition struct {
139+
s scalar.Uint
140+
architecture int
141+
globalMessageNumber int
142+
fields []field
143+
developerFields []developerField
144+
}
145+
146+
type definitionEntries map[uint64]definition
147+
148+
func (fes definitionEntries) MapUint(s scalar.Uint) (scalar.Uint, error) {
149+
u := s.Actual
150+
if fe, ok := fes[u]; ok {
151+
s = fe.s
152+
s.Actual = u
153+
}
154+
return s, nil
155+
}
156+
157+
func decodeBaseType(d *decode.D, f field) {
158+
switch f.baseType {
159+
case baseTypeEnum:
160+
d.FieldU8("value")
161+
case baseTypeSint8:
162+
d.FieldS8("value")
163+
case baseTypeUint8:
164+
d.FieldU8("value")
165+
case baseTypeSint16:
166+
d.FieldS16("value")
167+
case baseTypeUint16:
168+
d.FieldU16("value")
169+
case baseTypeSint32:
170+
d.FieldU32("value")
171+
case baseTypeUint32:
172+
d.FieldU32("value")
173+
case baseTypeString:
174+
d.FieldUTF8NullFixedLen("value", int(f.size))
175+
case baseTypeFloat32:
176+
d.FieldF32("value")
177+
case baseTypeFloat64:
178+
d.FieldF64("value")
179+
case baseTypeUint8z:
180+
d.FieldU8("value")
181+
case baseTypeUint16z:
182+
d.FieldU16("value")
183+
case baseTypeUint32z:
184+
d.FieldU32("value")
185+
case baseTypeByte:
186+
d.FieldRawLen("value", int64(f.size)*8)
187+
case baseTypeSint64:
188+
d.FieldS64("value")
189+
case baseTypeUint64:
190+
d.FieldU64("value")
191+
case baseTypeUint64z:
192+
d.FieldU64("value")
193+
default:
194+
d.Fatalf("unknown base type %d", f.baseType)
195+
}
196+
}
197+
198+
func decodeDataMessage(d *decode.D, de definition) {
199+
d.FieldArray("fields", func(d *decode.D) {
200+
for _, f := range de.fields {
201+
baseSize, ok := baseTypeSize[int(f.baseType)]
202+
if !ok {
203+
d.Fatalf("unknown base size for base type %d", f.baseType)
204+
}
205+
values := int(f.size) / baseSize
206+
207+
switch {
208+
case values == 1,
209+
f.baseType == baseTypeString,
210+
f.baseType == baseTypeByte:
211+
decodeBaseType(d, f)
212+
default:
213+
d.FieldArray("values", func(d *decode.D) {
214+
for i := 0; i < values; i++ {
215+
decodeBaseType(d, f)
216+
}
217+
})
218+
}
219+
}
220+
})
221+
if len(de.developerFields) > 0 {
222+
d.FieldArray("developer_fields", func(d *decode.D) {
223+
for _, f := range de.developerFields {
224+
d.FieldRawLen("filed", int64(f.size)*8)
225+
}
226+
})
227+
}
228+
}
229+
230+
func decodeDefinitionMessage(d *decode.D, messageTypeSpecific uint64) definition {
231+
var de definition
232+
d.FieldU8("reserved")
233+
de.architecture = int(d.FieldU8("architecture", architectureTypeMap))
234+
de.globalMessageNumber = int(d.FieldU16("global_message_number"))
235+
numFields := d.FieldU8("fields")
236+
d.FieldArray("field_definitions", func(d *decode.D) {
237+
for i := uint64(0); i < numFields; i++ {
238+
d.FieldStruct("field_definition", func(d *decode.D) {
239+
var f field
240+
f.number = uint8(d.FieldU8("field_definition_number"))
241+
f.size = uint8(d.FieldU8("size"))
242+
f.endianAbility = uint8(d.FieldU1("endian_ability"))
243+
d.FieldRawLen("reserved", 2)
244+
f.baseType = uint8(d.FieldU5("base_type_number", baseTypeMap))
245+
246+
de.fields = append(de.fields, f)
247+
})
248+
}
249+
})
250+
if messageTypeSpecific == 1 {
251+
developerFields := d.FieldU8("developer_fields")
252+
d.FieldArray("developer_field_definitions", func(d *decode.D) {
253+
for i := uint64(0); i < developerFields; i++ {
254+
d.FieldStruct("developer_field_definition", func(d *decode.D) {
255+
var f developerField
256+
f.number = uint8(d.FieldU8("field_number"))
257+
f.size = uint8(d.FieldU8("size"))
258+
f.developerIndex = uint8(d.FieldU8("developer_data_index"))
259+
260+
de.developerFields = append(de.developerFields, f)
261+
})
262+
}
263+
})
264+
}
265+
266+
return de
267+
}
268+
269+
func decodeFIT(d *decode.D) any {
270+
d.Endian = decode.LittleEndian
271+
272+
definitions := definitionEntries{
273+
0: definition{
274+
s: scalar.Uint{Sym: "file_id"},
275+
fields: []field{
276+
{number: 0, size: 1, baseType: baseTypeEnum},
277+
{number: 1, size: 2, baseType: baseTypeUint16},
278+
{number: 2, size: 2, baseType: baseTypeUint16},
279+
{number: 3, size: 4, baseType: baseTypeUint32z},
280+
{number: 4, size: 4, baseType: baseTypeUint32},
281+
{number: 5, size: 2, baseType: baseTypeUint16},
282+
// {number: 5, baseType: baseTypeString},
283+
284+
},
285+
},
286+
}
287+
var dataSize uint64
288+
289+
d.FieldStruct("header", func(d *decode.D) {
290+
size := d.FieldU8("size")
291+
if size < 12 {
292+
d.Fatalf("Header size too small %d < 12", size)
293+
}
294+
d.FieldU8("protocol_version")
295+
d.FieldU16("profile_version")
296+
dataSize = d.FieldU32("data_size")
297+
d.FieldUTF8("data_type", 4, d.StrAssert(".FIT"))
298+
d.FieldU16("crc", scalar.UintHex)
299+
})
300+
301+
d.FramedFn(int64(dataSize)*8, func(d *decode.D) {
302+
d.FieldArray("records", func(d *decode.D) {
303+
for !d.End() {
304+
d.FieldStruct("record_header", func(d *decode.D) {
305+
d.FieldU1("normal_header")
306+
messageType := d.FieldU1("message_type", messageTypeMap)
307+
messageTypeSpecific := d.FieldU1("message_type_specific")
308+
d.FieldU1("reserved")
309+
localMessageType := d.FieldU4("local_message_type", definitions)
310+
d.FieldStruct("message", func(d *decode.D) {
311+
switch messageType {
312+
case messageTypeData:
313+
if de, ok := definitions[localMessageType]; ok {
314+
decodeDataMessage(d, de)
315+
} else {
316+
d.Fatalf("unknown local message type %d", localMessageType)
317+
}
318+
case messageTypeDefinition:
319+
definitions[localMessageType] = decodeDefinitionMessage(d, messageTypeSpecific)
320+
default:
321+
panic("unreachable")
322+
}
323+
})
324+
})
325+
}
326+
})
327+
})
328+
d.FieldU16("crc", scalar.UintHex)
329+
330+
return nil
331+
}

format/fit/testdata/test.fit

467 KB
Binary file not shown.

format/format.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ var (
9595
Ether_8023_Frame = &decode.Group{Name: "ether8023_frame"}
9696
Exif = &decode.Group{Name: "exif"}
9797
Fairplay_SPC = &decode.Group{Name: "fairplay_spc"}
98+
FIT = &decode.Group{Name: "fit"}
9899
FLAC = &decode.Group{Name: "flac"}
99100
FLAC_Frame = &decode.Group{Name: "flac_frame"}
100101
FLAC_Metadatablock = &decode.Group{Name: "flac_metadatablock"}

0 commit comments

Comments
 (0)