rs1090 0.5.2

Rust library to decode Mode S and ADS-B signals
Documentation
use deku::prelude::*;
use serde::{Deserialize, Serialize};

use crate::decode::{AC13Field, ICAO};

/**
 * ## ACAS Active Resolution Advisory (BDS 3,0)
 *
 * Comm-B message reporting ACAS/TCAS resolution advisories.  
 * Per ICAO Doc 9871 Table A-2-48: BDS code 3,0 — ACAS active resolution advisory
 *
 * Purpose: To report resolution advisories (RAs) generated by ACAS equipment
 * to ground systems and other aircraft.
 *
 * Message Structure (56 bits):
 * | BDS | ARA  | RAC | TERM | MTI | TTI | TID      |
 * |-----|------|-----|------|-----|-----|----------|
 * | 8   | 14   | 4   | 1    | 1   | 2   | 26       |
 *
 * Field Encoding per ICAO Doc 9871 & Annex 10, Vol IV, §4.3.8.4.2.2:
 *
 * **BDS Code** (bits 1-8): Fixed value 0x30 (0011 0000 binary = 3,0 hex)
 *
 * **Active Resolution Advisories (ARA)** (bits 9-22): 14-bit RA field
 *   - Bit 9: Active RA issued (0=no RA, 1=RA active)
 *   - Bit 10: Corrective (1) vs. Preventive (0) RA
 *   - Bit 11: Downward (1) vs. Upward (0) sense
 *   - Bit 12: Increased rate RA
 *   - Bit 13: Sense reversal RA
 *   - Bit 14: Altitude crossing RA
 *   - Bit 15: Positive RA
 *   - Bit 16: Corrective RA
 *   - Bits 17-22: Additional RA complement bits
 *   - False if no RA, multiple threats, or different directions
 *
 * **RAC Record** (bits 23-26): 4-bit Resolution Advisory Complement
 *   - Records additional RA information
 *   - Validity depends on ARA bit 9
 *
 * **RA Terminated** (bit 27):
 *   - 0 = RA active
 *   - 1 = RA terminated
 *
 * **Multiple Threat Encounter (MTE)** (bit 28):
 *   - 0 = single threat
 *   - 1 = multiple threat encounter
 *
 * **Threat-Type Indicator (TTI)** (bits 29-30): 2-bit threat type
 *   - Indicates type of threatening aircraft
 *
 * **Threat Identity Data (TID)** (bits 31-56): 26-bit threat identification
 *   - Contains Mode S address or other identifier of threatening aircraft
 *   - Format depends on threat type
 *
 * Coding Rules per ICAO Annex 10, Vol IV, §4.3.8.4.2.2:
 * - Register coding conforms to ACAS MOPS (RTCA DO-185/EUROCAE ED-143)
 * - Bit 27 set to 1 indicates RA has terminated
 * - Multiple bits in ARA field can be set simultaneously
 * - Only valid when ACAS is generating RAs (not just TAs)
 *
 * Note: This register is used for ACAS Resolution Advisory reporting to
 * provide situational awareness to ATC and nearby aircraft. It is separate
 * from the Extended Squitter ACAS RA Broadcast (BDS 6,1 Subtype 2).
 *
 * Reference: ICAO Doc 9871 Table A-2-48, Annex 10 Vol IV §4.3.8.4.2.2  
 * Additional details: RTCA DO-185B, EUROCAE ED-143
 */

#[derive(Debug, PartialEq, Serialize, Deserialize, DekuRead, Clone)]
#[serde(tag = "bds", rename = "30")]
pub struct ACASResolutionAdvisory {
    #[deku(bits = "8", map = "fail_if_not30")]
    #[serde(skip)]
    /// The first eight bits indicate the BDS code 0011 0000 (3,0 in hexadecimal).
    pub bds: u8,

    #[deku(bits = "1")]
    /// Active resolution advisories.
    /// False if no RA or multiple thread/different directions.
    pub issued_ra: bool,

