use crate::hex_to_bytes; use std::fmt::Display; use crate::error::{Result, Error}; #[derive(Debug)] pub struct ADSBFrame { pub raw_frame: String, /// Downlink format (DF, 5 bits) pub downlink_format: u8, /// Transponder capability (CA, 3 bits) pub capability: Capability, /// Unique aircraft number (ICAO, 24 bits) pub icao: String, /// Message (ME, 56 bits) pub message: ADSBMessage, /// Parity/Interrogator ID/Checksum (PI, 24 bits) pub parity: u32, } impl ADSBFrame { /// Parse exactly 14 bytes (112 bits) of raw ADS-B ES data into its fields /// /// [ DF:5 ][ CA:3 ][ ICAO:24 ][ ME:56 ][ PI:24 ] pub fn decode(frame: &[u8]) -> Result { if frame.len() != 14 { return Err(Error::new(format!( "expected 14 bytes, received {}", frame.len() ))); } let mut raw_frame = "".to_string(); for byte in frame { raw_frame.push_str(&format!("{:02x}", byte).to_uppercase()); } // Decode the downlink format by discarding the lower 3 bits let downlink_format = &frame[0] >> 3; if downlink_format != 17 { return Err(Error::new(format!( "downlink format {} is not currently supported", downlink_format ))); } // Decode the capability by masking off everything but the lower 3 bits let capability_value = &frame[0] & 0b0000_0111; let capability = Capability::try_from(capability_value)?; let icao = Self::decode_icao(&frame[1..=3])?; let message = ADSBMessage::decode(&frame[4..=10])?; let parity = Self::decode_parity(&frame[11..])?; Ok(Self { raw_frame, downlink_format, capability, icao, message, parity, }) } pub fn encode(&self) -> Result> { Ok(hex_to_bytes(&self.raw_frame)?) } fn decode_icao(data: &[u8]) -> Result { if data.len() != 3 { return Err(Error::new(format!( "ICAO must be 3 bytes, received {}", data.len() ))); } let s = data .iter() .map(|b| format!("{:02X}", b)) .collect::(); Ok(s) } fn decode_parity(data: &[u8]) -> Result { if data.len() != 3 { return Err(Error::new(format!( "parity must be 3 bytes, received {}", data.len() ))); } let p = ((data[0] as u32) << 16) | ((data[1] as u32) << 8) | (data[2] as u32); Ok(p) } } impl Display for ADSBFrame { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "Frame: {}\ \nDF: {}\ \nCA: {:?}\ \nICAO: {}\ \nME: {:?}\ \nPI: {}", self.raw_frame, self.downlink_format, &self.capability, self.icao, &self.message, self.parity ) } } /// Transponder Capability (CA) codes from the ADS-B spec #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Capability { /// 0: Level 1 transponder Level1, /// 1-3: Reserved Reserved(u8), /// 4: Level 2+ transponder, ground (can set CA=7) Level2OnGround, /// 5: Level 2+ transponder, airborne (can set CA=7) Level2Airborne, /// 6: Level 2+ transponder, either ground or airborne (can set CA=7) Level2Either, /// 7: Downlink Request = 0, or Flight Status = 2,3,4,5 DownlinkRequestOrFlightStatus, } impl TryFrom for Capability { type Error = Error; fn try_from(value: u8) -> Result { let capability = match value { 0 => Capability::Level1, 1..=3 => Capability::Reserved(value), 4 => Capability::Level2OnGround, 5 => Capability::Level2Airborne, 6 => Capability::Level2Either, 7 => Capability::DownlinkRequestOrFlightStatus, _ => { return Err(Error::new(format!("invalid CA value: {}", value))); } }; Ok(capability) } } // fn get_bits(data: &[u8], from: usize, len: usize) -> u32 { // let mut val = 0; // for bit in 0..len { // let idx = from + bit; // let byte = data[idx / 8]; // let shift = 7 - (idx % 8); // let bit_val = ((byte >> shift) & 0x01) as u32; // val = (val << 1) | bit_val; // } // val // } #[derive(Debug, Clone, PartialEq, Eq)] pub enum ADSBMessage { AircraftIdentification(AircraftIdentification), SurfacePosition(SurfacePosition), AirbornePosition(AirbornePosition), AirborneVelocities(AirborneVelocities), Reserved(u8), AircraftStatus(AircraftStatus), TargetState(TargetState), AircraftOperationStatus(AircraftOperationStatus), } impl ADSBMessage { pub fn decode(data: &[u8]) -> Result { if data.len() != 7 { return Err(Error::new(format!( "ME field must be 7 bytes, received {}", data.len() ))); } // First 5 bits is the type code let type_code = data[0] >> 3; let message = match type_code { 1..=4 => { ADSBMessage::AircraftIdentification(AircraftIdentification::decode(type_code, data)?) } 5..=8 => ADSBMessage::SurfacePosition(SurfacePosition::decode(data)?), 9..=18 => ADSBMessage::AirbornePosition(AirbornePosition::decode(type_code, data)?), 19 => ADSBMessage::AirborneVelocities(AirborneVelocities::decode(data)?), 20..=22 => ADSBMessage::AirbornePosition(AirbornePosition::decode(type_code, data)?), 23..=27 => ADSBMessage::Reserved(type_code), 28 => ADSBMessage::AircraftStatus(AircraftStatus::decode(data)?), 29 => ADSBMessage::TargetState(TargetState::decode(data)?), 31 => ADSBMessage::AircraftOperationStatus(AircraftOperationStatus::decode(data)?), _ => { return Err(Error::new(format!( "unsupported ADS-B type_code {}", type_code ))); } }; Ok(message) } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct AircraftIdentification { type_code: u8, emitter_category: u8, wake_vortex_category: WakeVortexCategory, callsign: String, } impl AircraftIdentification { pub fn decode(type_code: u8, data: &[u8]) -> Result { // Byte 0: [ TC(5 bits) | emitter_category (3 bits) ] let emitter_category = data[0] & 0x07; // 56 bit buffer for message let mut bits: u64 = 0; for &b in data { bits = (bits << 8) | b as u64; } let mut callsign = String::with_capacity(8); for i in 0..8 { let shift = 48 - 6 * (i + 1); let raw6 = ((bits >> shift) & 0x3F) as u8; let ch = match raw6 { 1..=26 => (b'A' + (raw6 - 1)) as char, 48..=57 => (b'0' + (raw6 - 48)) as char, 32 => ' ', _ => continue, }; callsign.push(ch); } // trim any trailing spaces let callsign = callsign.trim_end().to_string(); Ok(Self { type_code, emitter_category, wake_vortex_category: WakeVortexCategory::from_tc_ca(type_code, emitter_category), callsign, }) } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum WakeVortexCategory { NoInfo, SurfaceEmergencyVehicle, SurfaceServiceVehicle, GroundObstruction, Glider, LighterThanAir, Parachutist, Ultralight, Reserved, UnmannedAerialVehicle, SpaceVehicle, Light, Medium1, Medium2, HighVortex, Heavy, HighPerformance, Rotorcraft, Unknown, } impl WakeVortexCategory { pub fn from_tc_ca(type_code: u8, emitter_category: u8) -> Self { match (type_code, emitter_category) { (_, 0) => WakeVortexCategory::NoInfo, (2, 1) => WakeVortexCategory::SurfaceEmergencyVehicle, (2, 3) => WakeVortexCategory::SurfaceServiceVehicle, (2, 4..=7) => WakeVortexCategory::GroundObstruction, (3, 1) => WakeVortexCategory::Glider, (3, 2) => WakeVortexCategory::LighterThanAir, (3, 3) => WakeVortexCategory::Parachutist, (3, 4) => WakeVortexCategory::Ultralight, (3, 5) => WakeVortexCategory::Reserved, (3, 6) => WakeVortexCategory::UnmannedAerialVehicle, (3, 7) => WakeVortexCategory::SpaceVehicle, (4, 1) => WakeVortexCategory::Light, (4, 2) => WakeVortexCategory::Medium1, (4, 3) => WakeVortexCategory::Medium2, (4, 4) => WakeVortexCategory::HighVortex, (4, 5) => WakeVortexCategory::Heavy, (4, 6) => WakeVortexCategory::HighPerformance, (4, 7) => WakeVortexCategory::Rotorcraft, _ => WakeVortexCategory::Unknown, } } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct SurfacePosition {} impl SurfacePosition { pub fn decode(_data: &[u8]) -> Result { Ok(Self {}) } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct AirbornePosition {} impl AirbornePosition { pub fn decode(_type_code: u8, _data: &[u8]) -> Result { Ok(Self {}) } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct AirborneVelocities {} impl AirborneVelocities { pub fn decode(_data: &[u8]) -> Result { Ok(Self {}) } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct AircraftStatus {} impl AircraftStatus { pub fn decode(_data: &[u8]) -> Result { Ok(Self {}) } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct TargetState {} impl TargetState { pub fn decode(_data: &[u8]) -> Result { Ok(Self {}) } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct AircraftOperationStatus {} impl AircraftOperationStatus { pub fn decode(_data: &[u8]) -> Result { Ok(Self {}) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_decode_df_17_aircraft_information() { let input = [ 0x8D, 0x48, 0x40, 0xD6, 0x20, 0x2C, 0xC3, 0x71, 0xC3, 0x1C, 0x32, 0xCE, 0x05, 0x76, ]; let frame = ADSBFrame::decode(&input).unwrap(); assert_eq!(frame.downlink_format, 17); assert_eq!(frame.capability, Capability::Level2Airborne); assert_eq!(frame.icao, "4840D6"); match frame.message { ADSBMessage::AircraftIdentification(ref id) => { assert_eq!(id.type_code, 4); assert_eq!(id.emitter_category, 0); assert_eq!(id.wake_vortex_category, WakeVortexCategory::NoInfo); assert_eq!(id.callsign, "KLM10102"); } _ => panic!("expected AircraftIdentification"), } assert_eq!(frame.parity, 13501814); let input = [ 0x8D, 0x48, 0x40, 0xD6, 0x20, 0x2C, 0xC3, 0x71, 0xC3, 0x2C, 0xE0, 0x57, 0x60, 0x98, ]; let frame = ADSBFrame::decode(&input).unwrap(); assert_eq!(frame.downlink_format, 17); assert_eq!(frame.capability, Capability::Level2Airborne); assert_eq!(frame.icao, "4840D6"); match frame.message { ADSBMessage::AircraftIdentification(ref id) => { assert_eq!(id.type_code, 4); assert_eq!(id.emitter_category, 0); assert_eq!(id.wake_vortex_category, WakeVortexCategory::NoInfo); assert_eq!(id.callsign, "KLM1023"); } _ => panic!("expected AircraftIdentification"), } assert_eq!(frame.parity, 5726360); let input = [ 0x8D, 0x7C, 0x71, 0x81, 0x21, 0x5D, 0x01, 0xA0, 0x82, 0x08, 0x20, 0x4D, 0x8B, 0xF1, ]; let frame = ADSBFrame::decode(&input).unwrap(); assert_eq!(frame.downlink_format, 17); assert_eq!(frame.capability, Capability::Level2Airborne); assert_eq!(frame.icao, "7C7181"); match frame.message { ADSBMessage::AircraftIdentification(ref id) => { assert_eq!(id.type_code, 4); assert_eq!(id.emitter_category, 1); assert_eq!(id.wake_vortex_category, WakeVortexCategory::Light); assert_eq!(id.callsign, "WPF"); } _ => panic!("expected AircraftIdentification"), } assert_eq!(frame.parity, 5082097); let input = [ 0x8D, 0x7C, 0x77, 0x45, 0x22, 0x61, 0x51, 0xA0, 0x82, 0x08, 0x20, 0x5C, 0xE9, 0xC2, ]; let frame = ADSBFrame::decode(&input).unwrap(); assert_eq!(frame.downlink_format, 17); assert_eq!(frame.capability, Capability::Level2Airborne); assert_eq!(frame.icao, "7C7745"); match frame.message { ADSBMessage::AircraftIdentification(ref id) => { assert_eq!(id.type_code, 4); assert_eq!(id.emitter_category, 2); assert_eq!(id.wake_vortex_category, WakeVortexCategory::Medium1); assert_eq!(id.callsign, "XUF"); } _ => panic!("expected AircraftIdentification"), } assert_eq!(frame.parity, 6089154); let input = [ 0x8D, 0x7C, 0x80, 0xAD, 0x23, 0x58, 0xF6, 0xB1, 0xE3, 0x5C, 0x60, 0xFF, 0x19, 0x25, ]; let frame = ADSBFrame::decode(&input).unwrap(); assert_eq!(frame.downlink_format, 17); assert_eq!(frame.capability, Capability::Level2Airborne); assert_eq!(frame.icao, "7C80AD"); match frame.message { ADSBMessage::AircraftIdentification(ref id) => { assert_eq!(id.type_code, 4); assert_eq!(id.emitter_category, 3); assert_eq!(id.wake_vortex_category, WakeVortexCategory::Medium2); assert_eq!(id.callsign, "VOZ1851"); } _ => panic!("expected AircraftIdentification"), } assert_eq!(frame.parity, 16718117); let input = [ 0x8D, 0x7C, 0x14, 0x65, 0x25, 0x44, 0x60, 0x74, 0xDF, 0x58, 0x20, 0x73, 0x8E, 0x90, ]; let frame = ADSBFrame::decode(&input).unwrap(); assert_eq!(frame.downlink_format, 17); assert_eq!(frame.capability, Capability::Level2Airborne); assert_eq!(frame.icao, "7C1465"); match frame.message { ADSBMessage::AircraftIdentification(ref id) => { assert_eq!(id.type_code, 4); assert_eq!(id.emitter_category, 5); assert_eq!(id.wake_vortex_category, WakeVortexCategory::Heavy); assert_eq!(id.callsign, "QFA475"); } _ => panic!("expected AircraftIdentification"), } assert_eq!(frame.parity, 7573136); } #[test] fn test_decode_df_17_operation_status() { let input = [ 0x8D, 0x89, 0x65, 0xD2, 0xF8, 0x21, 0x00, 0x02, 0x00, 0x49, 0xB8, 0x94, 0xA4, 0x5F, ]; let frame = ADSBFrame::decode(&input).unwrap(); dbg!(frame); } }