Skip to main content

rs1090_wasm/
lib.rs

1#![allow(rustdoc::missing_crate_level_docs)]
2
3mod utils;
4
5use js_sys::Object;
6use rs1090::data::airports::AIRPORTS;
7use rs1090::data::patterns::aircraft_information as patterns;
8use rs1090::decode::bds::bds05::AirbornePosition;
9use rs1090::decode::bds::bds10::DataLinkCapability;
10use rs1090::decode::bds::bds17::CommonUsageGICBCapabilityReport;
11use rs1090::decode::bds::bds18::GICBCapabilityReportPart1;
12use rs1090::decode::bds::bds19::GICBCapabilityReportPart2;
13use rs1090::decode::bds::bds20::AircraftIdentification;
14use rs1090::decode::bds::bds21::AircraftAndAirlineRegistrationMarkings;
15use rs1090::decode::bds::bds30::ACASResolutionAdvisory;
16use rs1090::decode::bds::bds40::SelectedVerticalIntention;
17use rs1090::decode::bds::bds44::MeteorologicalRoutineAirReport;
18use rs1090::decode::bds::bds45::MeteorologicalHazardReport;
19use rs1090::decode::bds::bds50::TrackAndTurnReport;
20use rs1090::decode::bds::bds60::HeadingAndSpeedReport;
21use rs1090::decode::cpr::{
22    airborne_position_with_reference, surface_position_with_reference,
23};
24use rs1090::prelude::*;
25use utils::set_panic_hook;
26use wasm_bindgen::prelude::*;
27
28#[wasm_bindgen]
29pub fn run() -> Result<(), JsValue> {
30    set_panic_hook();
31    Ok(())
32}
33
34struct DecodeError(DekuError);
35
36impl From<DecodeError> for JsError {
37    fn from(error: DecodeError) -> Self {
38        JsError::new(&format!("{}", error.0))
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#[wasm_bindgen]
72pub fn decode(
73    msg: &str,
74    reference: Option<Vec<f64>>,
75) -> Result<JsValue, JsError> {
76    let bytes = hex::decode(msg)?;
77    match Message::try_from(bytes.as_slice()) {
78        Ok(mut msg) => {
79            if let Some(reference) = reference.map(|v| [v[0], v[1]]) {
80                match &mut msg.df {
81                    ExtendedSquitterTisB { cf, .. } => {
82                        decode_message_with_reference(&mut cf.me, reference)
83                    }
84                    ExtendedSquitterADSB(adsb) => {
85                        decode_message_with_reference(
86                            &mut adsb.message,
87                            reference,
88                        )
89                    }
90                    _ => {}
91                }
92            }
93            let map_result = serde_wasm_bindgen::to_value(&msg)?;
94            Ok(Object::from_entries(&map_result).unwrap().into())
95        }
96        Err(e) => Err(DecodeError(e).into()),
97    }
98}
99
100#[wasm_bindgen]
101pub fn decode_bds05(msg: &str) -> Result<JsValue, JsError> {
102    let bytes = hex::decode(msg)?;
103    let tc = &bytes[4] >> 3;
104    if (9..22).contains(&tc) && tc != 19 {
105        let mut input = std::io::Cursor::new(&bytes[4..]);
106        let mut reader = Reader::new(&mut input);
107        match AirbornePosition::from_reader_with_ctx(&mut reader, tc) {
108            Ok(msg) => {
109                let map_result = serde_wasm_bindgen::to_value(&msg)?;
110                Ok(map_result)
111            }
112            Err(e) => Err(DecodeError(e).into()),
113        }
114    } else {
115        let msg =
116            format!("Invalid typecode {tc} for BDS 0,5 (9 to 18 or 20 to 22)");
117        Err(DecodeError(DekuError::Assertion(msg.into())).into())
118    }
119}
120
121#[wasm_bindgen]
122pub fn decode_bds10(msg: &str) -> Result<JsValue, JsError> {
123    let bytes = hex::decode(msg)?;
124    match DataLinkCapability::from_bytes((&bytes[4..], 0)) {
125        Ok((_, msg)) => {
126            let map_result = serde_wasm_bindgen::to_value(&msg)?;
127            Ok(map_result)
128        }
129        Err(e) => Err(DecodeError(e).into()),
130    }
131}
132
133#[wasm_bindgen]
134pub fn decode_bds17(msg: &str) -> Result<JsValue, JsError> {
135    let bytes = hex::decode(msg)?;
136    match CommonUsageGICBCapabilityReport::from_bytes((&bytes[4..], 0)) {
137        Ok((_, msg)) => {
138            let map_result = serde_wasm_bindgen::to_value(&msg)?;
139            Ok(map_result)
140        }
141        Err(e) => Err(DecodeError(e).into()),
142    }
143}
144
145#[wasm_bindgen]
146pub fn decode_bds18(msg: &str) -> Result<JsValue, JsError> {
147    let bytes = hex::decode(msg)?;
148    match GICBCapabilityReportPart1::from_bytes((&bytes[4..], 0)) {
149        Ok((_, msg)) => {
150            let map_result = serde_wasm_bindgen::to_value(&msg)?;
151            Ok(map_result)
152        }
153        Err(e) => Err(DecodeError(e).into()),
154    }
155}
156
157#[wasm_bindgen]
158pub fn decode_bds19(msg: &str) -> Result<JsValue, JsError> {
159    let bytes = hex::decode(msg)?;
160    match GICBCapabilityReportPart2::from_bytes((&bytes[4..], 0)) {
161        Ok((_, msg)) => {
162            let map_result = serde_wasm_bindgen::to_value(&msg)?;
163            Ok(map_result)
164        }
165        Err(e) => Err(DecodeError(e).into()),
166    }
167}
168
169#[wasm_bindgen]
170pub fn decode_bds20(msg: &str) -> Result<JsValue, JsError> {
171    let bytes = hex::decode(msg)?;
172    match AircraftIdentification::from_bytes((&bytes[4..], 0)) {
173        Ok((_, msg)) => {
174            let map_result = serde_wasm_bindgen::to_value(&msg)?;
175            Ok(map_result)
176        }
177        Err(e) => Err(DecodeError(e).into()),
178    }
179}
180
181#[wasm_bindgen]
182pub fn decode_bds21(msg: &str) -> Result<JsValue, JsError> {
183    let bytes = hex::decode(msg)?;
184    match AircraftAndAirlineRegistrationMarkings::from_bytes((&bytes[4..], 0)) {
185        Ok((_, msg)) => {
186            let map_result = serde_wasm_bindgen::to_value(&msg)?;
187            Ok(map_result)
188        }
189        Err(e) => Err(DecodeError(e).into()),
190    }
191}
192
193#[wasm_bindgen]
194pub fn decode_bds30(msg: &str) -> Result<JsValue, JsError> {
195    let bytes = hex::decode(msg)?;
196    match ACASResolutionAdvisory::from_bytes((&bytes[4..], 0)) {
197        Ok((_, msg)) => {
198            let map_result = serde_wasm_bindgen::to_value(&msg)?;
199            Ok(map_result)
200        }
201        Err(e) => Err(DecodeError(e).into()),
202    }
203}
204
205#[wasm_bindgen]
206pub fn decode_bds40(msg: &str) -> Result<JsValue, JsError> {
207    let bytes = hex::decode(msg)?;
208    match SelectedVerticalIntention::from_bytes((&bytes[4..], 0)) {
209        Ok((_, msg)) => {
210            let map_result = serde_wasm_bindgen::to_value(&msg)?;
211            Ok(map_result)
212        }
213        Err(e) => Err(DecodeError(e).into()),
214    }
215}
216
217#[wasm_bindgen]
218pub fn decode_bds44(msg: &str) -> Result<JsValue, JsError> {
219    let bytes = hex::decode(msg)?;
220    match MeteorologicalRoutineAirReport::from_bytes((&bytes[4..], 0)) {
221        Ok((_, msg)) => {
222            let map_result = serde_wasm_bindgen::to_value(&msg)?;
223            Ok(map_result)
224        }
225        Err(e) => Err(DecodeError(e).into()),
226    }
227}
228
229#[wasm_bindgen]
230pub fn decode_bds45(msg: &str) -> Result<JsValue, JsError> {
231    let bytes = hex::decode(msg)?;
232    match MeteorologicalHazardReport::from_bytes((&bytes[4..], 0)) {
233        Ok((_, msg)) => {
234            let map_result = serde_wasm_bindgen::to_value(&msg)?;
235            Ok(map_result)
236        }
237        Err(e) => Err(DecodeError(e).into()),
238    }
239}
240
241#[wasm_bindgen]
242pub fn decode_bds50(msg: &str) -> Result<JsValue, JsError> {
243    let bytes = hex::decode(msg)?;
244    match TrackAndTurnReport::from_bytes((&bytes[4..], 0)) {
245        Ok((_, msg)) => {
246            let map_result = serde_wasm_bindgen::to_value(&msg)?;
247            Ok(map_result)
248        }
249        Err(e) => Err(DecodeError(e).into()),
250    }
251}
252
253#[wasm_bindgen]
254pub fn decode_bds60(msg: &str) -> Result<JsValue, JsError> {
255    let bytes = hex::decode(msg)?;
256    match HeadingAndSpeedReport::from_bytes((&bytes[4..], 0)) {
257        Ok((_, msg)) => {
258            let map_result = serde_wasm_bindgen::to_value(&msg)?;
259            Ok(map_result)
260        }
261        Err(e) => Err(DecodeError(e).into()),
262    }
263}
264
265#[wasm_bindgen]
266pub fn decode_bds65(msg: &str) -> Result<JsValue, JsError> {
267    let bytes = hex::decode(msg)?;
268    let tc = &bytes[4] >> 3;
269    let enum_id = &bytes[4] & 0b111;
270    match (tc, enum_id) {
271        (31, id) if id < 2 => {
272            match AircraftOperationStatus::from_bytes((&bytes[4..], 0)) {
273                Ok((_, msg)) => {
274                    let map_result = serde_wasm_bindgen::to_value(&msg)?;
275                    Ok(map_result)
276                }
277                Err(e) => Err(DecodeError(e).into()),
278            }
279        }
280        _ => {
281            let msg = format!(
282                "Invalid typecode {tc} (31) or category {enum_id} (0 or 1) for BDS 6,5"
283            );
284            Err(DecodeError(DekuError::Assertion(msg.into())).into())
285        }
286    }
287}
288
289#[wasm_bindgen]
290pub fn aircraft_information(
291    icao24: &str,
292    registration: Option<String>,
293) -> Result<JsValue, JsError> {
294    match patterns(icao24, registration.as_deref()) {
295        Ok(res) => {
296            let js_result = serde_wasm_bindgen::to_value(&res)?;
297            Ok(js_result)
298        }
299        Err(_) => Err(JsError::new("invalid icao24 value")),
300    }
301}
302
303#[wasm_bindgen]
304pub fn airport_information(query: &str) -> Result<JsValue, JsError> {
305    let lowercase = query.to_lowercase();
306    let res: Vec<_> = AIRPORTS
307        .iter()
308        .filter(|a| {
309            a.name.to_lowercase().contains(&lowercase)
310                || a.city.to_lowercase().contains(&lowercase)
311                || a.icao.to_lowercase().contains(&lowercase)
312                || a.iata.to_lowercase().contains(&lowercase)
313        })
314        .collect();
315    Ok(serde_wasm_bindgen::to_value(&res)?)
316}