    #[deku(
        bits = "1",
        map = "|v: bool| -> Result<_, DekuError> {
            if *issued_ra { Ok(Some(v)) } else { Ok(None) }
        }"
    )]
    #[serde(skip_serializing_if = "Option::is_none")]
    /// Active resolution advisories: corrective/preventive
    pub corrective: Option<bool>,

    #[deku(
        bits = "1",
        map = "|v: bool| -> Result<_, DekuError> {
            if *issued_ra { Ok(Some(v)) } else { Ok(None) }
        }"
    )]
    #[serde(skip_serializing_if = "Option::is_none")]
    /// Active resolution advisories: downward/upward
    pub downward_sense: Option<bool>,

    #[deku(
        bits = "1",
        map = "|v: bool| -> Result<_, DekuError> {
            if *issued_ra { Ok(Some(v)) } else { Ok(None) }
        }"
    )]
    #[serde(skip_serializing_if = "Option::is_none")]
    /// Active resolution advisories:
    pub increased_rate: Option<bool>,

    #[deku(
        bits = "1",
        map = "|v: bool| -> Result<_, DekuError> {
            if *issued_ra { Ok(Some(v)) } else { Ok(None) }
        }"
    )]
    #[serde(skip_serializing_if = "Option::is_none")]
    /// Active resolution advisories:
    pub sense_reversal: Option<bool>,

    #[deku(
        bits = "1",
        map = "|v: bool| -> Result<_, DekuError> {
            if *issued_ra { Ok(Some(v)) } else { Ok(None) }
        }"
    )]
    #[serde(skip_serializing_if = "Option::is_none")]
    /// Active resolution advisories:
    pub altitude_crossing: Option<bool>,

    #[deku(
        bits = "1",
        map = "|v: bool| -> Result<_, DekuError> {
            if *issued_ra { Ok(Some(v)) } else { Ok(None) }
        }"
    )]
    #[serde(skip_serializing_if = "Option::is_none")]
    /// Active resolution advisories: positive/vertical speed limit
    pub positive: Option<bool>,

    #[deku(bits = "7")]
    #[serde(skip)]
    /// Active resolution advisories: reserved for ACAS III
    pub reserved_acas3: u16,

    #[deku(
        bits = "1",
        map = "|v: bool| -> Result<_, DekuError> {
            if *issued_ra { Ok(Some(v)) } else { Ok(None) }
        }"
    )]
    #[serde(skip_serializing_if = "Option::is_none")]
    /// Resolution advisory complements record: do not pass below
    pub no_below: Option<bool>,

    #[deku(
        bits = "1",
        map = "|v: bool| -> Result<_, DekuError> {
            if *issued_ra { Ok(Some(v)) } else { Ok(None) }
        }"
    )]
    #[serde(skip_serializing_if = "Option::is_none")]
    /// Resolution advisory complements record: do not pass above
    pub no_above: Option<bool>,

    #[deku(
        bits = "1",
        map = "|v: bool| -> Result<_, DekuError> {
            if *issued_ra { Ok(Some(v)) } else { Ok(None) }
        }"
    )]
    #[serde(skip_serializing_if = "Option::is_none")]
    /// Resolution advisory complements record: do not turn left
    pub no_left: Option<bool>,

    #[deku(
        bits = "1",
        map = "|v: bool| -> Result<_, DekuError> {
            if *issued_ra { Ok(Some(v)) } else { Ok(None) }
        }"
    )]
    #[serde(skip_serializing_if = "Option::is_none")]
    /// Resolution advisory complements record: do not turn right
    pub no_right: Option<bool>,

    #[deku(bits = "1")]
    /// RA terminated
    pub terminated: bool,

    #[deku(bits = "1")]
    /// Multiple threat encounter (not supported)
    pub multiple: bool,

    /// Threat type indicator
    #[serde(flatten)]
    pub threat_type: ThreatType,
}

#[derive(Debug, PartialEq, Serialize, Deserialize, DekuRead, Clone)]
#[deku(id_type = "u8", bits = "2")]
#[serde(untagged)]
pub enum ThreatType {
    #[deku(id = "0")]
    NoIdentity {
        #[deku(bits = "26")]
        #[serde(skip)]
        unused: u32,
    },

    #[deku(id = "1")]
    ThreatAddress(ThreadAddress),

    #[deku(id = "2")]
    ThreatOrientation(ThreatOrientation),

    #[deku(id = "3")]
    NotAssigned {
        #[deku(bits = "26")]
        #[serde(skip)]
        unused: u32,
    },
}

#[derive(Debug, PartialEq, Serialize, Deserialize, DekuRead, Clone)]
pub struct ThreadAddress {
    /// Threat identity data (icao24).
    pub threat_identity: ICAO,

    #[deku(bits = "2")]
    #[serde(skip)]
    pub zeros: u8,
}

#[derive(Debug, PartialEq, Serialize, Deserialize, DekuRead, Clone)]
pub struct ThreatOrientation {
    /// Altitude code on 13 bits
    #[serde(rename = "threat_altitude")]
    altitude: AC13Field,

    #[deku(
        bits = "7",
        map = "|n: u8| -> Result<_, DekuError> {
            if n == 0 { Ok(None) } else { Ok(Some((n as f32 - 1.) / 10.)) }
        }"
    )]
    /// Most recent threat range from ACAS (max 12.55 nautical miles)
    #[serde(rename = "threat_range")]
    range: Option<f32>,

    #[deku(
        bits = "6",
        map = "|n: u16| -> Result<_, DekuError> {
            if n == 0 { Ok(None) } else { Ok(Some(6 * (n - 1) + 3)) }
        }"
    )]
    /// Most recent estimated bearing of the threat aircraft,
    /// relative to their own heading (3 degree precision)
    #[serde(rename = "threat_bearing")]
    bearing: Option<u16>,
}

fn fail_if_not30(value: u8) -> Result<u8, DekuError> {
    if value == 0x30 {
        Ok(value)
    } else {
        Err(DekuError::Assertion(
            "First bits must be 0x30 in BDS 3,0".into(),
        ))
    }
}