package fitparser import ( "encoding/binary" "fmt" "math" ) // FieldDefinition represents a field definition in a message definition type FieldDefinition struct { Num uint8 // Field definition number Size uint8 // Size in bytes BaseType uint8 // Base type } // DeveloperFieldDefinition represents a developer field definition type DeveloperFieldDefinition struct { Num uint8 Size uint8 DeveloperDataIndex uint8 } // MessageDefinition represents a message definition record type MessageDefinition struct { Reserved uint8 Architecture uint8 // 0 = little endian, 1 = big endian GlobalMesgNum uint16 NumFields uint8 FieldDefinitions []FieldDefinition NumDevFields uint8 DevFieldDefinitions []DeveloperFieldDefinition } // Message represents a decoded FIT message type Message struct { Num uint16 // Global message number Fields map[uint8]interface{} // Field number -> value DevFields map[uint8]interface{} // Developer field number -> value } // NewMessage creates a new message func NewMessage(num uint16) *Message { return &Message{ Num: num, Fields: make(map[uint8]interface{}), DevFields: make(map[uint8]interface{}), } } // GetFieldValue returns the value of a field by field number func (m *Message) GetFieldValue(fieldNum uint8) (interface{}, bool) { val, ok := m.Fields[fieldNum] return val, ok } // GetFieldValueUint8 returns a field value as uint8 func (m *Message) GetFieldValueUint8(fieldNum uint8) (uint8, bool) { if val, ok := m.Fields[fieldNum]; ok { if v, ok := val.(uint8); ok { return v, true } } return 0, false } // GetFieldValueUint16 returns a field value as uint16 func (m *Message) GetFieldValueUint16(fieldNum uint8) (uint16, bool) { if val, ok := m.Fields[fieldNum]; ok { if v, ok := val.(uint16); ok { return v, true } } return 0, false } // GetFieldValueUint32 returns a field value as uint32 func (m *Message) GetFieldValueUint32(fieldNum uint8) (uint32, bool) { if val, ok := m.Fields[fieldNum]; ok { if v, ok := val.(uint32); ok { return v, true } } return 0, false } // GetFieldValueString returns a field value as string func (m *Message) GetFieldValueString(fieldNum uint8) (string, bool) { if val, ok := m.Fields[fieldNum]; ok { if v, ok := val.(string); ok { return v, true } } return "", false } // IsLittleEndian returns true if the message definition uses little endian byte order func (md *MessageDefinition) IsLittleEndian() bool { return md.Architecture == 0 } // DecodeField decodes a single field value from raw bytes func DecodeField(data []byte, fieldDef FieldDefinition, littleEndian bool) (interface{}, error) { if len(data) < int(fieldDef.Size) { return nil, fmt.Errorf("insufficient data for field: need %d bytes, got %d", fieldDef.Size, len(data)) } // Extract the base type number by masking (removes endian flag) baseTypeNum := fieldDef.BaseType & BaseTypeNumMask fieldData := data[:fieldDef.Size] switch baseTypeNum { case 0x00, 0x02, 0x0A, 0x0D: // Enum (0x00), Uint8 (0x02), Uint8z (0x0A), Byte (0x0D) if fieldDef.Size == 1 { return uint8(fieldData[0]), nil } // Array of bytes result := make([]uint8, fieldDef.Size) copy(result, fieldData) return result, nil case 0x01: // Sint8 if fieldDef.Size == 1 { return int8(fieldData[0]), nil } // Array of int8 result := make([]int8, fieldDef.Size) for i := range result { result[i] = int8(fieldData[i]) } return result, nil case 0x04, 0x0B: // Uint16 (0x84 masked), Uint16z (0x8B masked) if fieldDef.Size == 2 { if littleEndian { return binary.LittleEndian.Uint16(fieldData), nil } return binary.BigEndian.Uint16(fieldData), nil } // Array of uint16 count := fieldDef.Size / 2 result := make([]uint16, count) for i := range result { if littleEndian { result[i] = binary.LittleEndian.Uint16(fieldData[i*2:]) } else { result[i] = binary.BigEndian.Uint16(fieldData[i*2:]) } } return result, nil case 0x03: // Sint16 (0x83 masked) if fieldDef.Size == 2 { if littleEndian { return int16(binary.LittleEndian.Uint16(fieldData)), nil } return int16(binary.BigEndian.Uint16(fieldData)), nil } // Array of int16 count := fieldDef.Size / 2 result := make([]int16, count) for i := range result { if littleEndian { result[i] = int16(binary.LittleEndian.Uint16(fieldData[i*2:])) } else { result[i] = int16(binary.BigEndian.Uint16(fieldData[i*2:])) } } return result, nil case 0x06, 0x0C: // Uint32 (0x86 masked), Uint32z (0x8C masked) if fieldDef.Size == 4 { if littleEndian { return binary.LittleEndian.Uint32(fieldData), nil } return binary.BigEndian.Uint32(fieldData), nil } // Array of uint32 count := fieldDef.Size / 4 result := make([]uint32, count) for i := range result { if littleEndian { result[i] = binary.LittleEndian.Uint32(fieldData[i*4:]) } else { result[i] = binary.BigEndian.Uint32(fieldData[i*4:]) } } return result, nil case 0x05: // Sint32 (0x85 masked) if fieldDef.Size == 4 { if littleEndian { return int32(binary.LittleEndian.Uint32(fieldData)), nil } return int32(binary.BigEndian.Uint32(fieldData)), nil } // Array of int32 count := fieldDef.Size / 4 result := make([]int32, count) for i := range result { if littleEndian { result[i] = int32(binary.LittleEndian.Uint32(fieldData[i*4:])) } else { result[i] = int32(binary.BigEndian.Uint32(fieldData[i*4:])) } } return result, nil case 0x08: // Float32 (0x88 masked) if fieldDef.Size == 4 { var bits uint32 if littleEndian { bits = binary.LittleEndian.Uint32(fieldData) } else { bits = binary.BigEndian.Uint32(fieldData) } return math.Float32frombits(bits), nil } // Array of float32 count := fieldDef.Size / 4 result := make([]float32, count) for i := range result { var bits uint32 if littleEndian { bits = binary.LittleEndian.Uint32(fieldData[i*4:]) } else { bits = binary.BigEndian.Uint32(fieldData[i*4:]) } result[i] = math.Float32frombits(bits) } return result, nil case 0x09: // Float64 (0x89 masked) if fieldDef.Size == 8 { var bits uint64 if littleEndian { bits = binary.LittleEndian.Uint64(fieldData) } else { bits = binary.BigEndian.Uint64(fieldData) } return math.Float64frombits(bits), nil } // Array of float64 count := fieldDef.Size / 8 result := make([]float64, count) for i := range result { var bits uint64 if littleEndian { bits = binary.LittleEndian.Uint64(fieldData[i*8:]) } else { bits = binary.BigEndian.Uint64(fieldData[i*8:]) } result[i] = math.Float64frombits(bits) } return result, nil case 0x0F, 0x10: // Uint64 (0x8F masked), Uint64z (0x90 masked) if fieldDef.Size == 8 { if littleEndian { return binary.LittleEndian.Uint64(fieldData), nil } return binary.BigEndian.Uint64(fieldData), nil } // Array of uint64 count := fieldDef.Size / 8 result := make([]uint64, count) for i := range result { if littleEndian { result[i] = binary.LittleEndian.Uint64(fieldData[i*8:]) } else { result[i] = binary.BigEndian.Uint64(fieldData[i*8:]) } } return result, nil case 0x0E: // Sint64 (0x8E masked) if fieldDef.Size == 8 { if littleEndian { return int64(binary.LittleEndian.Uint64(fieldData)), nil } return int64(binary.BigEndian.Uint64(fieldData)), nil } // Array of int64 count := fieldDef.Size / 8 result := make([]int64, count) for i := range result { if littleEndian { result[i] = int64(binary.LittleEndian.Uint64(fieldData[i*8:])) } else { result[i] = int64(binary.BigEndian.Uint64(fieldData[i*8:])) } } return result, nil case 0x07: // String // String field - find null terminator end := len(fieldData) for i, b := range fieldData { if b == 0 { end = i break } } return string(fieldData[:end]), nil default: return nil, fmt.Errorf("unsupported base type: 0x%02X (num: 0x%02X)", fieldDef.BaseType, baseTypeNum) } }