package adsb import ( "errors" "fmt" "io" "git.maze.io/go/ham/protocol/adsb/fields" ) var ( ErrLength = errors.New("adsb: the supplied data must be 7 bytes long") ErrFormatType = errors.New("adsb: invalid format type code") ErrSubType = errors.New("adsb: invalid sub type code") ) type Payload interface { UnmarshalBytes([]byte) error } type Message []byte // FormatType returns the format type code // // Code BDS Description V0 V1 V2 // // 0 ? No Position // 1 0,8 Aircraft Id OK OK OK // 2 0,8 Aircraft Id OK OK OK // 3 0,8 Aircraft Id OK OK OK // 4 0,8 Aircraft Id OK OK OK // 5 0,6 Surface position OK OK OK // 6 0,6 Surface position OK OK OK // 7 0,6 Surface position OK OK OK // 8 0,6 Surface position OK OK OK // 9 0,5 Airborne position OK OK OK // // 10 0,5 Airborne position OK OK OK // 14 0,5 Airborne position OK OK OK // 12 0,5 Airborne position OK OK OK // 13 0,5 Airborne position OK OK OK // 14 0,5 Airborne position OK OK OK // 15 0,5 Airborne position OK OK OK // 16 0,5 Airborne position OK OK OK // 17 0,5 Airborne position OK OK OK // 18 0,5 Airborne position OK OK OK // 19 0,9 Airborne velocity OK OK OK // 20 0,5 Airborne position OK OK OK // 21 0,5 Airborne position OK OK OK // 22 0,5 Airborne position OK OK OK // 23 Reserved // 24 Reserved // 25 Reserved // 26 Reserved // 27 Reserved // 28 6,1 Emergency report OK OK OK // 29 6,2 Target and status __ OK OK // 30 Reserved // 31 6,5 Operational status OK OK OK func (msg Message) FormatType() byte { if len(msg) < 7 { return 0 } return (msg[0] & 0xF8) >> 3 } func (msg Message) Payload() (Payload, error) { var payload Payload switch formatType := msg.FormatType(); formatType { case 0: if msg[2]&0x0F == 0 && msg[3] == 0 && msg[4] == 0 && msg[5] == 0 && msg[6] == 0 { payload = new(NoPositionInformation) } else { payload = new(AirbornePositionType0) } case 1, 2, 3, 4: payload = new(AircraftIdentificationAndCategory) case 5, 6, 7, 8: payload = new(SurfacePosition) case 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22: payload = new(AirbornePosition) case 19: // check sub type switch subType := msg[0] & 0x07; subType { case 1: payload = new(AirborneVelocityAirSpeedNormal) case 2: payload = new(AirborneVelocityAirSpeedSupersonic) case 3: payload = new(AirborneVelocityGroundSpeedNormal) case 4: payload = new(AirborneVelocityGroundSpeedSupersonic) default: return nil, fmt.Errorf("adsb: invalid airborne velocity sub type %d", subType) } case 28: switch subType := msg[0] & 0x07; subType { case 0: payload = new(AircraftStatusNoInformation) case 1: payload = new(AircraftStatusEmergency) case 2: payload = new(AircraftStatusACAS) default: return nil, fmt.Errorf("adsb: invalid aircraft status sub type %d", subType) } case 29: switch subType := (msg[0] & 0x06) >> 1; subType { case 0: payload = new(TargetStateAndStatus0) case 1: payload = new(TargetStateAndStatus1) default: return nil, fmt.Errorf("adsb: invalid target state and status sub type %d", subType) } case 31: payload = new(AircraftOperationalStatus) default: return nil, fmt.Errorf("adsb: the format type code %d is not supported", formatType) } if err := payload.UnmarshalBytes(msg); err != nil { return nil, err } return payload, nil } type NoPositionInformation struct { FormatType byte AltitudeBarometric int32 NavigationIntegrityCategory byte } func (msg *NoPositionInformation) UnmarshalBytes(data []byte) error { if len(data) != 7 { return ErrLength } // Update the first byte to have a standard BDS code 0,5 (Airborne position with baro altitude) - format 15 temp := []byte{0x78, data[1], data[2], 0, 0, 0, 0} var pos AirbornePosition if err := pos.UnmarshalBytes(temp); err != nil { return err } msg.FormatType = pos.FormatType msg.AltitudeBarometric = pos.AltitudeInFeet msg.NavigationIntegrityCategory = 0 return nil } // AirbornePosition is a message at the format BDS 0,5 type AirbornePosition struct { FormatType byte SurveillanceStatus fields.SurveillanceStatus HasDualAntenna bool AltitudeSource fields.AltitudeSource AltitudeReportMethod fields.AltitudeReportMethod AltitudeInFeet int32 TimeSynchronizedToUTC bool CompactPositionReportingFormat fields.CompactPositionReportingFormat EncodedLatitude uint32 EncodedLongitude uint32 } func (msg *AirbornePosition) UnmarshalBytes(data []byte) error { if len(data) != 7 { return ErrLength } msg.FormatType = Message(data).FormatType() if msg.FormatType < 9 || msg.FormatType > 22 || msg.FormatType == 19 { return ErrFormatType } var err error if msg.AltitudeInFeet, msg.AltitudeSource, msg.AltitudeReportMethod, err = fields.ParseAltitude(data); err != nil { return err } return nil } type AirbornePositionType0 struct { FormatTypeCode byte NavigationIntegrityCategory byte AirbornePosition } func (msg *AirbornePositionType0) UnmarshalBytes(data []byte) error { // Update the first byte to have a standard BDS code 0,5 (Airborne position with baro altitude) - format 22 temp := []byte{0xB0, data[1], data[2], data[3], data[4], data[5], data[6]} if err := msg.AirbornePosition.UnmarshalBytes(temp); err != nil { return err } msg.NavigationIntegrityCategory = 0 return nil } type AircraftIdentificationAndCategory struct { FormatType byte CategorySet byte Category byte Identification string } func (msg *AircraftIdentificationAndCategory) UnmarshalBytes(data []byte) error { if len(data) < 7 { return io.ErrUnexpectedEOF } msg.FormatType = Message(data).FormatType() for msg.FormatType < 1 || msg.FormatType > 4 { return ErrFormatType } msg.Category = data[0] & 0x07 switch msg.FormatType { case 1: msg.CategorySet = 'D' case 2: msg.CategorySet = 'C' case 3: msg.CategorySet = 'B' default: msg.CategorySet = 'A' } // Get the codes codes := make([]byte, 8) codes[0] = (data[1] & 0xFC) >> 2 codes[1] = (data[1]&0x03)<<4 + (data[2]&0xF0)>>4 codes[2] = (data[2]&0x0F)<<2 + (data[3]&0xC0)>>6 codes[3] = data[3] & 0x3F codes[4] = (data[4] & 0xFC) >> 2 codes[5] = (data[4]&0x03)<<4 + (data[5]&0xF0)>>4 codes[6] = (data[5]&0x0F)<<2 + (data[6]&0xC0)>>6 codes[7] = data[6] & 0x3F // Convert the codes to actual char chars := make([]byte, 8) for i, code := range codes { chars[i] = identificationCharacterCoding[code] } msg.Identification = string(chars) return nil } type SurfacePosition struct { FormatType byte MovementStatus fields.MovementStatus MovementSpeed float64 GroundTrackStatus bool GroundTrack float64 TimeSynchronizedToUTC bool CompactPositionReportingFormat fields.CompactPositionReportingFormat EncodedLatitude fields.EncodedLatitude EncodedLongitude fields.EncodedLongitude } func (msg *SurfacePosition) UnmarshalBytes(data []byte) error { if len(data) != 7 { return ErrLength } msg.FormatType = (data[0] & 0xF8) >> 3 if msg.FormatType < 5 || msg.FormatType > 8 { return ErrFormatType } msg.MovementSpeed, msg.MovementStatus = fields.ParseMovementStatus(data) msg.GroundTrack, msg.GroundTrackStatus = fields.ParseGroundTrackStatus(data) msg.TimeSynchronizedToUTC = fields.ParseTimeSynchronizedToUTC(data) msg.CompactPositionReportingFormat = fields.ParseCompactPositioningReportFormat(data) msg.EncodedLatitude = fields.ParseEncodedLatitude(data) msg.EncodedLongitude = fields.ParseEncodedLongitude(data) return nil } type AirborneVelocity struct { FormatType byte SubType byte IntentChange bool IFRCapability bool NavigationUncertaintyCategory fields.NavigationUncertaintyCategory MagneticHeadingStatus bool MagneticHeading float64 AirspeedStatus fields.NumericValueStatus Airspeed uint16 VerticalRateSource fields.VerticalRateSource VerticalRateStatus fields.NumericValueStatus VerticalRate int16 } // AirborneVelocityGroundSpeedNormal is a message at the format BDS 9,0 type AirborneVelocityGroundSpeedNormal struct { AirborneVelocity DirectionEastWest fields.DirectionEastWest VelocityEWStatus fields.NumericValueStatus VelocityEW uint16 DirectionNorthSouth fields.DirectionNorthSouth VelocityNSStatus fields.NumericValueStatus VelocityNS uint16 DifferenceAltitudeGNSSBaroStatus fields.NumericValueStatus DifferenceAltitudeGNSSBaro int16 } func (msg *AirborneVelocityGroundSpeedNormal) UnmarshalBytes(data []byte) error { if err := msg.unmarshalBytes(data, 1); err != nil { return err } msg.Airspeed, msg.AirspeedStatus = fields.ParseAirspeedSupersonic(data) msg.DirectionEastWest = fields.ParseDirectionEastWest(data) msg.VelocityEW, msg.VelocityEWStatus = fields.ParseVelocityEastWestNormal(data) msg.DirectionNorthSouth = fields.ParseDirectionNorthSouth(data) msg.VelocityNS, msg.VelocityNSStatus = fields.ParseVelocityNorthSouthNormal(data) msg.DifferenceAltitudeGNSSBaro, msg.DifferenceAltitudeGNSSBaroStatus = fields.ParseHeightDifferenceFromBaro(data) return nil } // AirborneVelocityGroundSpeedSupersonic is a message at the format BDS 9,0 type AirborneVelocityGroundSpeedSupersonic struct { AirborneVelocity DirectionEastWest fields.DirectionEastWest VelocityEWStatus fields.NumericValueStatus VelocityEW uint16 DirectionNorthSouth fields.DirectionNorthSouth VelocityNSStatus fields.NumericValueStatus VelocityNS uint16 DifferenceAltitudeGNSSBaroStatus fields.NumericValueStatus DifferenceAltitudeGNSSBaro int16 } func (msg *AirborneVelocityGroundSpeedSupersonic) UnmarshalBytes(data []byte) error { if err := msg.unmarshalBytes(data, 2); err != nil { return err } msg.Airspeed, msg.AirspeedStatus = fields.ParseAirspeedSupersonic(data) msg.DirectionEastWest = fields.ParseDirectionEastWest(data) msg.VelocityEW, msg.VelocityEWStatus = fields.ParseVelocityEastWestSupersonic(data) msg.DirectionNorthSouth = fields.ParseDirectionNorthSouth(data) msg.VelocityNS, msg.VelocityNSStatus = fields.ParseVelocityNorthSouthSupersonic(data) msg.DifferenceAltitudeGNSSBaro, msg.DifferenceAltitudeGNSSBaroStatus = fields.ParseHeightDifferenceFromBaro(data) return nil } type AirborneVelocityAirSpeedNormal struct { AirborneVelocity HeightDifferenceFromBaroStatus fields.NumericValueStatus HeightDifferenceFromBaro int16 } func (msg *AirborneVelocity) unmarshalBytes(data []byte, subType byte) error { if len(data) != 7 { return ErrLength } msg.FormatType = (data[0] & 0xF8) >> 3 if msg.FormatType != 19 { return ErrFormatType } msg.SubType = data[0] & 0x07 if msg.SubType != subType { return ErrSubType } msg.IntentChange = fields.ParseIntentChange(data) msg.IFRCapability = fields.ParseIFRCapability(data) msg.NavigationUncertaintyCategory = fields.ParseeNavigationUncertaintyCategory(data) msg.MagneticHeading, msg.MagneticHeadingStatus = fields.ParseMagneticHeading(data) msg.VerticalRateSource = fields.ParseVerticalRateSource(data) msg.VerticalRate, msg.VerticalRateStatus = fields.ParseVerticalRate(data) return nil } func (msg *AirborneVelocityAirSpeedNormal) UnmarshalBytes(data []byte) error { if err := msg.unmarshalBytes(data, 3); err != nil { return err } msg.Airspeed, msg.AirspeedStatus = fields.ParseAirspeedNormal(data) msg.HeightDifferenceFromBaro, msg.HeightDifferenceFromBaroStatus = fields.ParseHeightDifferenceFromBaro(data) return nil } type AirborneVelocityAirSpeedSupersonic struct { AirborneVelocity HeightDifferenceFromBaroStatus fields.NumericValueStatus HeightDifferenceFromBaro int16 } func (msg *AirborneVelocityAirSpeedSupersonic) UnmarshalBytes(data []byte) error { if err := msg.unmarshalBytes(data, 4); err != nil { return err } msg.Airspeed, msg.AirspeedStatus = fields.ParseAirspeedSupersonic(data) msg.HeightDifferenceFromBaro, msg.HeightDifferenceFromBaroStatus = fields.ParseHeightDifferenceFromBaro(data) return nil } type AircraftStatusNoInformation struct { FormatType byte SubType byte EmergencyPriorityStatus fields.EmergencyPriorityStatus } func (msg *AircraftStatusNoInformation) UnmarshalBytes(data []byte) error { if len(data) != 7 { return ErrLength } msg.FormatType = (data[0] & 0xF8) >> 3 if msg.FormatType != 28 { return ErrFormatType } msg.SubType = data[0] & 0x07 if msg.SubType != 0 { return ErrSubType } msg.EmergencyPriorityStatus = fields.ParseEmergencyPriorityStatus(data) return nil } type AircraftStatusEmergency struct { FormatType byte SubType byte EmergencyPriorityStatus fields.EmergencyPriorityStatus ModeACode uint16 } func (msg *AircraftStatusEmergency) UnmarshalBytes(data []byte) error { if len(data) != 7 { return ErrLength } msg.FormatType = (data[0] & 0xF8) >> 3 if msg.FormatType != 28 { return ErrFormatType } msg.SubType = data[0] & 0x07 if msg.SubType != 1 { return ErrSubType } msg.EmergencyPriorityStatus = fields.ParseEmergencyPriorityStatus(data) msg.ModeACode = uint16((data[1]&0x1F)<<8) | uint16(data[2]) return nil } type AircraftStatusACAS struct { FormatType byte SubType byte ResolutionAdvisory ResolutionAdvisory } type TargetStateAndStatus0 struct { FormatType byte SubType byte VerticalDataAvailableSourceIndicator fields.VerticalDataAvailableSourceIndicator TargetAltitudeType fields.TargetAltitudeType TargetAltitudeCapability fields.TargetAltitudeCapability VerticalModeIndicator fields.VerticalModeIndicator TargetAltitudeStatus fields.NumericValueStatus TargetAltitude int32 HorizontalDataAvailableSourceIndicator fields.HorizontalDataAvailableSourceIndicator TargetHeadingTrackAngleStatus fields.NumericValueStatus TargetHeadingTrackAngle uint16 TargetHeadingTrackIndicator fields.TargetHeadingTrackIndicator HorizontalModeIndicator fields.HorizontalModeIndicator NavigationalAccuracyCategoryPosition fields.NavigationalAccuracyCategoryPositionV1 NICBaro fields.NICBaro SurveillanceIntegrityLevel fields.SurveillanceIntegrityLevel CapabilityModeCode fields.CapabilityModeCode EmergencyPriorityStatus fields.EmergencyPriorityStatus } func (msg *TargetStateAndStatus0) UnmarshalBytes(data []byte) error { if len(data) != 7 { return ErrLength } msg.FormatType = (data[0] & 0xF8) >> 3 if msg.FormatType != 29 { return ErrFormatType } msg.SubType = (data[0] & 0x06) >> 1 if msg.SubType != 0 { return ErrSubType } } var identificationCharacterCoding = []byte{ '#', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '#', '#', '#', '#', '#', ' ', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '#', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '#', '#', '#', '#', '#', '#', }