package fitparser import ( "bytes" "encoding/binary" "fmt" ) // Header represents a FIT file header type Header struct { Size uint8 // Header size (12 or 14 bytes) ProtocolVersion uint8 // Protocol version ProfileVersion uint16 // Profile version DataSize uint32 // Size of data records in bytes DataType [4]byte // ".FIT" signature CRC uint16 // CRC of header (only present if Size == 14) } // ParseHeader parses a FIT file header from the given data func ParseHeader(data []byte) (*Header, error) { if len(data) < HeaderMinSize { return nil, fmt.Errorf("header too small: got %d bytes, need at least %d", len(data), HeaderMinSize) } h := &Header{ Size: data[0], ProtocolVersion: data[1], DataType: [4]byte{data[8], data[9], data[10], data[11]}, } // Validate header size if h.Size != HeaderWithCRCSize && h.Size != HeaderWithoutCRCSize { return nil, fmt.Errorf("invalid header size: %d (expected %d or %d)", h.Size, HeaderWithoutCRCSize, HeaderWithCRCSize) } // Ensure we have enough data if len(data) < int(h.Size) { return nil, fmt.Errorf("insufficient data for header: got %d bytes, need %d", len(data), h.Size) } // Parse profile version (little endian) h.ProfileVersion = binary.LittleEndian.Uint16(data[2:4]) // Parse data size (little endian) h.DataSize = binary.LittleEndian.Uint32(data[4:8]) // Validate signature if h.DataType != FITSignature { return nil, fmt.Errorf("invalid FIT signature: got %s, expected .FIT", string(h.DataType[:])) } // Parse CRC if present if h.Size == HeaderWithCRCSize { h.CRC = binary.LittleEndian.Uint16(data[12:14]) // Validate header CRC calculatedCRC := CalculateCRC(data[0:12]) if calculatedCRC != h.CRC { return nil, fmt.Errorf("header CRC mismatch: got 0x%04X, expected 0x%04X", h.CRC, calculatedCRC) } } return h, nil } // Validate performs basic validation on the header func (h *Header) Validate() error { if h.Size != HeaderWithCRCSize && h.Size != HeaderWithoutCRCSize { return fmt.Errorf("invalid header size: %d", h.Size) } if h.DataType != FITSignature { return fmt.Errorf("invalid FIT signature: %s", string(h.DataType[:])) } protocolMajor := h.ProtocolVersion >> ProtocolVersionMajorShift if protocolMajor < 1 || protocolMajor > 2 { return fmt.Errorf("unsupported protocol version: %d.%d", protocolMajor, h.ProtocolVersion&ProtocolVersionMinorMask) } return nil } // ProtocolVersionMajor returns the major version of the protocol func (h *Header) ProtocolVersionMajor() uint8 { return h.ProtocolVersion >> ProtocolVersionMajorShift } // ProtocolVersionMinor returns the minor version of the protocol func (h *Header) ProtocolVersionMinor() uint8 { return h.ProtocolVersion & ProtocolVersionMinorMask } // IsFIT checks if the given data starts with a valid FIT file signature func IsFIT(data []byte) bool { if len(data) < HeaderMinSize { return false } signature := [4]byte{data[8], data[9], data[10], data[11]} return bytes.Equal(signature[:], FITSignature[:]) } // String returns a string representation of the header func (h *Header) String() string { return fmt.Sprintf("FIT Header: Protocol v%d.%d, Profile v%d, DataSize=%d bytes, HeaderSize=%d bytes", h.ProtocolVersionMajor(), h.ProtocolVersionMinor(), h.ProfileVersion, h.DataSize, h.Size) }