138 lines
5.0 KiB
Go
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
|
|
}
|
|
}
|