Files
ham/protocol/adsb/ra.go
maze 32f6c38c13
Some checks failed
Run tests / test (1.25) (push) Failing after 15s
Run tests / test (stable) (push) Failing after 17s
Fixed code smells
2026-02-22 21:14:58 +01:00

138 lines
5.0 KiB
Go

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