diff --git a/protocol/adsb/fields/bds05.go b/protocol/adsb/fields/bds05.go deleted file mode 100644 index dc4d327..0000000 --- a/protocol/adsb/fields/bds05.go +++ /dev/null @@ -1,158 +0,0 @@ -package fields - -// SurveillanceStatus is the surveillance status -// -// Specified in Doc 9871 / A.2.3.2.6 -type SurveillanceStatus byte - -const ( - SSNoCondition SurveillanceStatus = iota // No aircraft category information - SSPermanentAlert // Permanent alert (emergency condition) - SSTemporaryAlert // Temporary alert (change in Mode A identity code other than emergency condition) - SSSPICondition // SPI condition - -) - -// AltitudeSource is the type of source of the Altitude: Barometric or GNSS. -type AltitudeSource byte - -const ( - AltitudeBarometric AltitudeSource = iota // Altitude is barometric altitude - AltitudeGNSS // Altitude is GNSS height (HAE) -) - -// AltitudeReportMethod defines how the altitude is reported. -type AltitudeReportMethod byte - -const ( - AltitudeReport100FootIncrements AltitudeReportMethod = iota // 100-foot increments - AltitudeReport25FootIncrements // 25-foot increments -) - -// CompactPositionReportingFormat is the CPR (Compact Position Reporting) format definition -// -// Specified in Doc 9871 / A.2.3.3.3 -type CompactPositionReportingFormat byte - -const ( - CPRFormatEven CompactPositionReportingFormat = iota // Even format coding - CPRFormatOdd // Odd format coding -) - -func ParseCompactPositioningReportFormat(data []byte) CompactPositionReportingFormat { - bits := (data[2] & 0x04) >> 2 - return CompactPositionReportingFormat(bits) -} - -func ParseAltitude(data []byte) (altitude int32, source AltitudeSource, method AltitudeReportMethod, err error) { - source = AltitudeBarometric - if format := (data[0] & 0xF8) >> 3; 20 <= format && format <= 22 { - source = AltitudeGNSS - } - - // Altitude code is a 12 bits fields, so read a uint16 - // bit | 8 9 10 11 12 13 14 15| 16 17 18 19 20 21 22 23 | - // message | x x x x x x x x| x x x x _ _ _ _ | - // 100 foot |C1 A1 C2 A2 C4 A4 B1 Q| B2 D2 B4 D4 _ _ _ _ | - // Get the Q bit - if qBit := (data[1] & 0x01) != 0; qBit { - // If the Q bit equals 1, the 11-bit field represented by bits 8 to 14 and 16 to 18 - n := uint16(0) - n |= uint16(data[1]&0xFE) << 3 - n |= uint16(data[2]&0xF0) >> 4 - return 25*int32(n) - 1000, source, AltitudeReport25FootIncrements, nil - } - - // Otherwise, altitude is reported in 100 foot increments - c1 := (data[1] & 0x80) != 0 - a1 := (data[1] & 0x40) != 0 - c2 := (data[1] & 0x20) != 0 - a2 := (data[1] & 0x10) != 0 - c4 := (data[1] & 0x08) != 0 - a4 := (data[1] & 0x04) != 0 - b1 := (data[1] & 0x02) != 0 - b2 := (data[2] & 0x80) != 0 - d2 := (data[2] & 0x40) != 0 - b4 := (data[2] & 0x20) != 0 - d4 := (data[2] & 0x10) != 0 - if altitude, err = GillhamToAltitude(false, d2, d4, a1, a2, a4, b1, b2, b4, c1, c2, c4); err != nil { - return - } - - return -} - -// MovementStatus is the status of the Movement information -type MovementStatus int - -const ( - // MSNoInformation indicates no information - MSNoInformation MovementStatus = 0 // No information - MSAircraftStopped MovementStatus = 1 // The aircraft is stopped - MSAboveMaximum MovementStatus = 124 // The Movement is above the maximum - MSReservedDecelerating MovementStatus = 125 // Reserved - MSReservedAccelerating MovementStatus = 126 // Reserved - MSReservedBackingUp MovementStatus = 127 // Reserved -) - -func ParseMovementStatus(data []byte) (float64, MovementStatus) { - bits := (data[0]&0x07)<<4 + (data[1]&0xF0)>>4 - status := MovementStatus(bits) - return status.Speed(), status -} - -// Speed in knots. -func (status MovementStatus) Speed() float64 { - if status == 0 || status == 1 || status > 124 { - return 0 - } else if 2 <= status && status <= 8 { - return 0.125 + float64(status-2)*0.125 - } else if 9 <= status && status <= 12 { - return 1 + float64(status-9)*0.25 - } else if 13 <= status && status <= 38 { - return 2 + float64(status-13)*0.5 - } else if 39 <= status && status <= 93 { - return 15 + float64(status-39)*1.0 - } else if 94 <= status && status <= 108 { - return 70 + float64(status-94)*2.0 - } else if 109 <= status && status <= 123 { - return 100 + float64(status-109)*5.0 - } - - // Movement max - return 175 -} - -// EncodedLatitude is the encoded latitude -// -// Specified in Doc 9871 / C.2.6 -type EncodedLatitude uint32 - -func ParseEncodedLatitude(data []byte) EncodedLatitude { - return EncodedLatitude((data[2]&0x02)>>1)<<16 | - EncodedLatitude((data[2]&0x01)<<7+(data[3]&0xFE)>>1)<<8 | - EncodedLatitude((data[3]&0x01)<<7+(data[4]&0xFE)>>1) -} - -// EncodedLongitude is the encoded longitude -// -// Specified in Doc 9871 / C.2.6 -type EncodedLongitude uint32 - -func ParseEncodedLongitude(data []byte) EncodedLongitude { - return EncodedLongitude(data[4]&0x01)<<16 | - EncodedLongitude(data[5])<<8 | - EncodedLongitude(data[6]) -} - -func ParseGroundTrackStatus(data []byte) (track float64, status bool) { - status = (data[1] & 0x08) != 0 - - allBits := (uint16(data[1]&0x07)<<8 | uint16(data[2]&0xF0)) >> 4 - track = float64(allBits) * 360.0 / 128.0 - return -} - -func ParseTimeSynchronizedToUTC(data []byte) bool { - return ((data[2] & 0x08) >> 3) != 0 -} diff --git a/protocol/adsb/fields/bds09.go b/protocol/adsb/fields/bds09.go deleted file mode 100644 index 5b7c60b..0000000 --- a/protocol/adsb/fields/bds09.go +++ /dev/null @@ -1,208 +0,0 @@ -package fields - -// NumericValueStatus is the status of the numeric values, such as air speed, velocity, etc. -type NumericValueStatus byte - -const ( - NVSNoInformation NumericValueStatus = iota // No velocity information - NVSRegular // Velocity is computed on the linear scale value of field * factor - NVSMaximum // Velocity field value indicates velocity greater the maximum of the scale -) - -// ParseHeightDifference reads the Height difference from a 56 bits data field -func ParseHeightDifference(data []byte) (int16, NumericValueStatus) { - negative := data[6]&0x80 != 0 - difference := int16(data[6] & 0x7F) - - if difference == 0 { - return 0, NVSNoInformation - } else if difference >= 127 { - if negative { - return -3150, NVSMaximum - } else { - return 3150, NVSMaximum - } - } - - difference = (difference - 1) * 25 - if negative { - difference = -difference - } - - return difference, NVSRegular -} - -// VerticalRateSource is the Source Bit for Vertical Rate definition -// -// Specified in Doc 9871 / Table A-2-9 -type VerticalRateSource byte - -const ( - VerticalRateSourceGNSS VerticalRateSource = iota // GNSS source - VerticalRateSourceBarometric // Barometric source -) - -// DirectionNorthSouth is the Direction Bit NS Velocity definition -// -// Specified in Doc 9871 / Table A-2-9 -type DirectionNorthSouth byte - -func ParseDirectionNorthSouth(data []byte) DirectionNorthSouth { - return DirectionNorthSouth((data[3] & 0x80) >> 7) -} - -func ParseVelocityNorthSouthNormal(data []byte) (velocity uint16, status NumericValueStatus) { - velocity = (uint16(data[3]&0x7F)<<8 | uint16(data[4]&0xE0)) >> 5 - if velocity == 0 { - return 0, NVSNoInformation - } else if velocity >= 1023 { - return 1023, NVSMaximum - } - return velocity - 1, NVSRegular -} - -func ParseVelocityNorthSouthSupersonic(data []byte) (velocity uint16, status NumericValueStatus) { - velocity = (uint16(data[3]&0x7F)<<8 | uint16(data[4]&0xE0)) >> 5 - if velocity == 0 { - return 0, NVSNoInformation - } else if velocity >= 1023 { - return 4088, NVSMaximum - } - return (velocity - 1) * 4, NVSRegular -} - -const ( - DNSNorth DirectionNorthSouth = iota // North - DNSSouth // South -) - -// DirectionEastWest is the Direction Bit EW Velocity definition -// -// Specified in Doc 9871 / Table A-2-9 -type DirectionEastWest byte - -func ParseDirectionEastWest(data []byte) DirectionEastWest { - return DirectionEastWest((data[1] & 0x04) >> 2) -} - -func ParseVelocityEastWestNormal(data []byte) (velocity uint16, status NumericValueStatus) { - velocity = (uint16(data[1]&0x03) | uint16(data[2])) - if velocity == 0 { - return 0, NVSNoInformation - } else if velocity >= 1023 { - return 1023, NVSMaximum - } - return velocity - 1, NVSRegular -} - -func ParseVelocityEastWestSupersonic(data []byte) (velocity uint16, status NumericValueStatus) { - velocity = (uint16(data[1]&0x03) | uint16(data[2])) - if velocity == 0 { - return 0, NVSNoInformation - } else if velocity >= 1023 { - return 4088, NVSMaximum - } - return (velocity - 1) * 4, NVSRegular -} - -const ( - DEWEast DirectionEastWest = iota // East - DEWWest // West -) - -func ParseIntentChange(data []byte) bool { - return (data[1]&0x80)>>7 != 0 -} - -func ParseIFRCapability(data []byte) bool { - return (data[1]&0x40)>>6 != 0 -} - -// NavigationUncertaintyCategory is the Navigation Uncertainty Category definition -// -// Specified in Doc 9871 / Table A-2-9 -type NavigationUncertaintyCategory byte - -const ( - NUCPUnknown NavigationUncertaintyCategory = iota // Unknown - NUCPHorizontalLowerThan10VerticalLowerThan15Point2 // Horizontal < 10m/s and Vertical < 15.2m/s - NUCPHorizontalLowerThan3VerticalLowerThan4Point6 // Horizontal < 3m/s and Vertical < 4.6m/s - NUCPHorizontalLowerThan1VerticalLowerThan1Point5 // Horizontal < 1m/s and Vertical < 1.5m/s - NUCPHorizontalLowerThan0Point3VerticalLowerThan0Point46 // Horizontal < 0.3m/s and Vertical < 0.46m/s -) - -func ParseeNavigationUncertaintyCategory(data []byte) NavigationUncertaintyCategory { - return NavigationUncertaintyCategory((data[1] & 0x38) >> 3) -} - -func ParseMagneticHeading(data []byte) (heading float64, status bool) { - status = (data[1]&0x04)>>2 != 0 - value := uint16(data[1]&0x03)<<8 | uint16(data[2]) - heading = float64(value) * 360 / 1024.0 - return -} - -func ParseAirspeedNormal(data []byte) (speed uint16, status NumericValueStatus) { - velocity := (uint16(data[3]&0x7F)<<8 | uint16(data[4]&0xE0)) >> 5 - if velocity == 0 { - return 0, NVSNoInformation - } else if velocity >= 1023 { - return 1023, NVSMaximum - } - return velocity - 1, NVSRegular -} - -func ParseAirspeedSupersonic(data []byte) (speed uint16, status NumericValueStatus) { - velocity := (uint16(data[3]&0x7F)<<8 | uint16(data[4]&0xE0)) >> 5 - if velocity == 0 { - return 0, NVSNoInformation - } else if velocity >= 1023 { - return 4088, NVSMaximum - } - return (velocity - 1) * 4, NVSRegular -} - -func ParseVerticalRateSource(data []byte) VerticalRateSource { - return VerticalRateSource((data[4] & 0x10) >> 4) -} - -func ParseVerticalRate(data []byte) (rate int16, status NumericValueStatus) { - negative := data[4]&0x08 != 0 - rate = int16(uint16(data[4]&0x07)<<8 | uint16(data[5]&0xFC)) - if rate == 0 { - return 0, NVSNoInformation - } else if rate >= 511 { - if negative { - return -32640, NVSMaximum - } - return 32640, NVSMaximum - } - - rate = (rate - 1) * 64 - if negative { - rate = -rate - } - return rate, NVSRegular -} - -func ParseHeightDifferenceFromBaro(data []byte) (difference int16, status NumericValueStatus) { - negative := data[6]&0x80 != 0 - difference = int16(data[6] & 0x7F) - - if difference == 0 { - return 0, NVSNoInformation - } else if difference >= 127 { - if negative { - return -3150, NVSMaximum - } else { - return 3150, NVSMaximum - } - } - - difference = (difference - 1) * 25 - if negative { - difference = -difference - } - - return difference, NVSRegular -} diff --git a/protocol/adsb/fields/bds61.go b/protocol/adsb/fields/bds61.go deleted file mode 100644 index d46872b..0000000 --- a/protocol/adsb/fields/bds61.go +++ /dev/null @@ -1,21 +0,0 @@ -package fields - -// EmergencyPriorityStatus is the Emergency Priority Status definition -// -// Specified in Doc 9871 / Table B-2-97a -type EmergencyPriorityStatus byte - -const ( - EPSNoEmergency EmergencyPriorityStatus = iota // No emergency - EPSGeneralEmergency // General emergency - EPSLifeguardMedical // Lifeguard/medical emergency - EPSMinimumFuel // Minimum fuel - EPSNoCommunication // No communications - EPSUnlawfulInterference // Unlawful interference - EPSDownedAircraft // Downed aircraft - EPSReserved7 // Reserved -) - -func ParseEmergencyPriorityStatus(data []byte) EmergencyPriorityStatus { - return EmergencyPriorityStatus((data[1] & 0xE0) >> 5) -} diff --git a/protocol/adsb/fields/bds62.go b/protocol/adsb/fields/bds62.go deleted file mode 100644 index 375e800..0000000 --- a/protocol/adsb/fields/bds62.go +++ /dev/null @@ -1,176 +0,0 @@ -package fields - -// VerticalDataAvailableSourceIndicator is the Vertical Data Available / Source Indicator definition -// -// Specified in Doc 9871 / B.2.3.9.3 -type VerticalDataAvailableSourceIndicator byte - -const ( - VDANoValidDataAvailable VerticalDataAvailableSourceIndicator = iota // No data available - VDAAutopilot // Autopilot control panel selected value, such as Mode Control Panel (MCP) or Flight Control Unit (FCU) - VDAHoldingAltitude // Holding altitude - VDAFMS // FMS/RNAV system -) - -func ParseVerticalDataAvailableSourceIndicator(data []byte) VerticalDataAvailableSourceIndicator { - return VerticalDataAvailableSourceIndicator((data[0]&0x01)<<1 + (data[1]&0x80)>>7) -} - -// TargetAltitudeType is the Target Altitude Type definition -// -// Specified in Doc 9871 / B.2.3.9.4 -type TargetAltitudeType byte - -const ( - TATReferencedToPressureAltitude TargetAltitudeType = iota // Referenced to pressure-altitude (flight level) - TATReferencedToBarometricAltitude // Referenced to barometric corrected altitude (mean sea level) -) - -// ReadTargetAltitudeType reads the TargetAltitudeType from a 56 bits data field -func ParseTargetAltitudeType(data []byte) TargetAltitudeType { - return TargetAltitudeType((data[1] & 0x40) >> 6) -} - -// TargetAltitudeCapability is the Target Altitude Capability definition -// -// Specified in Doc 9871 / B.2.3.9.5 -type TargetAltitudeCapability byte - -const ( - TACAltitudeOnly TargetAltitudeCapability = iota // Holding altitude only - TACAltitudeOrAutopilot // Either holding altitude or autopilot control panel selected altitude - TACAltitudeOrAutopilotOrFMS // Either holding altitude, autopilot control panel selected altitude, or any FMS/RNAV level-off altitude - TACReserved3 // Reserved -) - -// ParseTargetAltitudeCapability reads the TargetAltitudeCapability from a 56 bits data field -func ParseTargetAltitudeCapability(data []byte) TargetAltitudeCapability { - return TargetAltitudeCapability((data[1] & 0x18) >> 3) -} - -// VerticalModeIndicator is the Vertical Mode Indicator definition -// -// Specified in Doc 9871 / B.2.3.9.6 -type VerticalModeIndicator byte - -const ( - VMIUnknown VerticalModeIndicator = iota // Unknown mode or information unavailable - VMIAcquiringMode // Acquiring Mode - VMICapturingMode // Capturing or Maintaining Mode - VMIReserved3 // Reserved -) - -// ParseVerticalModeIndicator reads the VerticalModeIndicator from a 56 bits data field -func ParseVerticalModeIndicator(data []byte) VerticalModeIndicator { - return VerticalModeIndicator((data[1] & 0x06) >> 1) -} - -// HorizontalDataAvailableSourceIndicator is the Horizontal Data Available / Source Indicator definition -// -// Specified in Doc 9871 / B.2.3.9.8 -type HorizontalDataAvailableSourceIndicator byte - -const ( - HDANoValidDataAvailable HorizontalDataAvailableSourceIndicator = iota // No data available - HDAAutopilot // Autopilot control panel selected value, such as Mode Control Panel (MCP) or Flight Control Unit (FCU) - HDAHoldingAltitude // Maintaining current heading or track angle (e.g. autopilot mode select) - HDAFMS // FMS/RNAV system (indicates track angle specified by leg type) -) - -// ParseHorizontalDataAvailableSourceIndicator reads the HorizontalDataAvailableSourceIndicator from a 56 bits data field -func ParseHorizontalDataAvailableSourceIndicator(data []byte) HorizontalDataAvailableSourceIndicator { - return HorizontalDataAvailableSourceIndicator((data[3] & 0x60) >> 5) -} - -// TargetHeadingTrackIndicator is the Target Heading / Track Angle Indicator definition -// -// Specified in Doc 9871 / B.2.3.9.10 -type TargetHeadingTrackIndicator byte - -const ( - TargetTrackHeadingAngle TargetHeadingTrackIndicator = iota // Target heading angle is being reported - TargetTrackAngle // Track angle is being reported - -) - -// ParseTargetHeadingTrackIndicator reads the TargetHeadingTrackIndicator from a 56 bits data field -func ParseTargetHeadingTrackIndicator(data []byte) TargetHeadingTrackIndicator { - return TargetHeadingTrackIndicator((data[4] & 0x08) >> 3) -} - -// ReadTargetHeadingTrackAngle reads the TargetAltitude from a 56 bits data field -// Specified in Doc 9871 / B.2.3.9.9 -func ReadTargetHeadingTrackAngle(data []byte) (uint16, NumericValueStatus) { - heading := (uint16(data[3]&0x1F)<<8 | uint16(data[2]&0xF0)) >> 4 - if heading > 359 { - return 0, NVSMaximum - } - return heading, NVSRegular -} - -// HorizontalModeIndicator is the Horizontal Mode Indicator definition -// -// Specified in Doc 9871 / B.2.3.9.11 -type HorizontalModeIndicator byte - -const ( - HMIUnknown HorizontalModeIndicator = iota // Unknown mode or information unavailable - HMIAcquiringMode // Acquiring Mode - HMICapturingMode // Capturing or Maintaining Mode - HMIReserved3 // Reserved -) - -// ParseHorizontalModeIndicator reads the HorizontalModeIndicator from a 56 bits data field -func ParseHorizontalModeIndicator(data []byte) HorizontalModeIndicator { - return HorizontalModeIndicator((data[4] & 0x06) >> 1) -} - -// NavigationalAccuracyCategoryPositionV1 is the Navigational Accuracy Category Position definition -// -// Specified in Doc 9871 / B.2.3.9.12 -type NavigationalAccuracyCategoryPositionV1 byte - -const ( - NACPV1EPUGreaterThan18Point52Km NavigationalAccuracyCategoryPositionV1 = iota // EPU >= 18.52 km (10 NM) - Unknown accuracy - NACPV1EPULowerThan18Point52Km // EPU < 18.52 km (10 NM) - RNP-10 accuracy - NACPV1EPULowerThan7Point408Km // EPU < 7.408 km (4 NM) - RNP-4 accuracy - NACPV1EPULowerThan3Point704Km // EPU < 3.704 km (2 NM) - RNP-2 accuracy - NACPV1EPUGreaterThan1852M // EPU < 1 852 m (1 NM) - RNP-1 accuracy - NACPV1EPULowerThan926M // EPU < 926 m (0.5 NM) - RNP-0.5 accuracy - NACPV1EPUGreaterThan555Point6M // EPU < 555.6 m ( 0.3 NM) - RNP-0.3 accuracy - NACPV1EPULowerThan185Point2M // EPU < 185.2 m (0.1 NM) - RNP-0.1 accuracy - NACPV1EPUGreaterThan92Point6M // EPU < 92.6 m (0.05 NM) - e.g. GPS (with SA) - NACPV1EPULowerThan30MAndVEPULowerThan45M // EPU < 30 m and VEPU < 45 m - e.g. GPS (SA off) - NACPV1EPULowerThan10MAndVEPULowerThan15M // EPU < 10 m and VEPU < 15 m - e.g. WAAS - NACPV1EPULowerThan4MAndVEPULowerThan3M // EPU < 3 m and VEPU < 4 m - e.g. LAAS - NACPV1Reserved12 // Reserved - NACPV1Reserved13 // Reserved - NACPV1Reserved14 // Reserved - NACPV1Reserved15 // Reserved -) - -// ParseNavigationalAccuracyCategoryPositionV1 reads the NavigationalAccuracyCategoryPositionV1 from a 56 bits data field -func ParseNavigationalAccuracyCategoryPositionV1(data []byte) NavigationalAccuracyCategoryPositionV1 { - return NavigationalAccuracyCategoryPositionV1((data[4]&0x01)<<3 + (data[5]&0xE0)>>5) -} - -// NICBaro is the NIC Baro definition -// -// Specified in Doc 9871 / B.2.3.9.13 -type NICBaro byte - -const ( - // NICBGilhamNotCrossChecked indicates that the barometric altitude that is being reported in the Airborne - // Position Message is based on a Gilham coded input that has not been cross-checked against another source of - // pressure-altitude - NICBGilhamNotCrossChecked NICBaro = iota - // NICBGilhamCrossCheckedOrNonGilham indicates that the barometric altitude that is being reported in the Airborne - // Position Message is either based on a Gilham code input that has been cross-checked against another source of - // pressure-altitude and verified as being consistent, or is based on a non-Gilham coded source - NICBGilhamCrossCheckedOrNonGilham -) - -// ParseNICBaro reads the NICBaro from a 56 bits data field -func ParseNICBaro(data []byte) NICBaro { - return NICBaro((data[5] & 0x10) >> 4) -} diff --git a/protocol/adsb/fields/util.go b/protocol/adsb/fields/util.go deleted file mode 100644 index fb77907..0000000 --- a/protocol/adsb/fields/util.go +++ /dev/null @@ -1,78 +0,0 @@ -package fields - -import "errors" - -// GillhamToAltitude convert an altitude given in Gillham bits to an altitude in feet. -func GillhamToAltitude(d1, d2, d4, a1, a2, a4, b1, b2, b4, c1, c2, c4 bool) (int32, error) { - fiveHundredBits := uint16(0) - if d1 { - fiveHundredBits |= 0x0100 - } - if d2 { - fiveHundredBits |= 0x0080 - } - if d4 { - fiveHundredBits |= 0x0040 - } - if a1 { - fiveHundredBits |= 0x0020 - } - if a2 { - fiveHundredBits |= 0x0010 - } - if a4 { - fiveHundredBits |= 0x0008 - } - if b1 { - fiveHundredBits |= 0x0004 - } - if b2 { - fiveHundredBits |= 0x0002 - } - if b4 { - fiveHundredBits |= 0x0001 - } - - oneHundredBits := uint16(0) - if c1 { - oneHundredBits |= 0x0004 - } - if c2 { - oneHundredBits |= 0x0002 - } - if c4 { - oneHundredBits |= 0x0001 - } - - oneHundred := int32(grayToBinary(oneHundredBits)) - fiveHundred := int32(grayToBinary(fiveHundredBits)) - - // Check for invalid codes. - if oneHundred == 5 || oneHundred == 6 || oneHundred == 0 { - return 0, errors.New("the bits C1 to to C4 are incorrect") - } - - // Remove 7s from OneHundreds. - if oneHundred == 7 { - oneHundred = 5 - } - - // Correct order of OneHundreds. - if fiveHundred%2 != 0 { - oneHundred = 6 - oneHundred - } - - // Convert to feet and apply altitude datum offset. - return (int32(fiveHundred)*500 + int32(oneHundred)*100) - 1300, nil -} - -func grayToBinary(num uint16) uint16 { - temp := uint16(0) - - temp = num ^ (num >> 8) - temp ^= temp >> 4 - temp ^= temp >> 2 - temp ^= temp >> 1 - - return temp -} diff --git a/protocol/adsb/fields/util_test.go b/protocol/adsb/fields/util_test.go deleted file mode 100644 index f2f8229..0000000 --- a/protocol/adsb/fields/util_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package fields - -import "testing" - -func TestGillhamToAltitude(t *testing.T) { - - tests := []struct { - Test string - Want int32 - }{ - {"00000000010", -1000}, - {"00000001010", -500}, - {"00000011010", 0}, - {"00000011110", 100}, - {"00000010011", 600}, - {"00000110010", 1000}, - {"00001001001", 5800}, - {"00011100100", 10300}, - {"01100011010", 32000}, - {"01110000100", 46300}, - {"10000000011", 126600}, - {"10000000001", 126700}, - } - - for _, test := range tests { - t.Run(test.Test, func(t *testing.T) { - alt, err := GillhamToAltitude(testString2Bits(test.Test)) - if err != nil { - t.Fatalf("%v", err) - } - if alt != test.Want { - t.Errorf("expected altitude %d, got %d", test.Want, alt) - } - }) - } -} - -func testString2Bits(s string) (bool, bool, bool, bool, bool, bool, bool, bool, bool, bool, bool, bool) { - d2 := s[0] == '1' - d4 := s[1] == '1' - a1 := s[2] == '1' - a2 := s[3] == '1' - a4 := s[4] == '1' - b1 := s[5] == '1' - b2 := s[6] == '1' - b4 := s[7] == '1' - c1 := s[8] == '1' - c2 := s[9] == '1' - c4 := s[10] == '1' - - return false, d2, d4, a1, a2, a4, b1, b2, b4, c1, c2, c4 -} diff --git a/protocol/adsb/message.go b/protocol/adsb/message.go deleted file mode 100644 index f3b8a97..0000000 --- a/protocol/adsb/message.go +++ /dev/null @@ -1,527 +0,0 @@ -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', '#', '#', '#', '#', '#', '#', -} diff --git a/protocol/adsb/ra.go b/protocol/adsb/ra.go deleted file mode 100644 index ad5baef..0000000 --- a/protocol/adsb/ra.go +++ /dev/null @@ -1,137 +0,0 @@ -package adsb - -import ( - "encoding/binary" - "errors" -) - -// ResolutionAdvisory is an ACAS message providing information about ResolutionAdvisory -// -// Defined at 3.1.2.8.3.1 and 4.3.8.4.2.4 -type ResolutionAdvisory struct { - ActiveRA ActiveResolutionAdvisory - RAComplement RAComplement - RATerminatedIndicator RATerminatedIndicator - MultipleThreatEncounter MultipleThreatEncounter - ThreatTypeIndicator ThreatTypeIndicator - ThreatIdentityAddress *ThreatIdentityAddress - ThreatIdentityAltitude *ThreatIdentityAltitude - ThreatIdentityRange *ThreatIdentityRange - ThreatIdentityBearing *ThreatIdentityBearing -} - -// ThreatTypeIndicator indicates the type of information contained in the TID part of the message -type ThreatTypeIndicator int - -// ThreatIdentityAddress is a 3 bytes ICAO Address. The Most Significant Byte of the address -// is always 0. -type ThreatIdentityAddress uint32 - -const ( - ThreatTypeNoIdentity ThreatTypeIndicator = iota // No identity data in TID - ThreatTypeModeS // Contains a Mode S transponder address - ThreatTypeAltitudeRangeBearing // Contains altitude, range and bearing data - ThreatTypeReserved3 // Reserved -) - -// ThreatIdentityAltitude is the altitude of the threat. It is given in 100 feet increment -type ThreatIdentityAltitude struct { - AltitudeValid bool - AltitudeInFeet int32 -} - -func parseThreatTypeIndicator(data []byte) ThreatTypeIndicator { - return ThreatTypeIndicator((data[2] & 0x0C) >> 2) -} - -// parseThreatIdentityAltitude reads the altitude code from a message -func parseThreatIdentityAltitude(data []byte) (ThreatIdentityAltitude, error) { - - // Altitude code is a 13 bits fields, so read a uint16 - // byte data[2] | data[3] | data[4] - // bit 19 20 21 22 23|24 25 26 27 28 29 30 31|32 33 34 35 36 - // value _ _ _ C1 A1|C2 A2 C4 A4 0 B1 D1 B2|D2 B4 D4 _ _ - - // Start by D2 B4 D4 - altitudeCode := uint16(data[4]&0xE0) >> 5 - // Then pack B1 D1 B2 - altitudeCode += uint16(data[3]&0x07) << 3 - // Then C2 A2 C4 A4 - altitudeCode += uint16(data[3]&0xF0) << 2 - // Then C1 A1 - altitudeCode += uint16(data[2]&0x03) << 2 - - // Detect invalid altitude - if altitudeCode == 0 { - return ThreatIdentityAltitude{}, nil - } - - c1 := (altitudeCode & 0x0800) != 0 - a1 := (altitudeCode & 0x0400) != 0 - c2 := (altitudeCode & 0x0200) != 0 - a2 := (altitudeCode & 0x0100) != 0 - c4 := (altitudeCode & 0x0080) != 0 - a4 := (altitudeCode & 0x0040) != 0 - b1 := (altitudeCode & 0x0020) != 0 - d1 := (altitudeCode & 0x0010) != 0 - b2 := (altitudeCode & 0x0008) != 0 - d2 := (altitudeCode & 0x0004) != 0 - b4 := (altitudeCode & 0x0002) != 0 - d4 := (altitudeCode & 0x0001) != 0 - - altitudeFeet, err := gillhamToAltitude(d1, d2, d4, a1, a2, a4, b1, b2, b4, c1, c2, c4) - if err != nil { - return ThreatIdentityAltitude{}, errors.New("adsb: the altitude field is malformed") - } - - return ThreatIdentityAltitude{true, altitudeFeet}, nil -} - -// ThreatIdentityRange is TIDR (threat identity data range subfield). This 7-bit subfield (76-82) shall contain the -// most recent threat range estimated by ACAS. -type ThreatIdentityRange byte - -func parseThreatIdentityRange(data []byte) ThreatIdentityRange { - return ThreatIdentityRange((data[4]&0x1F)<<2 + (data[5]&0xA0)>>6) -} - -// ThreatIdentityBearing is TIDR (threat identity data bearing subfield). This 6-bit subfield (83-88) shall contain the -// most recent estimated bearing of the threat aircraft, relative to the ACAS aircraft heading. -type ThreatIdentityBearing byte - -func parseThreatIdentityBearing(data []byte) ThreatIdentityBearing { - return ThreatIdentityBearing(data[5] & 0x3F) -} - -func parseResolutionAdvisory(data []byte) (*ResolutionAdvisory, error) { - if len(data) != 6 { - return nil, errors.New("adsb: data for ACAS ResolutionAdvisory must be 6 bytes long") - } - - // Format of the message is as follows: - // 0 1 2 3 4 5 - // | RAC | R R M T TID | TID | TID | TID | - // ARA | ARA RAC | A A T T d d | d d d d d d d d | d d d d d d d d | d d d d d d _ _ | - // | RAC | C T E I TIDA| TIDA | TIDA TIDR |TIDR TIDB | - // a a a a a a a a | a a a a a a c c | c c t m i i a a | a a a a a a a a | a a a r r r r r | r r b b b b b b | - - ra := new(ResolutionAdvisory) - switch ra.ThreatTypeIndicator = parseThreatTypeIndicator(data); ra.ThreatTypeIndicator { - case ThreatTypeModeS: - addr := ThreatIdentityAddress((binary.BigEndian.Uint32(data[2:]) >> 2) & 0xFFFFFF) - ra.ThreatIdentityAddress = &addr - - case ThreatTypeAltitudeRangeBearing: - altitude, err := parseThreatIdentityAltitude(data) - if err != nil { - return nil, err - } - ra.ThreatIdentityAltitude = &altitude - - threatRange := parseThreatIdentityRange(data) - ra.ThreatIdentityRange = &threatRange - - bearing := parseThreatIdentityBearing(data) - ra.ThreatIdentityBearing = &bearing - } -} diff --git a/protocol/meshtastic/node.go b/protocol/meshtastic/node.go deleted file mode 100644 index 149a3c6..0000000 --- a/protocol/meshtastic/node.go +++ /dev/null @@ -1,173 +0,0 @@ -package meshtastic - -import ( - "crypto/rand" - "encoding/binary" - "fmt" - "math" - "math/big" - "strconv" - "strings" -) - -type NodeID uint32 - -const ( - // BroadcastNodeID is the special NodeID used when broadcasting a packet to a channel. - BroadcastNodeID NodeID = math.MaxUint32 - - // BroadcastNodeIDNoLora is a special broadcast address that excludes LoRa transmission. - // Used for MQTT-only broadcasts. This is ^all with the NO_LORA flag (0x40) cleared. - BroadcastNodeIDNoLora NodeID = math.MaxUint32 ^ 0x40 - - // ReservedNodeIDThreshold is the threshold at which NodeIDs are considered reserved. Random NodeIDs should not - // be generated below this threshold. - // Source: https://github.com/meshtastic/firmware/blob/d1ea58975755e146457a8345065e4ca357555275/src/mesh/NodeDB.cpp#L461 - reservedNodeIDThreshold NodeID = 4 -) - -// ParseNodeID parses a NodeID from various string formats: -// - "!abcd1234" (Meshtastic format with ! prefix) -// - "0xabcd1234" (hex with 0x prefix) -// - "abcd1234" (plain hex) -// - "12345678" (decimal) -func ParseNodeID(s string) (NodeID, error) { - s = strings.TrimSpace(s) - if s == "" { - return 0, fmt.Errorf("empty node ID string") - } - - // Handle !prefix format - if strings.HasPrefix(s, "!") { - s = s[1:] - n, err := strconv.ParseUint(s, 16, 32) - if err != nil { - return 0, fmt.Errorf("invalid node ID %q: %w", s, err) - } - return NodeID(n), nil - } - - // Handle 0x prefix - if strings.HasPrefix(s, "0x") || strings.HasPrefix(s, "0X") { - n, err := strconv.ParseUint(s[2:], 16, 32) - if err != nil { - return 0, fmt.Errorf("invalid node ID %q: %w", s, err) - } - return NodeID(n), nil - } - - // Try hex first if it looks like hex (contains a-f) - sLower := strings.ToLower(s) - if strings.ContainsAny(sLower, "abcdef") { - n, err := strconv.ParseUint(s, 16, 32) - if err != nil { - return 0, fmt.Errorf("invalid node ID %q: %w", s, err) - } - return NodeID(n), nil - } - - // Try decimal - n, err := strconv.ParseUint(s, 10, 32) - if err != nil { - // Fall back to hex for 8-char strings - if len(s) == 8 { - n, err = strconv.ParseUint(s, 16, 32) - if err != nil { - return 0, fmt.Errorf("invalid node ID %q: %w", s, err) - } - return NodeID(n), nil - } - return 0, fmt.Errorf("invalid node ID %q: %w", s, err) - } - return NodeID(n), nil -} - -// Uint32 returns the underlying uint32 value of the NodeID. -func (n NodeID) Uint32() uint32 { - return uint32(n) -} - -// String converts the NodeID to a hex formatted string. -// This is typically how NodeIDs are displayed in Meshtastic UIs. -func (n NodeID) String() string { - return fmt.Sprintf("!%08x", uint32(n)) -} - -// Bytes converts the NodeID to a byte slice -func (n NodeID) Bytes() []byte { - bytes := make([]byte, 4) // uint32 is 4 bytes - binary.BigEndian.PutUint32(bytes, n.Uint32()) - return bytes -} - -// DefaultLongName returns the default long node name based on the NodeID. -// Source: https://github.com/meshtastic/firmware/blob/d1ea58975755e146457a8345065e4ca357555275/src/mesh/NodeDB.cpp#L382 -func (n NodeID) DefaultLongName() string { - bytes := make([]byte, 4) // uint32 is 4 bytes - binary.BigEndian.PutUint32(bytes, n.Uint32()) - return fmt.Sprintf("Meshtastic %04x", bytes[2:]) -} - -// DefaultShortName returns the default short node name based on the NodeID. -// Last two bytes of the NodeID represented in hex. -// Source: https://github.com/meshtastic/firmware/blob/d1ea58975755e146457a8345065e4ca357555275/src/mesh/NodeDB.cpp#L382 -func (n NodeID) DefaultShortName() string { - bytes := make([]byte, 4) // uint32 is 4 bytes - binary.BigEndian.PutUint32(bytes, n.Uint32()) - return fmt.Sprintf("%04x", bytes[2:]) -} - -// UnmarshalText implements encoding.TextUnmarshaler for use with config parsers like Viper. -func (n *NodeID) UnmarshalText(text []byte) error { - parsed, err := ParseNodeID(string(text)) - if err != nil { - return err - } - *n = parsed - return nil -} - -// MarshalText implements encoding.TextMarshaler. -func (n NodeID) MarshalText() ([]byte, error) { - return []byte(n.String()), nil -} - -// IsReservedID returns true if this is a reserved or broadcast NodeID. -func (n NodeID) IsReservedID() bool { - return n < reservedNodeIDThreshold || n >= BroadcastNodeIDNoLora -} - -// IsBroadcast returns true if this is any form of broadcast address. -func (n NodeID) IsBroadcast() bool { - return n == BroadcastNodeID || n == BroadcastNodeIDNoLora -} - -// ToMacAddress returns a MAC address string derived from the NodeID. -// This creates a locally administered unicast MAC address. -func (n NodeID) ToMacAddress() string { - bytes := n.Bytes() - // Use 0x02 as the first octet (locally administered, unicast) - // Then 0x00 as padding, followed by the 4 bytes of the NodeID - return fmt.Sprintf("02:00:%02x:%02x:%02x:%02x", bytes[0], bytes[1], bytes[2], bytes[3]) -} - -// RandomNodeID returns a randomised NodeID. -// It's recommended to call this the first time a node is started and persist the result. -// -// Hardware meshtastic nodes first try a NodeID of the last four bytes of the BLE MAC address. If that ID is already in -// use or invalid, a random NodeID is generated. -// Source: https://github.com/meshtastic/firmware/blob/d1ea58975755e146457a8345065e4ca357555275/src/mesh/NodeDB.cpp#L466 -func RandomNodeID() (NodeID, error) { - // Generates a random uint32 between reservedNodeIDThreshold and math.MaxUint32 - randomInt, err := rand.Int( - rand.Reader, - big.NewInt( - int64(math.MaxUint32-reservedNodeIDThreshold.Uint32()), - ), - ) - if err != nil { - return NodeID(0), fmt.Errorf("reading entropy: %w", err) - } - r := uint32(randomInt.Uint64()) + reservedNodeIDThreshold.Uint32() - return NodeID(r), nil -} diff --git a/protocol/meshtastic/packet.go b/protocol/meshtastic/packet.go deleted file mode 100644 index b6d6a3f..0000000 --- a/protocol/meshtastic/packet.go +++ /dev/null @@ -1,54 +0,0 @@ -package meshtastic - -import ( - "encoding/binary" - "errors" -) - -const ( - minPacketSize = 4 + 4 + 4 + 1 + 1 + 1 + 1 - maxPayloadSize = 237 -) - -var ( - // ErrInvalidPacket signals the source buffer does not contain a valid packet. - ErrInvalidPacket = errors.New("meshtastic: invalid packet") -) - -type Packet struct { - Destination NodeID - Source NodeID - ID uint32 - Flags uint8 - ChannelHash uint8 - NextHop uint8 - RelayNode uint8 - PayloadLength int - Payload [maxPayloadSize]byte -} - -func (packet *Packet) Decode(data []byte) error { - if len(data) < minPacketSize { - return ErrInvalidPacket - } - - packet.Destination = parseNodeID(data[0:]) - packet.Source = parseNodeID(data[4:]) - packet.ID = binary.LittleEndian.Uint32(data[8:]) - packet.Flags = data[12] - packet.ChannelHash = data[13] - packet.NextHop = data[14] - packet.RelayNode = data[15] - packet.PayloadLength = len(data[16:]) - copy(packet.Payload[:], data[16:]) - - return nil -} - -func (packet *Packet) HopLimit() int { - return -} - -func parseNodeID(data []byte) NodeID { - return NodeID(binary.LittleEndian.Uint32(data)) -}