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