_rust/
lib.rs

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