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