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 {
#[arg(long)]
config: Option<String>,
#[command(subcommand)]
command: Option<Command>,
}
#[derive(Debug, Subcommand)]
enum Command {
Vdl2(vdl2::Cli),
#[command(alias = "acars-vhf")]
Vhf(vhf::Cli),
#[command(name = "airframes.io", alias = "airframes")]
AirframesIo(airframes::Options),
#[command(alias = "hf")]
Hfdl(hfdl::Options),
Decode {
#[command(subcommand)]
command: DecodeCommand,
},
}
#[derive(Debug, Subcommand)]
enum DecodeCommand {
Acars {
#[arg(help = "Hex-encoded ACARS frame bytes")]
hex: String,
#[arg(short, long, value_enum, default_value_t = Direction::Unknown)]
direction: Direction,
#[arg(long)]
raw: bool,
},
Avlc {
#[arg(help = "Hex-encoded AVLC frame bytes (with FCS)")]
hex: String,
#[arg(long)]
raw: bool,
},
Adsc {
#[arg(help = "ADS-C app text payload")]
payload: String,
#[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(())
}