datalink 0.1.0

Decode aviation datalink traffic from payloads, SDR, files, and Airframes.io
mod airframes;
mod hfdl;
mod iq_pipeline;
mod merged;
mod source;
mod util;
mod vdl2;
mod vhf;

use acars::decode::acars::{parse_acars_frame, MessageDirection};
use acars::decode::avlc::parse_avlc_frame;
use acars::decode::payload::arinc622::adsc::parse_adsc_app_text;
use clap::{Parser, Subcommand, ValueEnum};

#[derive(Debug, Clone, Copy, ValueEnum)]
enum Direction {
    Unknown,
    Uplink,
    Downlink,
}

#[derive(Debug, Parser)]
#[command(name = "datalink", about = "Decode aviation datalink traffic")]
struct Args {
    /// Merged receiver configuration file. Used when no bearer subcommand is provided.
    #[arg(long)]
    config: Option<String>,

    #[command(subcommand)]
    command: Option<Command>,
}

#[derive(Debug, Subcommand)]
enum Command {
    /// VDL Mode 2 frontend for I/Q and SDR inputs.
    Vdl2(vdl2::Cli),
    /// Classic VHF ACARS frontend.
    #[command(alias = "acars-vhf")]
    Vhf(vhf::Cli),
    /// Airframes.io websocket feed.
    #[command(name = "airframes.io", alias = "airframes")]
    AirframesIo(airframes::Options),
    /// HF Data Link frontend.
    #[command(alias = "hf")]
    Hfdl(hfdl::Options),
    /// Decode standalone payloads or frames.
    Decode {
        #[command(subcommand)]
        command: DecodeCommand,
    },
}

#[derive(Debug, Subcommand)]
enum DecodeCommand {
    /// Decode a hex ACARS frame.
    Acars {
        #[arg(help = "Hex-encoded ACARS frame bytes")]
        hex: String,
        #[arg(short, long, value_enum, default_value_t = Direction::Unknown)]
        direction: Direction,
        /// Include the full nested decoder output under raw_decode
        #[arg(long)]
        raw: bool,
    },
    /// Decode a hex AVLC frame (including 2-byte FCS).
    Avlc {
        #[arg(help = "Hex-encoded AVLC frame bytes (with FCS)")]
        hex: String,
        /// Include the full nested decoder output under raw_decode
        #[arg(long)]
        raw: bool,
    },
    /// Decode an ADS-C application-layer text payload.
    Adsc {
        #[arg(help = "ADS-C app text payload")]
        payload: String,
        /// Include the full nested decoder output under raw_decode
        #[arg(long)]
        raw: bool,
    },
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let args = Args::parse();

    match args.command {
        Some(Command::Vdl2(options)) => vdl2::run(options).await,
        Some(Command::Vhf(options)) => vhf::run(options).await,
        Some(Command::AirframesIo(options)) => airframes::run(options).await,
        Some(Command::Hfdl(options)) => hfdl::run(options).await,
        Some(Command::Decode { command }) => run_decode(command),
        None => merged::run(args.config).await,
    }
}

fn run_decode(command: DecodeCommand) -> anyhow::Result<()> {
    match command {
        DecodeCommand::Acars {
            hex,
            direction,
            raw,
        } => {
            let bytes = hex::decode(hex.trim())?;
            let dir = match direction {
                Direction::Unknown => MessageDirection::Unknown,
                Direction::Uplink => MessageDirection::GroundToAir,
                Direction::Downlink => MessageDirection::AirToGround,
            };
            let message = parse_acars_frame(&bytes, dir)?;
            let raw_value = serde_json::to_value(&message)?;
            let out = acars::decode::compact::compact_acars_value(raw_value, raw);
            println!("{}", serde_json::to_string_pretty(&out)?);
        }
        DecodeCommand::Avlc { hex, raw } => {
            let bytes = hex::decode(hex.trim())?;
            let frame = parse_avlc_frame(&bytes)?;
            let mut obj = serde_json::to_value(&frame)?;
            if let serde_json::Value::Object(ref mut m) = obj {
                m.insert("frame".into(), crate::util::bytes_to_hex(&bytes).into());
            }
            let out = acars::decode::compact::compact_avlc_value(obj, raw);
            println!("{}", serde_json::to_string_pretty(&out)?);
        }
        DecodeCommand::Adsc { payload, raw } => {
            let adsc = parse_adsc_app_text(payload.trim())?;
            let raw_value = serde_json::to_value(&adsc)?;
            let mut out = serde_json::json!({
                "path": "acars",
                "protocol_stack": ["acars", "arinc622", "ads_c"],
                "message_class": "app_message",
                "summary": "ADS-C application payload",
                "app": { "protocol": "ads_c", "standard": "ARINC 622", "payload": raw_value.clone() },
            });
            if raw {
                out.as_object_mut()
                    .unwrap()
                    .insert("raw_decode".into(), raw_value);
            }
            println!("{}", serde_json::to_string_pretty(&out)?);
        }
    }

    Ok(())
}