1
1
mirror of https://github.com/wader/fq.git synced 2024-10-05 16:07:23 +03:00
This commit is contained in:
Kristian Karl 2022-08-02 08:55:19 +02:00 committed by Mattias Wadman
parent a7d54ff190
commit 5d67341e1a
4 changed files with 333 additions and 0 deletions

View File

@ -21,6 +21,7 @@ import (
_ "github.com/wader/fq/format/dns"
_ "github.com/wader/fq/format/elf"
_ "github.com/wader/fq/format/fairplay"
_ "github.com/wader/fq/format/fit"
_ "github.com/wader/fq/format/flac"
_ "github.com/wader/fq/format/gif"
_ "github.com/wader/fq/format/gzip"

331
format/fit/fit.go Normal file
View File

@ -0,0 +1,331 @@
package fit
// TODO: chained files
// TODO: developer message?
// TODO: filed number mapping, xls file?
// TODO: Compressed Timestamp Header
// https://developer.garmin.com/fit/protocol/
import (
"github.com/wader/fq/format"
"github.com/wader/fq/pkg/decode"
"github.com/wader/fq/pkg/interp"
"github.com/wader/fq/pkg/scalar"
)
func init() {
interp.RegisterFormat(
format.FIT,
&decode.Format{
Description: "Flexible and Interoperable Data Transfer Protocol",
Groups: []*decode.Group{format.Probe},
DecodeFn: decodeFIT,
})
}
const (
architectureTypeLittleEndian = 0
architectureTypeBigEndian = 1
)
var architectureTypeMap = scalar.UintMapSymStr{
architectureTypeLittleEndian: "little_endian",
architectureTypeBigEndian: "big_endian",
}
const (
messageTypeData = 0
messageTypeDefinition = 1
)
var messageTypeMap = scalar.UintMapSymStr{
messageTypeData: "data",
messageTypeDefinition: "definition",
}
// Base Type # Endian Ability Base Type Field Type Name Invalid Value Size (Bytes) Comment
// 0 0 0x00 enum 0xFF 1
// 1 0 0x01 sint8 0x7F 1 2s complement format
// 2 0 0x02 uint8 0xFF 1
// 3 1 0x83 sint16 0x7FFF 2 2s complement format
// 4 1 0x84 uint16 0xFFFF 2
// 5 1 0x85 sint32 0x7FFFFFFF 4 2s complement format
// 6 1 0x86 uint32 0xFFFFFFFF 4
// 7 0 0x07 string 0x00 1 Null terminated string encoded in UTF-8 format
// 8 1 0x88 float32 0xFFFFFFFF 4
// 9 1 0x89 float64 0xFFFFFFFFFFFFFFFF 8
// 10 0 0x0A uint8z 0x00 1
// 11 1 0x8B uint16z 0x0000 2
// 12 1 0x8C uint32z 0x00000000 4
// 13 0 0x0D byte 0xFF 1 Array of bytes. Field is invalid if all bytes are invalid.
// 14 1 0x8E sint64 0x7FFFFFFFFFFFFFFF 8 2s complement format
// 15 1 0x8F uint64 0xFFFFFFFFFFFFFFFF 8
// 16 1 0x90 uint64z 0x0000000000000000 8
const (
baseTypeEnum = 0
baseTypeSint8 = 1
baseTypeUint8 = 2
baseTypeSint16 = 3
baseTypeUint16 = 4
baseTypeSint32 = 5
baseTypeUint32 = 6
baseTypeString = 7
baseTypeFloat32 = 8
baseTypeFloat64 = 9
baseTypeUint8z = 10
baseTypeUint16z = 11
baseTypeUint32z = 12
baseTypeByte = 13
baseTypeSint64 = 14
baseTypeUint64 = 15
baseTypeUint64z = 16
)
var baseTypeMap = scalar.UintMapSymStr{
baseTypeEnum: "enum",
baseTypeSint8: "sint8",
baseTypeUint8: "uint8",
baseTypeSint16: "sint16",
baseTypeUint16: "uint16",
baseTypeSint32: "sint32",
baseTypeUint32: "uint32",
baseTypeString: "string",
baseTypeFloat32: "float32",
baseTypeFloat64: "float64",
baseTypeUint8z: "uint8z",
baseTypeUint16z: "uint16z",
baseTypeUint32z: "uint32z",
baseTypeByte: "byte",
baseTypeSint64: "sint64",
baseTypeUint64: "uint64",
baseTypeUint64z: "uint64z",
}
var baseTypeSize = map[int]int{
baseTypeEnum: 1,
baseTypeSint8: 1,
baseTypeUint8: 1,
baseTypeSint16: 2,
baseTypeUint16: 2,
baseTypeSint32: 4,
baseTypeUint32: 4,
baseTypeString: 1,
baseTypeFloat32: 4,
baseTypeFloat64: 8,
baseTypeUint8z: 1,
baseTypeUint16z: 2,
baseTypeUint32z: 4,
baseTypeByte: 1,
baseTypeSint64: 8,
baseTypeUint64: 8,
baseTypeUint64z: 8,
}
type field struct {
number uint8
size uint8
endianAbility uint8
baseType uint8
}
type developerField struct {
number uint8
size uint8
developerIndex uint8
}
type definition struct {
s scalar.Uint
architecture int
globalMessageNumber int
fields []field
developerFields []developerField
}
type definitionEntries map[uint64]definition
func (fes definitionEntries) MapUint(s scalar.Uint) (scalar.Uint, error) {
u := s.Actual
if fe, ok := fes[u]; ok {
s = fe.s
s.Actual = u
}
return s, nil
}
func decodeBaseType(d *decode.D, f field) {
switch f.baseType {
case baseTypeEnum:
d.FieldU8("value")
case baseTypeSint8:
d.FieldS8("value")
case baseTypeUint8:
d.FieldU8("value")
case baseTypeSint16:
d.FieldS16("value")
case baseTypeUint16:
d.FieldU16("value")
case baseTypeSint32:
d.FieldU32("value")
case baseTypeUint32:
d.FieldU32("value")
case baseTypeString:
d.FieldUTF8NullFixedLen("value", int(f.size))
case baseTypeFloat32:
d.FieldF32("value")
case baseTypeFloat64:
d.FieldF64("value")
case baseTypeUint8z:
d.FieldU8("value")
case baseTypeUint16z:
d.FieldU16("value")
case baseTypeUint32z:
d.FieldU32("value")
case baseTypeByte:
d.FieldRawLen("value", int64(f.size)*8)
case baseTypeSint64:
d.FieldS64("value")
case baseTypeUint64:
d.FieldU64("value")
case baseTypeUint64z:
d.FieldU64("value")
default:
d.Fatalf("unknown base type %d", f.baseType)
}
}
func decodeDataMessage(d *decode.D, de definition) {
d.FieldArray("fields", func(d *decode.D) {
for _, f := range de.fields {
baseSize, ok := baseTypeSize[int(f.baseType)]
if !ok {
d.Fatalf("unknown base size for base type %d", f.baseType)
}
values := int(f.size) / baseSize
switch {
case values == 1,
f.baseType == baseTypeString,
f.baseType == baseTypeByte:
decodeBaseType(d, f)
default:
d.FieldArray("values", func(d *decode.D) {
for i := 0; i < values; i++ {
decodeBaseType(d, f)
}
})
}
}
})
if len(de.developerFields) > 0 {
d.FieldArray("developer_fields", func(d *decode.D) {
for _, f := range de.developerFields {
d.FieldRawLen("filed", int64(f.size)*8)
}
})
}
}
func decodeDefinitionMessage(d *decode.D, messageTypeSpecific uint64) definition {
var de definition
d.FieldU8("reserved")
de.architecture = int(d.FieldU8("architecture", architectureTypeMap))
de.globalMessageNumber = int(d.FieldU16("global_message_number"))
numFields := d.FieldU8("fields")
d.FieldArray("field_definitions", func(d *decode.D) {
for i := uint64(0); i < numFields; i++ {
d.FieldStruct("field_definition", func(d *decode.D) {
var f field
f.number = uint8(d.FieldU8("field_definition_number"))
f.size = uint8(d.FieldU8("size"))
f.endianAbility = uint8(d.FieldU1("endian_ability"))
d.FieldRawLen("reserved", 2)
f.baseType = uint8(d.FieldU5("base_type_number", baseTypeMap))
de.fields = append(de.fields, f)
})
}
})
if messageTypeSpecific == 1 {
developerFields := d.FieldU8("developer_fields")
d.FieldArray("developer_field_definitions", func(d *decode.D) {
for i := uint64(0); i < developerFields; i++ {
d.FieldStruct("developer_field_definition", func(d *decode.D) {
var f developerField
f.number = uint8(d.FieldU8("field_number"))
f.size = uint8(d.FieldU8("size"))
f.developerIndex = uint8(d.FieldU8("developer_data_index"))
de.developerFields = append(de.developerFields, f)
})
}
})
}
return de
}
func decodeFIT(d *decode.D) any {
d.Endian = decode.LittleEndian
definitions := definitionEntries{
0: definition{
s: scalar.Uint{Sym: "file_id"},
fields: []field{
{number: 0, size: 1, baseType: baseTypeEnum},
{number: 1, size: 2, baseType: baseTypeUint16},
{number: 2, size: 2, baseType: baseTypeUint16},
{number: 3, size: 4, baseType: baseTypeUint32z},
{number: 4, size: 4, baseType: baseTypeUint32},
{number: 5, size: 2, baseType: baseTypeUint16},
// {number: 5, baseType: baseTypeString},
},
},
}
var dataSize uint64
d.FieldStruct("header", func(d *decode.D) {
size := d.FieldU8("size")
if size < 12 {
d.Fatalf("Header size too small %d < 12", size)
}
d.FieldU8("protocol_version")
d.FieldU16("profile_version")
dataSize = d.FieldU32("data_size")
d.FieldUTF8("data_type", 4, d.StrAssert(".FIT"))
d.FieldU16("crc", scalar.UintHex)
})
d.FramedFn(int64(dataSize)*8, func(d *decode.D) {
d.FieldArray("records", func(d *decode.D) {
for !d.End() {
d.FieldStruct("record_header", func(d *decode.D) {
d.FieldU1("normal_header")
messageType := d.FieldU1("message_type", messageTypeMap)
messageTypeSpecific := d.FieldU1("message_type_specific")
d.FieldU1("reserved")
localMessageType := d.FieldU4("local_message_type", definitions)
d.FieldStruct("message", func(d *decode.D) {
switch messageType {
case messageTypeData:
if de, ok := definitions[localMessageType]; ok {
decodeDataMessage(d, de)
} else {
d.Fatalf("unknown local message type %d", localMessageType)
}
case messageTypeDefinition:
definitions[localMessageType] = decodeDefinitionMessage(d, messageTypeSpecific)
default:
panic("unreachable")
}
})
})
}
})
})
d.FieldU16("crc", scalar.UintHex)
return nil
}

BIN
format/fit/testdata/test.fit vendored Normal file

Binary file not shown.

View File

@ -95,6 +95,7 @@ var (
Ether_8023_Frame = &decode.Group{Name: "ether8023_frame"}
Exif = &decode.Group{Name: "exif"}
Fairplay_SPC = &decode.Group{Name: "fairplay_spc"}
FIT = &decode.Group{Name: "fit"}
FLAC = &decode.Group{Name: "flac"}
FLAC_Frame = &decode.Group{Name: "flac_frame"}
FLAC_Metadatablock = &decode.Group{Name: "flac_metadatablock"}