111 lines
3.4 KiB
Go
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)
|
|
}
|