_rust/
lib.rs

1#![allow(rustdoc::missing_crate_level_docs)]
2
3use pyo3::exceptions::{PyAssertionError, PyValueError};
4use pyo3::prelude::*;
5use pyo3::types::PyDict;
6use rayon::prelude::*;
7use rs1090::data::patterns::{
8    aircraft_information as patterns, AircraftInformation,
9};
10use rs1090::decode::bds::bds05::AirbornePosition;
11use rs1090::decode::bds::bds10::DataLinkCapability;
12use rs1090::decode::bds::bds17::CommonUsageGICBCapabilityReport;
13use rs1090::decode::bds::bds18::GICBCapabilityReportPart1;
14use rs1090::decode::bds::bds19::GICBCapabilityReportPart2;
15use rs1090::decode::bds::bds20::AircraftIdentification;
16use rs1090::decode::bds::bds21::AircraftAndAirlineRegistrationMarkings;
17use rs1090::decode::bds::bds30::ACASResolutionAdvisory;
18use rs1090::decode::bds::bds40::SelectedVerticalIntention;
19use rs1090::decode::bds::bds44::MeteorologicalRoutineAirReport;
20use rs1090::decode::bds::bds45::MeteorologicalHazardReport;
21use rs1090::decode::bds::bds50::TrackAndTurnReport;
22use rs1090::decode::bds::bds60::HeadingAndSpeedReport;
23use rs1090::decode::bds::bds65::AircraftOperationStatus;
24use rs1090::decode::cpr::{
25    airborne_position_with_reference, decode_positions,
26    surface_position_with_reference, Position,
27};
28use rs1090::decode::flarm::Flarm;
29use rs1090::prelude::*;
30
31#[pyfunction]
32fn decode_1090(msg: String) -> PyResult<Vec<u8>> {
33    let bytes = hex::decode(msg).unwrap();
34    if let Ok((_, msg)) = Message::from_bytes((&bytes, 0)) {
35        let pkl = serde_pickle::to_vec(&msg, Default::default()).unwrap();
36        Ok(pkl)
37    } else {
38        Ok([128, 4, 78, 46].to_vec()) // None
39    }
40}
41
42fn decode_message_with_reference(me: &mut ME, reference: [f64; 2]) {
43    let [latitude_ref, longitude_ref] = reference;
44    match me {
45        ME::BDS05 {
46            inner: airborne, ..
47        } => {
48            if let Some(pos) = airborne_position_with_reference(
49                airborne,
50                latitude_ref,
51                longitude_ref,
52            ) {
53                airborne.latitude = Some(pos.latitude);
54                airborne.longitude = Some(pos.longitude);
55            }
56        }
57        ME::BDS06 { inner: surface, .. } => {
58            if let Some(pos) = surface_position_with_reference(
59                surface,
60                latitude_ref,
61                longitude_ref,
62            ) {
63                surface.latitude = Some(pos.latitude);
64                surface.longitude = Some(pos.longitude);
65            }
66        }
67        _ => (),
68    }
69}
70
71#[pyfunction]
72fn decode_1090_with_reference(
73    msg: String,
74    reference: [f64; 2],
75) -> PyResult<Vec<u8>> {
76    let bytes = hex::decode(msg).unwrap();
77    if let Ok((_, mut msg)) = Message::from_bytes((&bytes, 0)) {
78        match &mut msg.df {
79            ExtendedSquitterTisB { cf, .. } => {
80                decode_message_with_reference(&mut cf.me, reference)
81            }
82            ExtendedSquitterADSB(adsb) => {
83                decode_message_with_reference(&mut adsb.message, reference)
84            }
85            _ => {}
86        }
87        let pkl = serde_pickle::to_vec(&msg, Default::default()).unwrap();
88        Ok(pkl)
89    } else {
90        Ok([128, 4, 78, 46].to_vec()) // None
91    }
92}
93
94struct DecodeError(DekuError);
95
96impl From<DecodeError> for PyErr {
97    fn from(error: DecodeError) -> Self {
98        match error.0 {
99            DekuError::Assertion(msg) => PyAssertionError::new_err(msg),
100            _ => PyValueError::new_err(error.0.to_string()),
101        }
102    }
103}
104
105#[pyfunction]
106fn decode_bds05(msg: String) -> PyResult<Vec<u8>> {
107    let bytes = hex::decode(msg).unwrap();
108    let tc = &bytes[4] >> 3;
109    if (9..22).contains(&tc) && tc != 19 {
110        let mut input = std::io::Cursor::new(&bytes[4..]);
111        let mut reader = Reader::new(&mut input);
112        match AirbornePosition::from_reader_with_ctx(&mut reader, tc) {
113            Ok(msg) => {
114                let pkl =
115                    serde_pickle::to_vec(&msg, Default::default()).unwrap();
116                Ok(pkl)
117            }
118            Err(e) => Err(DecodeError(e).into()),
119        }
120    } else {
121        let msg =
122            format!("Invalid typecode {tc} for BDS 0,5 (9 to 18 or 20 to 22)");
123        Err(PyAssertionError::new_err(msg))
124    }
125}
126
127#[pyfunction]
128fn decode_bds10(msg: String) -> PyResult<Vec<u8>> {
129    let bytes = hex::decode(msg).unwrap();
130    match DataLinkCapability::from_bytes((&bytes[4..], 0)) {
131        Ok((_, msg)) => {
132            let pkl = serde_pickle::to_vec(&msg, Default::default()).unwrap();
133            Ok(pkl)
134        }
135        Err(e) => Err(DecodeError(e).into()),
136    }
137}
138
139#[pyfunction]
140fn decode_bds17(msg: String) -> PyResult<Vec<u8>> {
141    let bytes = hex::decode(msg).unwrap();
142    match CommonUsageGICBCapabilityReport::from_bytes((&bytes[4..], 0)) {
143        Ok((_, msg)) => {
144            let pkl = serde_pickle::to_vec(&msg, Default::default()).unwrap();
145            Ok(pkl)
146        }
147        Err(e) => Err(DecodeError(e).into()),
148    }
149}
150
151#[pyfunction]
152fn decode_bds18(msg: String) -> PyResult<Vec<u8>> {
153    let bytes = hex::decode(msg).unwrap();
154    match GICBCapabilityReportPart1::from_bytes((&bytes[4..], 0)) {
155        Ok((_, msg)) => {
156            let pkl = serde_pickle::to_vec(&msg, Default::default()).unwrap();
157            Ok(pkl)
158        }
159        Err(e) => Err(DecodeError(e).into()),
160    }
161}
162
163#[pyfunction]
164fn decode_bds19(msg: String) -> PyResult<Vec<u8>> {
165    let bytes = hex::decode(msg).unwrap();
166    match GICBCapabilityReportPart2::from_bytes((&bytes[4..], 0)) {
167        Ok((_, msg)) => {
168            let pkl = serde_pickle::to_vec(&msg, Default::default()).unwrap();
169            Ok(pkl)
170        }
171        Err(e) => Err(DecodeError(e).into()),
172    }
173}
174
175#[pyfunction]
176fn decode_bds20(msg: String) -> PyResult<Vec<u8>> {
177    let bytes = hex::decode(msg).unwrap();
178    match AircraftIdentification::from_bytes((&bytes[4..], 0)) {
179        Ok((_, msg)) => {
180            let pkl = serde_pickle::to_vec(&msg, Default::default()).unwrap();
181            Ok(pkl)
182        }
183        Err(e) => Err(DecodeError(e).into()),
184    }
185}
186
187#[pyfunction]
188fn decode_bds21(msg: String) -> PyResult<Vec<u8>> {
189    let bytes = hex::decode(msg).unwrap();
190    match AircraftAndAirlineRegistrationMarkings::from_bytes((&bytes[4..], 0)) {
191        Ok((_, msg)) => {
192            let pkl = serde_pickle::to_vec(&msg, Default::default()).unwrap();
193            Ok(pkl)
194        }
195        Err(e) => Err(DecodeError(e).into()),
196    }
197}
198
199#[pyfunction]
200fn decode_bds30(msg: String) -> PyResult<Vec<u8>> {
201    let bytes = hex::decode(msg).unwrap();
202    match ACASResolutionAdvisory::from_bytes((&bytes[4..], 0)) {
203        Ok((_, msg)) => {
204            let pkl = serde_pickle::to_vec(&msg, Default::default()).unwrap();
205            Ok(pkl)
206        }
207        Err(e) => Err(DecodeError(e).into()),
208    }
209}
210
211#[pyfunction]
212fn decode_bds40(msg: String) -> PyResult<Vec<u8>> {
213    let bytes = hex::decode(msg).unwrap();
214    match SelectedVerticalIntention::from_bytes((&bytes[4..], 0)) {
215        Ok((_, msg)) => {
216            let pkl = serde_pickle::to_vec(&msg, Default::default()).unwrap();
217            Ok(pkl)
218        }
219        Err(e) => Err(DecodeError(e).into()),
220    }
221}
222
223#[pyfunction]
224fn decode_bds44(msg: String) -> PyResult<Vec<u8>> {
225    let bytes = hex::decode(msg).unwrap();
226    match MeteorologicalRoutineAirReport::from_bytes((&bytes[4..], 0)) {
227        Ok((_, msg)) => {
228            let pkl = serde_pickle::to_vec(&msg, Default::default()).unwrap();
229            Ok(pkl)
230        }
231        Err(e) => Err(DecodeError(e).into()),
232    }
233}
234#[pyfunction]
235fn decode_bds45(msg: String) -> PyResult<Vec<u8>> {
236    let bytes = hex::decode(msg).unwrap();
237    match MeteorologicalHazardReport::from_bytes((&bytes[4..], 0)) {
238        Ok((_, msg)) => {
239            let pkl = serde_pickle::to_vec(&msg, Default::default()).unwrap();
240            Ok(pkl)
241        }
242        Err(e) => Err(DecodeError(e).into()),
243    }
244}
245
246#[pyfunction]
247fn decode_bds50(msg: String) -> PyResult<Vec<u8>> {
248    let bytes = hex::decode(msg).unwrap();
249    match TrackAndTurnReport::from_bytes((&bytes[4..], 0)) {
250        Ok((_, msg)) => {
251            let pkl = serde_pickle::to_vec(&msg, Default::default()).unwrap();
252            Ok(pkl)
253        }
254        Err(e) => Err(DecodeError(e).into()),
255    }
256}
257
258#[pyfunction]
259fn decode_bds60(msg: String) -> PyResult<Vec<u8>> {
260    let bytes = hex::decode(msg).unwrap();
261    match HeadingAndSpeedReport::from_bytes((&bytes[4..], 0)) {
262        Ok((_, msg)) => {
263            let pkl = serde_pickle::to_vec(&msg, Default::default()).unwrap();
264            Ok(pkl)
265        }
266        Err(e) => Err(DecodeError(e).into()),
267    }
268}
269
270#[pyfunction]
271fn decode_bds65(msg: String) -> PyResult<Vec<u8>> {
272    let bytes = hex::decode(msg).unwrap();
273    let tc = &bytes[4] >> 3;
274    let enum_id = &bytes[4] & 0b111;
275    match (tc, enum_id) {
276        (31, id) if id < 2 => {
277            match AircraftOperationStatus::from_bytes((&bytes[4..], 0)) {
278                Ok((_, msg)) => {
279                    let pkl =
280                        serde_pickle::to_vec(&msg, Default::default()).unwrap();
281                    Ok(pkl)
282                }
283                Err(e) => Err(DecodeError(e).into()),
284            }
285        }
286        _ => Err(PyAssertionError::new_err(format!(
287            "Invalid typecode {tc} (31) or category {enum_id} (0 or 1) for BDS 6,5"
288        ))),
289    }
290}
291
292#[pyfunction]
293fn decode_1090_vec(msgs_set: Vec<Vec<String>>) -> PyResult<Vec<u8>> {
294    let res: Vec<Option<Message>> = msgs_set
295        .par_iter()
296        .map(|msgs| {
297            msgs.iter()
298                .map(|msg| {
299                    let bytes = hex::decode(msg).unwrap();
300                    if let Ok((_, msg)) = Message::from_bytes((&bytes, 0)) {
301                        Some(msg)
302                    } else {
303                        None
304                    }
305                })
306                .collect()
307        })
308        .flat_map(|v: Vec<Option<Message>>| v)
309        .collect();
310    let pkl = serde_pickle::to_vec(&res, Default::default()).unwrap();
311    Ok(pkl)
312}
313
314#[pyfunction]
315#[pyo3(signature = (msgs_set, ts_set, reference=None))]
316fn decode_1090t_vec(
317    msgs_set: Vec<Vec<String>>,
318    ts_set: Vec<Vec<f64>>,
319    reference: Option<[f64; 2]>,
320) -> PyResult<Vec<u8>> {
321    let mut res: Vec<TimedMessage> = msgs_set
322        .par_iter()
323        .zip(ts_set)
324        .map(|(msgs, ts)| {
325            msgs.iter()
326                .zip(ts)
327                .filter_map(|(msg, timestamp)| {
328                    let bytes = hex::decode(msg).unwrap();
329                    if let Ok((_, message)) = Message::from_bytes((&bytes, 0)) {
330                        Some(TimedMessage {
331                            timestamp,
332                            frame: bytes,
333                            message: Some(message),
334                            metadata: vec![],
335                            decode_time: None,
336                        })
337                    } else {
338                        None
339                    }
340                })
341                .collect()
342        })
343        .flat_map(|v: Vec<TimedMessage>| v)
344        .collect();
345
346    let position = reference.map(|[latitude, longitude]| Position {
347        latitude,
348        longitude,
349    });
350    decode_positions(&mut res, position, &None);
351
352    let pkl = serde_pickle::to_vec(&res, Default::default()).unwrap();
353    Ok(pkl)
354}
355
356#[pyfunction]
357fn decode_flarm(
358    msg: String,
359    ts: u32,
360    reflat: f64,
361    reflon: f64,
362) -> PyResult<Vec<u8>> {
363    let bytes = hex::decode(msg).unwrap();
364    let reference = [reflat, reflon];
365    if let Ok(msg) = Flarm::from_record(ts, &reference, &bytes) {
366        let pkl = serde_pickle::to_vec(&msg, Default::default()).unwrap();
367        Ok(pkl)
368    } else {
369        Ok([128, 4, 78, 46].to_vec()) // None
370    }
371}
372
373#[pyfunction]
374fn decode_flarm_vec(
375    msgs_set: Vec<Vec<String>>,
376    ts_set: Vec<Vec<u32>>,
377    ref_lat: Vec<Vec<f64>>,
378    ref_lon: Vec<Vec<f64>>,
379) -> PyResult<Vec<u8>> {
380    let reference: Vec<Vec<[f64; 2]>> = ref_lat
381        .iter()
382        .zip(ref_lon.iter())
383        .map(|(lat, lon)| {
384            lat.iter()
385                .zip(lon.iter())
386                .map(|(lat, lon)| [*lat, *lon])
387                .collect()
388        })
389        .collect();
390    let res: Vec<Flarm> = msgs_set
391        .par_iter()
392        .zip(ts_set)
393        .zip(reference)
394        .map(|((msgs, ts), reference)| {
395            msgs.iter()
396                .zip(ts)
397                .zip(reference)
398                .filter_map(|((msg, timestamp), reference)| {
399                    let bytes = hex::decode(msg).unwrap();
400                    Flarm::from_record(timestamp, &reference, &bytes).ok()
401                })
402                .collect()
403        })
404        .flat_map(|v: Vec<Flarm>| v)
405        .collect();
406
407    let pkl = serde_pickle::to_vec(&res, Default::default()).unwrap();
408    Ok(pkl)
409}
410
411struct WrapAircraftInfo(AircraftInformation);
412
413impl<'a> IntoPyObject<'a> for WrapAircraftInfo {
414    type Target = PyDict;
415    type Output = Bound<'a, Self::Target>;
416    type Error = PyErr;
417
418    fn into_pyobject(
419        self,
420        py: Python<'a>,
421    ) -> Result<Self::Output, Self::Error> {
422        let dict = PyDict::new(py);
423        dict.set_item("icao24", self.0.icao24)?;
424        if let Some(registration) = self.0.registration {
425            dict.set_item("registration", registration)?;
426        }
427        dict.set_item(
428            "country",
429            self.0.country.or(Some("Unknown".to_string())),
430        )?;
431        dict.set_item("flag", self.0.flag.or(Some("🏳".to_string())))?;
432        if let Some(pattern) = self.0.pattern {
433            dict.set_item("pattern", pattern)?;
434        }
435        if let Some(category) = self.0.category {
436            dict.set_item("category", category)?;
437        }
438        if let Some(comment) = self.0.comment {
439            dict.set_item("comment", comment)?;
440        }
441        Ok(dict)
442    }
443}
444
445#[pyfunction]
446#[pyo3(signature = (icao24, registration=None))]
447fn aircraft_information(
448    icao24: &str,
449    registration: Option<&str>,
450) -> PyResult<WrapAircraftInfo> {
451    Ok(WrapAircraftInfo(patterns(icao24, registration)?))
452}
453
454/// A Python module implemented in Rust.
455#[pymodule]
456fn _rust(m: &Bound<'_, PyModule>) -> PyResult<()> {
457    // Decoding functions
458    m.add_function(wrap_pyfunction!(decode_1090, m)?)?;
459    m.add_function(wrap_pyfunction!(decode_1090_with_reference, m)?)?;
460    m.add_function(wrap_pyfunction!(decode_1090_vec, m)?)?;
461    m.add_function(wrap_pyfunction!(decode_1090t_vec, m)?)?;
462    m.add_function(wrap_pyfunction!(decode_flarm, m)?)?;
463    m.add_function(wrap_pyfunction!(decode_flarm_vec, m)?)?;
464
465    // Comm-B BDS inference
466    m.add_function(wrap_pyfunction!(decode_bds05, m)?)?;
467    m.add_function(wrap_pyfunction!(decode_bds10, m)?)?;
468    m.add_function(wrap_pyfunction!(decode_bds17, m)?)?;
469    m.add_function(wrap_pyfunction!(decode_bds18, m)?)?;
470    m.add_function(wrap_pyfunction!(decode_bds19, m)?)?;
471    m.add_function(wrap_pyfunction!(decode_bds20, m)?)?;
472    m.add_function(wrap_pyfunction!(decode_bds21, m)?)?;
473    m.add_function(wrap_pyfunction!(decode_bds30, m)?)?;
474    m.add_function(wrap_pyfunction!(decode_bds40, m)?)?;
475    m.add_function(wrap_pyfunction!(decode_bds44, m)?)?;
476    m.add_function(wrap_pyfunction!(decode_bds45, m)?)?;
477    m.add_function(wrap_pyfunction!(decode_bds50, m)?)?;
478    m.add_function(wrap_pyfunction!(decode_bds60, m)?)?;
479    m.add_function(wrap_pyfunction!(decode_bds65, m)?)?;
480
481    // icao24 functions
482    m.add_function(wrap_pyfunction!(aircraft_information, m)?)?;
483
484    Ok(())
485}