pub mod bds05;
pub mod bds06;
pub mod bds08;
pub mod bds09;
pub mod bds10;
pub mod bds17;
pub mod bds18;
pub mod bds19;
pub mod bds20;
pub mod bds21;
pub mod bds30;
pub mod bds40;
pub mod bds44;
pub mod bds45;
pub mod bds50;
pub mod bds60;
pub mod bds61;
pub mod bds62;
pub mod bds65;
use self::bds05::AirbornePosition;
use self::bds06::SurfacePosition;
use self::bds08::AircraftIdentification as Bds08AircraftIdentification;
use self::bds09::AirborneVelocity;
use self::bds10::DataLinkCapability;
use self::bds17::CommonUsageGICBCapabilityReport;
use self::bds18::GICBCapabilityReportPart1;
use self::bds19::GICBCapabilityReportPart2;
use self::bds20::AircraftIdentification as Bds20AircraftIdentification;
use self::bds21::AircraftAndAirlineRegistrationMarkings;
use self::bds30::ACASResolutionAdvisory;
use self::bds40::SelectedVerticalIntention;
use self::bds44::MeteorologicalRoutineAirReport;
use self::bds45::MeteorologicalHazardReport;
use self::bds50::TrackAndTurnReport;
use self::bds60::HeadingAndSpeedReport;
use self::bds61::AircraftStatus;
use self::bds62::TargetStateAndStatusInformation;
use self::bds65::AircraftOperationStatus;
use deku::{DekuContainerRead, DekuError, DekuReader};
use serde::Serialize;
use std::fmt;
#[derive(Debug, Clone, PartialEq, Serialize)]
pub enum DecodingError {
InvalidBdsCode(u8),
TypeCodeMismatch {
bds_code: u8,
received_tc: u8,
expected_tc: String,
},
PayloadTooShort {
expected: usize,
received: usize,
},
DecodingFailed(String),
}
impl fmt::Display for DecodingError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DecodingError::InvalidBdsCode(code) => {
write!(f, "Invalid BDS code 0x{:02x}", code)
}
DecodingError::TypeCodeMismatch {
bds_code,
received_tc,
expected_tc,
} => {
write!(
f,
"Type Code mismatch for BDS 0x{:02x}: received TC={}, expected {}",
bds_code, received_tc, expected_tc
)
}
DecodingError::PayloadTooShort { expected, received } => {
write!(
f,
"Payload too short: expected {} bytes, got {}",
expected, received
)
}
DecodingError::DecodingFailed(msg) => {
write!(f, "Decoding failed: {}", msg)
}
}
}
}
impl std::error::Error for DecodingError {}
impl From<DekuError> for DecodingError {
fn from(err: DekuError) -> Self {
DecodingError::DecodingFailed(err.to_string())
}
}
#[derive(Debug, Clone, PartialEq, Serialize, serde::Deserialize)]
#[serde(untagged)]
pub enum DecodedBds {
Bds05(AirbornePosition),
Bds06(SurfacePosition),
Bds08(Bds08AircraftIdentification),
Bds09(AirborneVelocity),
Bds10(DataLinkCapability),
Bds17(CommonUsageGICBCapabilityReport),
Bds18(GICBCapabilityReportPart1),
Bds19(GICBCapabilityReportPart2),
Bds20(Bds20AircraftIdentification),
Bds21(AircraftAndAirlineRegistrationMarkings),
Bds30(ACASResolutionAdvisory),
Bds40(SelectedVerticalIntention),
Bds44(MeteorologicalRoutineAirReport),
Bds45(MeteorologicalHazardReport),
Bds50(TrackAndTurnReport),
Bds60(HeadingAndSpeedReport),
Bds61(AircraftStatus),
Bds62(TargetStateAndStatusInformation),
Bds65(AircraftOperationStatus),
}
fn extract_tc(payload: &[u8]) -> u8 {
if payload.is_empty() {
return 0;
}
payload[0] >> 3
}
pub fn decode_payload(
payload: &[u8],
bds_code: u8,
) -> Result<DecodedBds, DecodingError> {
match bds_code {
0x05 => {
let tc = extract_tc(payload);
if !((9..=18).contains(&tc) || (20..=22).contains(&tc)) {
return Err(DecodingError::TypeCodeMismatch {
bds_code,
received_tc: tc,
expected_tc: "9-18, 20-22".to_string(),
});
}
AirbornePosition::from_reader_with_ctx(
&mut deku::reader::Reader::new(std::io::Cursor::new(payload)),
tc,
)
.map(DecodedBds::Bds05)
.map_err(|e| DecodingError::DecodingFailed(e.to_string()))
}
0x06 => {
let tc = extract_tc(payload);
if !(5..=8).contains(&tc) {
return Err(DecodingError::TypeCodeMismatch {
bds_code,
received_tc: tc,
expected_tc: "5-8".to_string(),
});
}
SurfacePosition::from_reader_with_ctx(
&mut deku::reader::Reader::new(std::io::Cursor::new(payload)),
tc,
)
.map(DecodedBds::Bds06)
.map_err(|e| DecodingError::DecodingFailed(e.to_string()))
}
0x08 => {
let tc = extract_tc(payload);
if !(1..=4).contains(&tc) {
return Err(DecodingError::TypeCodeMismatch {
bds_code,
received_tc: tc,
expected_tc: "1-4".to_string(),
});
}
Bds08AircraftIdentification::from_reader_with_ctx(
&mut deku::reader::Reader::new(std::io::Cursor::new(payload)),
tc,
)
.map(DecodedBds::Bds08)
.map_err(|e| DecodingError::DecodingFailed(e.to_string()))
}
0x09 => {
let tc = extract_tc(payload);
if tc != 19 {
return Err(DecodingError::TypeCodeMismatch {
bds_code,
received_tc: tc,
expected_tc: "19".to_string(),
});
}
AirborneVelocity::from_bytes((payload, 5))
.map(|(_, decoded)| DecodedBds::Bds09(decoded))
.map_err(|e| DecodingError::DecodingFailed(e.to_string()))
}
0x10 => {
let mut data = vec![0x10];
data.extend_from_slice(payload);
DataLinkCapability::from_bytes((&data, 0))
.map(|(_, decoded)| DecodedBds::Bds10(decoded))
.map_err(|e| DecodingError::DecodingFailed(e.to_string()))
}
0x17 => CommonUsageGICBCapabilityReport::from_bytes((payload, 0))
.map(|(_, decoded)| DecodedBds::Bds17(decoded))
.map_err(|e| DecodingError::DecodingFailed(e.to_string())),
0x18 => GICBCapabilityReportPart1::from_bytes((payload, 0))
.map(|(_, decoded)| DecodedBds::Bds18(decoded))
.map_err(|e| DecodingError::DecodingFailed(e.to_string())),
0x19 => GICBCapabilityReportPart2::from_bytes((payload, 0))
.map(|(_, decoded)| DecodedBds::Bds19(decoded))
.map_err(|e| DecodingError::DecodingFailed(e.to_string())),
0x20 => Bds20AircraftIdentification::from_bytes((payload, 0))
.map(|(_, decoded)| DecodedBds::Bds20(decoded))
.map_err(|e| DecodingError::DecodingFailed(e.to_string())),
0x21 => {
AircraftAndAirlineRegistrationMarkings::from_bytes((payload, 0))
.map(|(_, decoded)| DecodedBds::Bds21(decoded))
.map_err(|e| DecodingError::DecodingFailed(e.to_string()))
}
0x30 => ACASResolutionAdvisory::from_bytes((payload, 0))
.map(|(_, decoded)| DecodedBds::Bds30(decoded))
.map_err(|e| DecodingError::DecodingFailed(e.to_string())),
0x40 => SelectedVerticalIntention::from_bytes((payload, 0))
.map(|(_, decoded)| DecodedBds::Bds40(decoded))
.map_err(|e| DecodingError::DecodingFailed(e.to_string())),
0x44 => MeteorologicalRoutineAirReport::from_bytes((payload, 0))
.map(|(_, decoded)| DecodedBds::Bds44(decoded))
.map_err(|e| DecodingError::DecodingFailed(e.to_string())),
0x45 => MeteorologicalHazardReport::from_bytes((payload, 0))
.map(|(_, decoded)| DecodedBds::Bds45(decoded))
.map_err(|e| DecodingError::DecodingFailed(e.to_string())),
0x50 => TrackAndTurnReport::from_bytes((payload, 0))
.map(|(_, decoded)| DecodedBds::Bds50(decoded))
.map_err(|e| DecodingError::DecodingFailed(e.to_string())),
0x60 => HeadingAndSpeedReport::from_bytes((payload, 0))
.map(|(_, decoded)| DecodedBds::Bds60(decoded))
.map_err(|e| DecodingError::DecodingFailed(e.to_string())),
0x61 => {
let tc = extract_tc(payload);
if tc != 28 {
return Err(DecodingError::TypeCodeMismatch {
bds_code,
received_tc: tc,
expected_tc: "28".to_string(),
});
}
AircraftStatus::from_bytes((payload, 5))
.map(|(_, decoded)| DecodedBds::Bds61(decoded))
.map_err(|e| DecodingError::DecodingFailed(e.to_string()))
}
0x62 => {
let tc = extract_tc(payload);
if tc != 29 {
return Err(DecodingError::TypeCodeMismatch {
bds_code,
received_tc: tc,
expected_tc: "29".to_string(),
});
}
TargetStateAndStatusInformation::from_bytes((payload, 5))
.map(|(_, decoded)| DecodedBds::Bds62(decoded))
.map_err(|e| DecodingError::DecodingFailed(e.to_string()))
}
0x65 => {
let tc = extract_tc(payload);
if tc != 31 {
return Err(DecodingError::TypeCodeMismatch {
bds_code,
received_tc: tc,
expected_tc: "31".to_string(),
});
}
AircraftOperationStatus::from_bytes((payload, 5))
.map(|(_, decoded)| DecodedBds::Bds65(decoded))
.map_err(|e| DecodingError::DecodingFailed(e.to_string()))
}
_ => Err(DecodingError::InvalidBdsCode(bds_code)),
}
}
pub fn decode_bds(
payload: &[u8],
bds_code: u8,
) -> Result<DecodedBds, DecodingError> {
if payload.is_empty() {
return Err(DecodingError::PayloadTooShort {
expected: 1,
received: 0,
});
}
match bds_code {
0x05 => {
let tc = extract_tc(payload);
if !((9..=18).contains(&tc) || (20..=22).contains(&tc)) {
return Err(DecodingError::TypeCodeMismatch {
bds_code,
received_tc: tc,
expected_tc: "9-18, 20-22".to_string(),
});
}
AirbornePosition::from_reader_with_ctx(
&mut deku::reader::Reader::new(std::io::Cursor::new(payload)),
tc,
)
.map(DecodedBds::Bds05)
.map_err(|e| DecodingError::DecodingFailed(e.to_string()))
}
0x06 => {
let tc = extract_tc(payload);
if !(5..=8).contains(&tc) {
return Err(DecodingError::TypeCodeMismatch {
bds_code,
received_tc: tc,
expected_tc: "5-8".to_string(),
});
}
SurfacePosition::from_reader_with_ctx(
&mut deku::reader::Reader::new(std::io::Cursor::new(payload)),
tc,
)
.map(DecodedBds::Bds06)
.map_err(|e| DecodingError::DecodingFailed(e.to_string()))
}
0x08 => {
let tc = extract_tc(payload);
if !(1..=4).contains(&tc) {
return Err(DecodingError::TypeCodeMismatch {
bds_code,
received_tc: tc,
expected_tc: "1-4".to_string(),
});
}
Bds08AircraftIdentification::from_reader_with_ctx(
&mut deku::reader::Reader::new(std::io::Cursor::new(payload)),
tc,
)
.map(DecodedBds::Bds08)
.map_err(|e| DecodingError::DecodingFailed(e.to_string()))
}
0x09 => {
let tc = extract_tc(payload);
if tc != 19 {
return Err(DecodingError::TypeCodeMismatch {
bds_code,
received_tc: tc,
expected_tc: "19".to_string(),
});
}
AirborneVelocity::from_bytes((payload, 5))
.map(|(_, decoded)| DecodedBds::Bds09(decoded))
.map_err(|e| DecodingError::DecodingFailed(e.to_string()))
}
0x10 => {
let mut data = vec![0x10];
data.extend_from_slice(payload);
DataLinkCapability::from_bytes((&data, 0))
.map(|(_, decoded)| DecodedBds::Bds10(decoded))
.map_err(|e| DecodingError::DecodingFailed(e.to_string()))
}
0x17 => CommonUsageGICBCapabilityReport::from_bytes((payload, 0))
.map(|(_, decoded)| DecodedBds::Bds17(decoded))
.map_err(|e| DecodingError::DecodingFailed(e.to_string())),
0x18 => GICBCapabilityReportPart1::from_bytes((payload, 0))
.map(|(_, decoded)| DecodedBds::Bds18(decoded))
.map_err(|e| DecodingError::DecodingFailed(e.to_string())),
0x19 => GICBCapabilityReportPart2::from_bytes((payload, 0))
.map(|(_, decoded)| DecodedBds::Bds19(decoded))
.map_err(|e| DecodingError::DecodingFailed(e.to_string())),
0x20 => Bds20AircraftIdentification::from_bytes((payload, 0))
.map(|(_, decoded)| DecodedBds::Bds20(decoded))
.map_err(|e| DecodingError::DecodingFailed(e.to_string())),
0x21 => {
AircraftAndAirlineRegistrationMarkings::from_bytes((payload, 0))
.map(|(_, decoded)| DecodedBds::Bds21(decoded))
.map_err(|e| DecodingError::DecodingFailed(e.to_string()))
}
0x30 => ACASResolutionAdvisory::from_bytes((payload, 0))
.map(|(_, decoded)| DecodedBds::Bds30(decoded))
.map_err(|e| DecodingError::DecodingFailed(e.to_string())),
0x40 => SelectedVerticalIntention::from_bytes((payload, 0))
.map(|(_, decoded)| DecodedBds::Bds40(decoded))
.map_err(|e| DecodingError::DecodingFailed(e.to_string())),
0x44 => MeteorologicalRoutineAirReport::from_bytes((payload, 0))
.map(|(_, decoded)| DecodedBds::Bds44(decoded))
.map_err(|e| DecodingError::DecodingFailed(e.to_string())),
0x45 => MeteorologicalHazardReport::from_bytes((payload, 0))
.map(|(_, decoded)| DecodedBds::Bds45(decoded))
.map_err(|e| DecodingError::DecodingFailed(e.to_string())),
0x50 => TrackAndTurnReport::from_bytes((payload, 0))
.map(|(_, decoded)| DecodedBds::Bds50(decoded))
.map_err(|e| DecodingError::DecodingFailed(e.to_string())),
0x60 => HeadingAndSpeedReport::from_bytes((payload, 0))
.map(|(_, decoded)| DecodedBds::Bds60(decoded))
.map_err(|e| DecodingError::DecodingFailed(e.to_string())),
0x61 => {
let tc = extract_tc(payload);
if tc != 28 {
return Err(DecodingError::TypeCodeMismatch {
bds_code,
received_tc: tc,
expected_tc: "28".to_string(),
});
}
AircraftStatus::from_bytes((payload, 5))
.map(|(_, decoded)| DecodedBds::Bds61(decoded))
.map_err(|e| DecodingError::DecodingFailed(e.to_string()))
}
0x62 => {
let tc = extract_tc(payload);
if tc != 29 {
return Err(DecodingError::TypeCodeMismatch {
bds_code,
received_tc: tc,
expected_tc: "29".to_string(),
});
}
TargetStateAndStatusInformation::from_bytes((payload, 5))
.map(|(_, decoded)| DecodedBds::Bds62(decoded))
.map_err(|e| DecodingError::DecodingFailed(e.to_string()))
}
0x65 => {
let tc = extract_tc(payload);
if tc != 31 {
return Err(DecodingError::TypeCodeMismatch {
bds_code,
received_tc: tc,
expected_tc: "31".to_string(),
});
}
AircraftOperationStatus::from_bytes((payload, 5))
.map(|(_, decoded)| DecodedBds::Bds65(decoded))
.map_err(|e| DecodingError::DecodingFailed(e.to_string()))
}
_ => Err(DecodingError::InvalidBdsCode(bds_code)),
}
}
pub fn infer_bds(payload: &[u8]) -> Vec<DecodedBds> {
if payload.is_empty() {
return Vec::new();
}
let mut results = Vec::new();
for bds_code in &[
0x05u8, 0x06, 0x08, 0x09, 0x10, 0x17, 0x18, 0x19, 0x20, 0x21, 0x30,
0x40, 0x44, 0x45, 0x50, 0x60, 0x61, 0x62, 0x65,
] {
if let Ok(decoded) = decode_bds(payload, *bds_code) {
results.push(decoded);
}
}
results
}