extern crate alloc;
pub mod adsb;
pub mod bds;
pub mod commb;
pub mod cpr;
pub mod crc;
use adsb::{ADSB, ME};
use alloc::fmt;
use bds::BDS;
use crc::modes_checksum;
use deku::bitvec::{BitSlice, Msb0};
use deku::prelude::*;
use serde::ser::{Serialize, Serializer};
#[derive(Debug, PartialEq, serde::Serialize, DekuRead, Clone)]
#[deku(type = "u8", bits = "5")]
#[serde(tag = "DF")]
pub enum DF {
#[deku(id = "0")]
#[serde(rename = "DF0")]
ShortAirAirSurveillance {
#[deku(bits = "1")]
#[serde(skip)]
vs: u8,
#[deku(bits = "1")]
#[serde(skip)]
cc: u8,
#[deku(bits = "1")]
#[serde(skip)]
unused: u8,
#[deku(bits = "3")]
#[serde(skip)]
sl: u8,
#[deku(bits = "2")]
#[serde(skip)]
unused1: u8,
#[deku(bits = "4")]
#[serde(skip)]
ri: u8,
#[deku(bits = "2")]
#[serde(skip)]
unused2: u8,
#[serde(rename = "altitude")]
ac: AC13Field,
#[serde(rename = "icao24")]
ap: ICAO,
},
#[deku(id = "4")]
#[serde(rename = "DF4")]
SurveillanceAltitudeReply {
#[serde(skip)]
fs: FlightStatus,
#[serde(skip)]
dr: DownlinkRequest,
#[serde(skip)]
um: UtilityMessage,
#[serde(rename = "altitude")]
ac: AC13Field,
#[serde(rename = "icao24")]
ap: ICAO,
},
#[deku(id = "5")]
#[serde(rename = "DF5")]
SurveillanceIdentityReply {
fs: FlightStatus,
#[serde(skip)]
dr: DownlinkRequest,
#[serde(skip)]
um: UtilityMessage,
#[serde(rename = "squawk")]
id: IdentityCode,
#[serde(rename = "icao24")]
ap: ICAO,
},
#[deku(id = "11")]
#[serde(rename = "DF11")]
AllCallReply {
capability: Capability,
#[serde(rename = "icao24")]
icao: ICAO,
#[serde(skip)]
p_icao: ICAO,
},
#[deku(id = "16")]
#[serde(rename = "DF16")]
LongAirAirSurveillance {
#[deku(bits = "1")]
#[serde(skip)]
vs: u8,
#[deku(bits = "2")]
#[serde(skip)]
spare1: u8,
#[deku(bits = "3")]
#[serde(skip)]
sl: u8,
#[deku(bits = "2")]
#[serde(skip)]
spare2: u8,
#[deku(bits = "4")]
#[serde(skip)]
ri: u8,
#[deku(bits = "2")]
#[serde(skip)]
spare3: u8,
#[serde(rename = "altitude")]
ac: AC13Field,
#[deku(count = "7")]
#[serde(skip)]
mv: Vec<u8>,
#[serde(rename = "icao24")]
ap: ICAO,
},
#[deku(id = "17")]
#[serde(rename = "ADSB")]
ExtendedSquitterADSB(ADSB),
#[deku(id = "18")]
#[serde(skip)]
ExtendedSquitterTisB {
cf: ControlField,
pi: ICAO,
},
#[deku(id = "19")]
#[serde(skip)]
ExtendedSquitterMilitary {
#[deku(bits = "3")]
af: u8,
},
#[deku(id = "20")]
#[serde(rename = "DF20")]
CommBAltitudeReply {
#[serde(skip)]
fs: FlightStatus,
#[serde(skip)]
dr: DownlinkRequest,
#[serde(skip)]
um: UtilityMessage,
#[serde(rename = "altitude")]
ac: AC13Field,
#[serde(skip)]
bds: BDS,
#[serde(skip)]
ap: ICAO,
},
#[deku(id = "21")]
#[serde(rename = "DF21")]
CommBIdentityReply {
#[serde(skip)]
fs: FlightStatus,
#[serde(skip)]
dr: DownlinkRequest,
#[serde(skip)]
um: UtilityMessage,
#[serde(rename = "squawk")]
id: IdentityCode,
#[serde(skip)]
bds: BDS,
#[serde(rename = "icao24")]
ap: ICAO,
},
#[deku(id_pat = "24..=31")]
CommDExtended {
#[deku(bits = "1")]
spare: u8,
#[serde(skip)]
ke: KE,
#[deku(bits = "4")]
nd: u8,
#[deku(count = "10")]
md: Vec<u8>,
parity: ICAO,
},
}
#[derive(Debug, PartialEq, serde::Serialize, DekuRead, Clone)]
pub struct Message {
#[serde(flatten)]
pub df: DF,
#[deku(reader = "Self::read_crc(df, deku::input_bits)")]
#[serde(skip)]
pub crc: u32,
}
impl Message {
fn read_crc<'b>(
df: &DF,
rest: &'b BitSlice<u8, Msb0>,
) -> Result<(&'b BitSlice<u8, Msb0>, u32), DekuError> {
const MODES_LONG_MSG_BYTES: usize = 14;
const MODES_SHORT_MSG_BYTES: usize = 7;
let bit_len = if let Ok(id) = df.deku_id() {
if id & 0x10 != 0 {
MODES_LONG_MSG_BYTES * 8
} else {
MODES_SHORT_MSG_BYTES * 8
}
} else {
MODES_LONG_MSG_BYTES * 8
};
let (_, remaining_bytes, _) = rest.domain().region().unwrap();
let crc = modes_checksum(remaining_bytes, bit_len)?;
match (df, crc) {
(DF::ExtendedSquitterADSB(_), c) if c > 0 => {
Err(DekuError::Assertion(format!(
"Invalid CRC in ADS-B message: {c}"
)))
}
_ => Ok((rest, crc)),
}
}
}
impl fmt::Display for Message {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let crc = self.crc;
match &self.df {
DF::ShortAirAirSurveillance { ac, .. } => {
writeln!(f, " DF0. Short Air-Air Surveillance")?;
writeln!(f, " ICAO Address: {crc:06x} (Mode S / ADS-B)")?;
if ac.0 > 0 {
let altitude = ac.0;
writeln!(f, " Air/Ground: airborne")?;
writeln!(f, " Altitude: {altitude} ft barometric")?;
} else {
writeln!(f, " Air/Ground: ground")?;
}
}
DF::SurveillanceAltitudeReply { fs, ac, .. } => {
writeln!(f, " DF4. Surveillance, Altitude Reply")?;
writeln!(f, " ICAO Address: {crc:06x} (Mode S / ADS-B)")?;
writeln!(f, " Air/Ground: {fs}")?;
if ac.0 > 0 {
let altitude = ac.0;
writeln!(f, " Altitude: {altitude} ft barometric")?;
}
}
DF::SurveillanceIdentityReply { fs, id, .. } => {
writeln!(f, " DF5. Surveillance, Identity Reply")?;
writeln!(f, " ICAO Address: {crc:06x} (Mode S / ADS-B)")?;
writeln!(f, " Air/Ground: {fs}")?;
writeln!(f, " Squawk: {id}")?;
}
DF::AllCallReply {
capability, icao, ..
} => {
writeln!(f, " DF11. All Call Reply")?;
writeln!(f, " ICAO Address: {icao} (Mode S / ADS-B)")?;
writeln!(f, " Air/Ground: {capability}")?;
}
DF::LongAirAirSurveillance { ac, .. } => {
writeln!(f, " DF16. Long Air-Air ACAS")?;
writeln!(f, " ICAO Address: {crc:06x} (Mode S / ADS-B)")?;
if ac.0 > 0 {
let altitude = ac.0;
writeln!(f, " Air/Ground: airborne")?;
writeln!(f, " Baro altitude: {altitude} ft")?;
} else {
writeln!(f, " Air/Ground: ground")?;
}
}
DF::ExtendedSquitterADSB(msg) => {
write!(f, "{msg}")?;
}
DF::ExtendedSquitterTisB { cf, .. } => {
write!(f, "{cf}")?;
}
DF::ExtendedSquitterMilitary { .. } => {} DF::CommBAltitudeReply { bds, ac, .. } => {
writeln!(f, " DF20. Comm-B, Altitude Reply")?;
writeln!(f, " ICAO Address: {crc:x?}")?;
let altitude = ac.0;
writeln!(f, " Altitude: {altitude} ft")?;
write!(f, " {bds}")?;
}
DF::CommBIdentityReply { bds, id, .. } => {
writeln!(f, " DF21. Comm-B, Identity Reply")?;
writeln!(f, " ICAO Address: {crc:x?}")?;
writeln!(f, " Squawk: {id:x?}")?;
write!(f, " {bds}")?;
}
DF::CommDExtended { .. } => {
writeln!(f, " DF24..=31 Comm-D Extended Length Message")?;
writeln!(f, " ICAO Address: {crc:x?}")?;
}
}
Ok(())
}
}
#[derive(PartialEq, Eq, PartialOrd, DekuRead, Hash, Copy, Clone, Ord)]
pub struct ICAO(pub [u8; 3]);
impl fmt::Debug for ICAO {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:02x}", self.0[0])?;
write!(f, "{:02x}", self.0[1])?;
write!(f, "{:02x}", self.0[2])?;
Ok(())
}
}
impl fmt::Display for ICAO {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:02x}", self.0[0])?;
write!(f, "{:02x}", self.0[1])?;
write!(f, "{:02x}", self.0[2])?;
Ok(())
}
}
impl Serialize for ICAO {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let icao = format!("{}", &self);
serializer.serialize_str(&icao)
}
}
impl core::str::FromStr for ICAO {
type Err = core::num::ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let num = u32::from_str_radix(s, 16)?;
let bytes = num.to_be_bytes();
let num = [bytes[1], bytes[2], bytes[3]];
Ok(Self(num))
}
}
#[derive(PartialEq, DekuRead, Copy, Clone)]
pub struct IdentityCode(#[deku(reader = "Self::read(deku::rest)")] pub u16);
impl IdentityCode {
fn read(
rest: &BitSlice<u8, Msb0>,
) -> Result<(&BitSlice<u8, Msb0>, u16), DekuError> {
let (rest, num) =
u16::read(rest, (deku::ctx::Endian::Big, deku::ctx::BitSize(13)))?;
Ok((rest, decode_id13(num)))
}
}
impl fmt::Debug for IdentityCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:04x}", self.0)?;
Ok(())
}
}
impl fmt::Display for IdentityCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:04x}", self.0)?;
Ok(())
}
}
impl Serialize for IdentityCode {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let squawk = format!("{:04x}", &self.0);
serializer.serialize_str(&squawk)
}
}
#[derive(Debug, PartialEq, Eq, serde::Serialize, DekuRead, Copy, Clone)]
pub struct AC13Field(#[deku(reader = "Self::read(deku::rest)")] pub u16);
impl AC13Field {
fn read(
rest: &BitSlice<u8, Msb0>,
) -> Result<(&BitSlice<u8, Msb0>, u16), DekuError> {
let (rest, ac13field) =
u16::read(rest, (deku::ctx::Endian::Big, deku::ctx::BitSize(13)))?;
let m_bit = ac13field & 0x0040;
let q_bit = ac13field & 0x0010;
if m_bit != 0 {
let meters = ((ac13field & 0x1f80) >> 2) | (ac13field & 0x3f);
Ok((rest, (meters as f32 * 3.28084) as u16))
} else if q_bit != 0 {
let n = ((ac13field & 0x1f80) >> 2)
| ((ac13field & 0x0020) >> 1)
| (ac13field & 0x000f);
Ok((rest, n * 25 - 1000)) } else {
if let Ok(n) = gray2alt(decode_id13(ac13field)) {
Ok((rest, (100 * n) as u16))
} else {
Ok((rest, 0))
}
}
}
}
#[derive(Debug, PartialEq, serde::Serialize, DekuRead, Copy, Clone)]
#[deku(type = "u8", bits = "3")]
#[allow(non_camel_case_types)]
pub enum Capability {
#[serde(rename = "level1")]
AG_LEVEL1 = 0x00,
#[deku(id_pat = "0x01..=0x03")]
AG_RESERVED,
#[serde(rename = "ground")]
AG_GROUND = 0x04,
#[serde(rename = "airborne")]
AG_AIRBORNE = 0x05,
#[serde(rename = "ground/airborne")]
AG_GROUND_AIRBORNE = 0x06,
AG_DR0 = 0x07,
}
impl fmt::Display for Capability {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
Self::AG_LEVEL1 => "Level 1",
Self::AG_RESERVED => "reserved",
Self::AG_GROUND => "ground",
Self::AG_AIRBORNE => "airborne",
Self::AG_GROUND_AIRBORNE => "ground/airborne",
Self::AG_DR0 => "DR0",
}
)
}
}
#[derive(Debug, PartialEq, serde::Serialize, DekuRead, Copy, Clone)]
#[deku(type = "u8", bits = "3")]
#[serde(rename_all = "snake_case")]
pub enum FlightStatus {
NoAlertNoSpiAirborne = 0b000,
NoAlertNoSpiOnGround = 0b001,
AlertNoSpiAirborne = 0b010,
AlertNoSpiOnGround = 0b011,
AlertSpiAirborneGround = 0b100,
NoAlertSpiAirborneGround = 0b101,
Reserved = 0b110,
NotAssigned = 0b111,
}
impl fmt::Display for FlightStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
Self::NoAlertNoSpiAirborne => "airborne",
Self::AlertSpiAirborneGround
| Self::NoAlertSpiAirborneGround => "airborne/ground",
Self::NoAlertNoSpiOnGround => "ground",
Self::AlertNoSpiAirborne => "airborne",
Self::AlertNoSpiOnGround => "ground",
_ => "reserved",
}
)
}
}
#[derive(Debug, PartialEq, Eq, DekuRead, Copy, Clone)]
#[deku(type = "u8", bits = "5")]
pub enum DownlinkRequest {
None = 0b00000,
RequestSendCommB = 0b00001,
CommBBroadcastMsg1 = 0b00100,
CommBBroadcastMsg2 = 0b00101,
#[deku(id_pat = "_")]
Unknown,
}
#[derive(Debug, PartialEq, Eq, DekuRead, Copy, Clone)]
pub struct UtilityMessage {
#[deku(bits = "4")]
pub iis: u8,
pub ids: UtilityMessageType,
}
#[derive(Debug, PartialEq, Eq, DekuRead, Copy, Clone)]
#[deku(type = "u8", bits = "2")]
pub enum UtilityMessageType {
NoInformation = 0b00,
CommB = 0b01,
CommC = 0b10,
CommD = 0b11,
}
#[derive(Debug, PartialEq, serde::Serialize, DekuRead, Clone)]
pub struct ControlField {
t: ControlFieldType,
pub aa: ICAO,
pub me: ME,
}
impl fmt::Display for ControlField {
fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
Ok(())
}
}
#[derive(Debug, PartialEq, serde::Serialize, DekuRead, Clone)]
#[deku(type = "u8", bits = "3")]
#[allow(non_camel_case_types)]
pub enum ControlFieldType {
#[deku(id = "0")]
ADSB_ES_NT,
#[deku(id = "1")]
ADSB_ES_NT_ALT,
#[deku(id = "2")]
TISB_FINE,
#[deku(id = "3")]
TISB_COARSE,
#[deku(id = "4")]
TISB_MANAGE,
#[deku(id = "5")]
TISB_ADSB_RELAY,
#[deku(id = "6")]
TISB_ADSB,
#[deku(id = "7")]
Reserved,
}
impl fmt::Display for ControlFieldType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s_type = match self {
Self::ADSB_ES_NT | Self::ADSB_ES_NT_ALT => "(ADS-B)",
Self::TISB_COARSE | Self::TISB_ADSB_RELAY | Self::TISB_FINE => {
"(TIS-B)"
}
Self::TISB_MANAGE | Self::TISB_ADSB => "(ADS-R)",
Self::Reserved => "(unknown addressing scheme)",
};
write!(f, "{s_type}")
}
}
#[derive(Debug, PartialEq, Eq, DekuRead, Copy, Clone)]
#[deku(type = "u8", bits = "1")]
pub enum KE {
DownlinkELMTx = 0,
UplinkELMAck = 1,
}
#[rustfmt::skip]
pub fn decode_id13(id13_field: u16) -> u16 {
let mut hex_gillham: u16 = 0;
if id13_field & 0x1000 != 0 { hex_gillham |= 0x0010; } if id13_field & 0x0800 != 0 { hex_gillham |= 0x1000; } if id13_field & 0x0400 != 0 { hex_gillham |= 0x0020; } if id13_field & 0x0200 != 0 { hex_gillham |= 0x2000; } if id13_field & 0x0100 != 0 { hex_gillham |= 0x0040; } if id13_field & 0x0080 != 0 { hex_gillham |= 0x4000; } if id13_field & 0x0020 != 0 { hex_gillham |= 0x0100; } if id13_field & 0x0010 != 0 { hex_gillham |= 0x0001; } if id13_field & 0x0008 != 0 { hex_gillham |= 0x0200; } if id13_field & 0x0004 != 0 { hex_gillham |= 0x0002; } if id13_field & 0x0002 != 0 { hex_gillham |= 0x0400; } if id13_field & 0x0001 != 0 { hex_gillham |= 0x0004; } hex_gillham
}
#[rustfmt::skip]
pub fn gray2alt(gray: u16) -> Result<i32, &'static str> {
let mut five_hundreds: u32 = 0;
let mut one_hundreds: u32 = 0;
if (gray & 0x8889) != 0 || (gray & 0x00f0) == 0 {
return Err("Invalid altitude");
}
if gray & 0x0010 != 0 { one_hundreds ^= 0x007; } if gray & 0x0020 != 0 { one_hundreds ^= 0x003; } if gray & 0x0040 != 0 { one_hundreds ^= 0x001; } if (one_hundreds & 5) == 5 { one_hundreds ^= 2; }
if one_hundreds > 5 { return Err("Invalid altitude"); }
if gray & 0x0002 != 0 { five_hundreds ^= 0x0ff; } if gray & 0x0004 != 0 { five_hundreds ^= 0x07f; } if gray & 0x1000 != 0 { five_hundreds ^= 0x03f; } if gray & 0x2000 != 0 { five_hundreds ^= 0x01f; } if gray & 0x4000 != 0 { five_hundreds ^= 0x00f; } if gray & 0x0100 != 0 { five_hundreds ^= 0x007; } if gray & 0x0200 != 0 { five_hundreds ^= 0x003; } if gray & 0x0400 != 0 { five_hundreds ^= 0x001; } if five_hundreds & 1 != 0 && one_hundreds <= 6 {
one_hundreds = 6 - one_hundreds;
}
let n = (five_hundreds * 5) + one_hundreds;
if n >= 13 {
Ok(n as i32 - 13)
} else {
Err("Invalid altitude")
}
}
#[cfg(test)]
mod tests {
use super::*;
use hexlit::hex;
#[test]
fn test_ac13field() {
let bytes = hex!("A02014B400000000000000F9D514");
let msg = Message::from_bytes((&bytes, 0)).unwrap().1;
match msg.df {
DF::CommBAltitudeReply { ac, .. } => {
assert_eq!(ac.0, 32300);
}
_ => unreachable!(),
}
}
#[test]
fn test_invalid_crc() {
let bytes = hex!("8d4ca251204994b1c36e60a5343d");
let msg = Message::from_bytes((&bytes, 0));
if let Err(e) = msg {
match e {
DekuError::Assertion(_msg) => (),
_ => unreachable!(),
}
} else {
unreachable!()
}
}
}