Files
fit-parser/fitparser/header.go
Thomas Klaehn 88b74b7dbc Initial commit
Signed-off-by: Thomas Klaehn <thomas.klaehn@perinet.io>
2026-02-08 07:28:32 +00:00

111 lines
3.4 KiB
Go

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)
}