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