#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc(html_logo_url = "https://raw.githubusercontent.com/rsadsb/adsb_deku/master/media/logo.png")]
extern crate alloc;
#[cfg(feature = "alloc")]
use alloc::{fmt, format, string::String, string::ToString, vec, vec::Vec};
#[cfg(feature = "alloc")]
use core::{
clone::Clone,
cmp::Eq,
cmp::PartialEq,
default::Default,
fmt::Debug,
hash::Hash,
iter::IntoIterator,
marker::Copy,
prelude::rust_2021::derive,
result,
result::Result::{Err, Ok},
write, writeln,
};
pub mod adsb;
pub mod bds;
pub mod cpr;
mod crc;
mod mode_ac;
#[doc = include_str!("../README.md")]
mod readme_test {}
use adsb::{ControlField, ADSB};
use bds::BDS;
use deku::ctx::{BitSize, Endian};
use deku::no_std_io::{Cursor, Read, Seek};
use deku::prelude::*;
struct ReaderCrc<R: Read + Seek> {
reader: R,
cache: Vec<u8>,
just_seeked: bool,
}
impl<R: Read + Seek> Read for ReaderCrc<R> {
fn read(&mut self, buf: &mut [u8]) -> deku::no_std_io::Result<usize> {
let n = self.reader.read(buf);
if !self.just_seeked {
if let Ok(n) = n {
self.cache.extend_from_slice(&buf[..n]);
}
}
self.just_seeked = false;
n
}
}
impl<R: Read + Seek> Seek for ReaderCrc<R> {
fn seek(&mut self, pos: deku::no_std_io::SeekFrom) -> deku::no_std_io::Result<u64> {
self.just_seeked = true;
self.reader.seek(pos)
}
}
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Frame {
pub df: DF,
pub crc: u32,
}
impl Frame {
pub fn from_bytes(buf: &[u8]) -> Result<Frame, DekuError> {
let cursor = Cursor::new(buf);
Self::from_reader(cursor)
}
pub fn from_reader<R: Read + Seek>(r: R) -> Result<Frame, DekuError> {
let mut reader_crc = ReaderCrc { reader: r, cache: vec![], just_seeked: false };
let mut reader = Reader::new(&mut reader_crc);
let df = DF::from_reader_with_ctx(&mut reader, ())?;
let crc = Self::read_crc(&df, &mut reader_crc)?;
Ok(Self { df, crc })
}
}
impl Frame {
fn read_crc<R: Read + Seek>(
df: &DF,
reader: &mut ReaderCrc<R>,
) -> result::Result<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
};
if bit_len > reader.cache.len() * 8 {
let mut buf = vec![];
reader.read_to_end(&mut buf).unwrap();
reader.cache.append(&mut buf);
}
let crc = crc::modes_checksum(&reader.cache, bit_len)?;
Ok(crc)
}
}
impl fmt::Display for Frame {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let crc = self.crc;
match &self.df {
DF::ShortAirAirSurveillance { altitude, .. } => {
writeln!(f, " Short Air-Air Surveillance")?;
writeln!(f, " ICAO Address: {crc:06x} (Mode S / ADS-B)")?;
if altitude.0 > 0 {
let altitude = altitude.0;
writeln!(f, " Air/Ground: airborne?")?;
writeln!(f, " Altitude: {altitude} ft barometric")?;
} else {
writeln!(f, " Air/Ground: ground")?;
}
}
DF::SurveillanceAltitudeReply { fs, ac, .. } => {
writeln!(f, " 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, .. } => {
let identity = id.0;
writeln!(f, " Surveillance, Identity Reply")?;
writeln!(f, " ICAO Address: {crc:06x} (Mode S / ADS-B)")?;
writeln!(f, " Air/Ground: {fs}")?;
writeln!(f, " Identity: {identity:04x}")?;
}
DF::AllCallReply { capability, icao, .. } => {
writeln!(f, " All Call Reply")?;
writeln!(f, " ICAO Address: {icao} (Mode S / ADS-B)")?;
writeln!(f, " Air/Ground: {capability}")?;
}
DF::LongAirAir { altitude, .. } => {
writeln!(f, " Long Air-Air ACAS")?;
writeln!(f, " ICAO Address: {crc:06x} (Mode S / ADS-B)")?;
if altitude.0 > 0 {
let altitude = altitude.0;
writeln!(f, " Air/Ground: airborne?")?;
writeln!(f, " Baro altitude: {altitude} ft")?;
} else {
writeln!(f, " Air/Ground: ground")?;
}
}
DF::ADSB(adsb) => {
write!(f, "{}", adsb.to_string("(Mode S / ADS-B)")?)?;
}
DF::TisB { cf, .. } => {
write!(f, "{cf}")?;
}
DF::ExtendedQuitterMilitaryApplication { .. } => {}
DF::CommBAltitudeReply { bds, alt, .. } => {
writeln!(f, " Comm-B, Altitude Reply")?;
writeln!(f, " ICAO Address: {crc:x?} (Mode S / ADS-B)")?;
let altitude = alt.0;
writeln!(f, " Altitude: {altitude} ft")?;
write!(f, " {bds}")?;
}
DF::CommBIdentityReply { id, bds, .. } => {
writeln!(f, " Comm-B, Identity Reply")?;
writeln!(f, " ICAO Address: {crc:x?} (Mode S / ADS-B)")?;
writeln!(f, " Squawk: {id:x?}")?;
write!(f, " {bds}")?;
}
DF::ModeSExtendedSquitter { .. } => {
writeln!(f, " Mode S Extended Squitter Message")?;
writeln!(f, " ICAO Address: {crc:x?} (Mode S / ADS-B)")?;
}
}
Ok(())
}
}
#[derive(Debug, PartialEq, DekuRead, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[deku(id_type = "u8", bits = "5")]
pub enum DF {
#[deku(id = "17")]
ADSB(ADSB),
#[deku(id = "11")]
AllCallReply {
capability: Capability,
icao: ICAO,
p_icao: ICAO,
},
#[deku(id = "0")]
ShortAirAirSurveillance {
#[deku(bits = "1")]
vs: u8,
#[deku(bits = "1")]
cc: u8,
#[deku(bits = "1")]
unused: u8,
#[deku(bits = "3")]
sl: u8,
#[deku(bits = "2")]
unused1: u8,
#[deku(bits = "4")]
ri: u8,
#[deku(bits = "2")]
unused2: u8,
altitude: AC13Field,
parity: ICAO,
},
#[deku(id = "4")]
SurveillanceAltitudeReply {
fs: FlightStatus,
dr: DownlinkRequest,
um: UtilityMessage,
ac: AC13Field,
ap: ICAO,
},
#[deku(id = "5")]
SurveillanceIdentityReply {
fs: FlightStatus,
dr: DownlinkRequest,
um: UtilityMessage,
id: IdentityCode,
ap: ICAO,
},
#[deku(id = "16")]
LongAirAir {
#[deku(bits = "1")]
vs: u8,
#[deku(bits = "2")]
spare1: u8,
#[deku(bits = "3")]
sl: u8,
#[deku(bits = "2")]
spare2: u8,
#[deku(bits = "4")]
ri: u8,
#[deku(bits = "2")]
spare3: u8,
altitude: AC13Field,
#[deku(count = "7")]
mv: Vec<u8>,
parity: ICAO,
},
#[deku(id = "18")]
TisB {
cf: ControlField,
pi: ICAO,
},
#[deku(id = "19")]
ExtendedQuitterMilitaryApplication {
#[deku(bits = "3")]
af: u8,
},
#[deku(id = "20")]
CommBAltitudeReply {
flight_status: FlightStatus,
dr: DownlinkRequest,
um: UtilityMessage,
alt: AC13Field,
bds: BDS,
},
#[deku(id = "21")]
CommBIdentityReply {
fs: FlightStatus,
dr: DownlinkRequest,
um: UtilityMessage,
#[deku(
bits = "13",
endian = "big",
map = "|squawk: u32| -> Result<_, DekuError> {Ok(mode_ac::decode_id13_field(squawk))}"
)]
id: u32,
bds: BDS,
parity: ICAO,
},
#[deku(id_pat = "24..=31")]
ModeSExtendedSquitter {
df: u8,
capability: Capability,
icao: ICAO,
#[deku(bits = 5)]
type_code: u8,
#[deku(bits = 51)]
adsb_data: u64,
parity: ICAO,
},
}
#[derive(Debug, PartialEq, Eq, DekuRead, Default, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Altitude {
pub ss: SurveillanceStatus,
#[deku(bits = "1")]
pub saf_or_imf: u8,
#[deku(reader = "Self::read(deku::reader)")]
pub alt: Option<u16>,
#[deku(bits = "1")]
pub t: bool,
pub odd_flag: CPRFormat,
#[deku(bits = "17", endian = "big")]
pub lat_cpr: u32,
#[deku(bits = "17", endian = "big")]
pub lon_cpr: u32,
}
impl fmt::Display for Altitude {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let altitude = self
.alt
.map_or_else(|| "None".to_string(), |altitude| format!("{altitude} ft barometric"));
writeln!(f, " Altitude: {altitude}")?;
writeln!(f, " CPR type: Airborne")?;
writeln!(f, " CPR odd flag: {}", self.odd_flag)?;
writeln!(f, " CPR latitude: ({})", self.lat_cpr)?;
writeln!(f, " CPR longitude: ({})", self.lon_cpr)?;
Ok(())
}
}
impl Altitude {
fn read<R: Read + Seek>(reader: &mut Reader<R>) -> Result<Option<u16>, DekuError> {
let num = u32::from_reader_with_ctx(reader, (Endian::Big, BitSize(12)))?;
let q = num & 0x10;
if q > 0 {
let n = ((num & 0x0fe0) >> 1) | (num & 0x000f);
let n = n * 25;
if n > 1000 {
Ok(u16::try_from(n - 1000).ok())
} else {
Ok(None)
}
} else {
let mut n = ((num & 0x0fc0) << 1) | (num & 0x003f);
n = mode_ac::decode_id13_field(n);
if let Ok(n) = mode_ac::mode_a_to_mode_c(n) {
Ok(u16::try_from(n * 100).ok())
} else {
Ok(None)
}
}
}
}
#[derive(Debug, PartialEq, Eq, DekuRead, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[deku(id_type = "u8", bits = "2")]
pub enum SurveillanceStatus {
NoCondition = 0,
PermanentAlert = 1,
TemporaryAlert = 2,
SPICondition = 3,
}
impl Default for SurveillanceStatus {
fn default() -> Self {
Self::NoCondition
}
}
#[derive(Debug, PartialEq, Eq, DekuRead, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[deku(id_type = "u8", bits = "1")]
pub enum CPRFormat {
Even = 0,
Odd = 1,
}
impl Default for CPRFormat {
fn default() -> Self {
Self::Even
}
}
impl fmt::Display for CPRFormat {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
Self::Even => "even",
Self::Odd => "odd",
}
)
}
}
#[derive(Debug, PartialEq, Eq, DekuRead, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[deku(id_type = "u8", bits = "1")]
pub enum Sign {
Positive = 0,
Negative = 1,
}
impl Sign {
#[must_use]
pub fn value(&self) -> i16 {
match self {
Self::Positive => 1,
Self::Negative => -1,
}
}
}
impl fmt::Display for Sign {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
Self::Positive => "",
Self::Negative => "-",
}
)
}
}
#[derive(Debug, PartialEq, Eq, DekuRead, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct IdentityCode(#[deku(reader = "Self::read(deku::reader)")] pub u16);
impl IdentityCode {
fn read<R: Read + Seek>(reader: &mut Reader<R>) -> result::Result<u16, DekuError> {
let num = u32::from_reader_with_ctx(reader, (Endian::Big, BitSize(13)))?;
let c1 = (num & 0b1_0000_0000_0000) >> 12;
let a1 = (num & 0b0_1000_0000_0000) >> 11;
let c2 = (num & 0b0_0100_0000_0000) >> 10;
let a2 = (num & 0b0_0010_0000_0000) >> 9;
let c4 = (num & 0b0_0001_0000_0000) >> 8;
let a4 = (num & 0b0_0000_1000_0000) >> 7;
let b1 = (num & 0b0_0000_0010_0000) >> 5;
let d1 = (num & 0b0_0000_0001_0000) >> 4;
let b2 = (num & 0b0_0000_0000_1000) >> 3;
let d2 = (num & 0b0_0000_0000_0100) >> 2;
let b4 = (num & 0b0_0000_0000_0010) >> 1;
let d4 = num & 0b0_0000_0000_0001;
let a = (a4 << 2) | (a2 << 1) | a1;
let b = (b4 << 2) | (b2 << 1) | b1;
let c = (c4 << 2) | (c2 << 1) | c1;
let d = (d4 << 2) | (d2 << 1) | d1;
let num: u16 = ((a << 12) | (b << 8) | (c << 4) | d) as u16;
Ok(num)
}
}
/// ICAO Address; Mode S transponder code
#[derive(Debug, PartialEq, Eq, PartialOrd, DekuRead, Hash, Copy, Clone, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ICAO(pub [u8; 3]);
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 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))
}
}
/// Type of `DownlinkRequest`
#[derive(Debug, PartialEq, Eq, DekuRead, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[deku(id_type = "u8", bits = "5")]
pub enum DownlinkRequest {
#[deku(id = 0b00000)]
None,
#[deku(id = 0b00001)]
RequestSendCommB,
#[deku(id = 0b00100)]
CommBBroadcastMsg1,
#[deku(id = 0b00101)]
CommBBroadcastMsg2,
#[deku(id_pat = "_")]
Unknown(u8),
}
/// Uplink / Downlink
#[derive(Debug, PartialEq, Eq, DekuRead, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[deku(id_type = "u8", bits = "1")]
pub enum KE {
DownlinkELMTx = 0,
UplinkELMAck = 1,
}
#[derive(Debug, PartialEq, Eq, DekuRead, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct UtilityMessage {
#[deku(bits = "4")]
pub iis: u8,
pub ids: UtilityMessageType,
}
/// Message Type
#[derive(Debug, PartialEq, Eq, DekuRead, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[deku(id_type = "u8", bits = "2")]
pub enum UtilityMessageType {
NoInformation = 0b00,
CommB = 0b01,
CommC = 0b10,
CommD = 0b11,
}
/// Airborne / Ground and SPI
#[derive(Debug, PartialEq, Eq, DekuRead, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[deku(id_type = "u8", bits = "3")]
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
| Self::AlertSPIAirborneGround
| Self::NoAlertSPIAirborneGround => "airborne?",
Self::NoAlertNoSPIOnGround => "ground?",
Self::AlertNoSPIAirborne => "airborne",
Self::AlertNoSPIOnGround => "ground",
_ => "reserved",
}
)
}
}
/// 13 bit encoded altitude
#[derive(Debug, PartialEq, Eq, DekuRead, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct AC13Field(#[deku(reader = "Self::read(deku::reader)")] pub u16);
impl AC13Field {
// TODO Add unit
fn read<R: Read + Seek>(reader: &mut Reader<R>) -> result::Result<u16, DekuError> {
let num = u16::from_reader_with_ctx(reader, (Endian::Big, BitSize(13)))?;
// Handle invalid or special codes
if num == 0 || num == 0b1111111111111 {
return Ok(0);
}
let m_bit = num & 0x0040;
let q_bit = num & 0x0010;
if m_bit != 0 {
// TODO: read altitude when meter is selected
Ok(0)
} else if q_bit != 0 {
let n = ((num & 0x1f80) >> 2) | ((num & 0x0020) >> 1) | (num & 0x000f);
let n = n * 25;
if n > 1000 {
Ok(n - 1000)
} else {
// TODO: add error
Ok(0)
}
} else {
// TODO 11 bit gillham coded altitude
if let Ok(n) = mode_ac::mode_a_to_mode_c(mode_ac::decode_id13_field(num as u32)) {
Ok((100 * n) as u16)
} else {
Ok(0)
}
}
}
}
/// Transponder level and additional information (3.1.2.5.2.2.1)
#[derive(Debug, PartialEq, Eq, DekuRead, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[deku(id_type = "u8", bits = "3")]
#[allow(non_camel_case_types)]
pub enum Capability {
/// Level 1 transponder (surveillance only), and either airborne or on the ground
#[deku(id = 0x00)]
AG_UNCERTAIN,
#[deku(id_pat = "0x01..=0x03")]
Reserved(u8),
/// Level 2 or above transponder, on ground
#[deku(id = 0x04)]
AG_GROUND,
/// Level 2 or above transponder, airborne
#[deku(id = 0x05)]
AG_AIRBORNE,
/// Level 2 or above transponder, either airborne or on ground
#[deku(id = 0x06)]
AG_UNCERTAIN2,
/// DR field is not equal to 0, or fs field equal 2, 3, 4, or 5, and either airborne or on
/// ground
#[deku(id = 0x07)]
AG_UNCERTAIN3,
}
impl fmt::Display for Capability {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
Self::AG_UNCERTAIN => "uncertain1",
Self::Reserved(_) => "reserved",
Self::AG_GROUND => "ground",
Self::AG_AIRBORNE => "airborne",
Self::AG_UNCERTAIN2 => "uncertain2",
Self::AG_UNCERTAIN3 => "airborne?",
}
)
}
}
const CHAR_LOOKUP: &[u8; 64] = b"#ABCDEFGHIJKLMNOPQRSTUVWXYZ##### ###############0123456789######";
pub(crate) fn aircraft_identification_read<R: Read + Seek>(
reader: &mut Reader<R>,
) -> Result<String, DekuError> {
let mut chars = vec![];
for _ in 0..=6 {
let c = <u8>::from_reader_with_ctx(reader, BitSize(6))?;
if c != 32 {
chars.push(c);
}
}
let encoded = chars.into_iter().map(|b| CHAR_LOOKUP[b as usize] as char).collect::<String>();
Ok(encoded)
}