Skip to main content

c_its_parser/
de.rs

1//! C-ITS Message Decoding
2//!
3//! Provides Rust and wasm functions to decode messages
4
5#![allow(non_snake_case)]
6
7#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
8use wasm_bindgen::prelude::*;
9
10#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
11use crate::transport::decode::Decode as _;
12#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
13macro_rules! btp {
14    ($btp_ty:ty, $input:ident) => {
15        <$btp_ty>::decode($input)
16            .map_err(crate::map_err_to_string)
17            .and_then(|(rem, tp)| {
18                tp.encode_to_json()
19                    .map_err(crate::map_err_to_string)
20                    .map(|json| (rem, json))
21            })
22    };
23}
24
25#[cfg(feature = "_etsi")]
26#[allow(clippy::too_many_lines)]
27/// Decodes an ASN.1 message with headers. Supported encoding rules are UPER, JER, and XER. JSON and XML strings are expected as UTF-8 slices.
28///
29/// # Params
30///  - `input`: binary input containing the ITS message
31///  - `headers`: indicate which headers are present in the binary input. GeoNetworking and transport headers will be decoded and returned, other headers will be skipped.
32///
33/// # Notes
34/// CDD 2.2.1 has changed the capitalization of the `messageId` and `stationId` data fields compared to CDD 1.3.1.
35/// This didn't change the UPER encoding and therefore hasn't lead to a new protocol version in the ITS PDU header, but changes the XER and JER encodings.
36/// There's a fallback in place to handle XER or JER-encoded DENM, IVIM, MAPEM, SPATEM, SREM and SSEM messages which were encoded using the old CDD.
37///
38/// # Errors
39/// Throws string error on decoding errors.
40pub fn decode(
41    input: &'_ [u8],
42    headers: crate::Headers,
43) -> Result<crate::ItsMessage<'_>, alloc::string::String> {
44    use alloc::borrow::ToOwned as _;
45    use alloc::string::ToString as _;
46
47    use crate::{ItsMessage, standards};
48
49    let (input, transport, geonetworking) = match headers {
50        crate::Headers::None => Ok((input, None, None)),
51        crate::Headers::GnBtp => {
52            decode_gn_btp_headers(input).map(|(rem, tp, gn)| (rem, Some(tp), Some(gn)))
53        }
54        crate::Headers::IEEE802LlcGnBtp => crate::pcap::remove_wlan_headers(input)
55            .and_then(decode_gn_btp_headers)
56            .map(|(rem, tp, gn)| (rem, Some(tp), Some(gn))),
57        crate::Headers::RadioTap802LlcGnBtp => crate::pcap::remove_pcap_headers(input)
58            .and_then(decode_gn_btp_headers)
59            .map(|(rem, tp, gn)| (rem, Some(tp), Some(gn))),
60    }?;
61    let (encoding_rules, mut protocol_version, msg_type) = message_type(input)?;
62
63    let input = match msg_type {
64        // workaround to parse DENM and IVIM as XER/ JER which still uses old CDD
65        1 | 6 => {
66            if let Ok(data) = core::primitive::str::from_utf8(input)
67                && (data.trim_start().starts_with('<') || data.trim_start().starts_with('{'))
68                && data.contains("messageID")
69            {
70                // CDD 2.2.1 is using slightly different names for some things that CDD 1.3.1, e.g.
71                // `messageID` (and other IDs) were changed to `messageId`, etc.
72                // The IVIM 2.2.1 also changed how other things are named.
73                // So we're using a fictional protocol_version 1 to fall into the right message type
74                // in the match statement below.
75                protocol_version = 1;
76            }
77            input.to_owned()
78        }
79        // workaround to parse MAPEM, SPATEM, SREM, SSEM as XER/ JER which still uses old CDD
80        4 | 5 | 9 | 10 => {
81            if let Ok(data) = core::primitive::str::from_utf8(input) {
82                // live-patch messageID and stationID in PDU header for MAPEMs, SPATEMs, SREMs and SSEMs
83                // Note: an SREM may contain stationID in the requestor, but that's actually fine!!! So we shall only patch the PDU header
84                if data.trim_start().starts_with('<') && data.contains("messageID") {
85                    // XML end tags are not allowed to contain a space between the `</` and the name, so this find is safe to use
86                    let patched_msg = match data.find("</header") {
87                        Some(header_end_pos) => {
88                            let (header, remains) = data.split_at(header_end_pos);
89
90                            let patched_msg = header.replace("messageID", "messageId");
91                            let patched_msg = patched_msg.replace("stationID", "stationId");
92
93                            patched_msg + remains
94                        }
95                        None => data.to_string(),
96                    };
97
98                    patched_msg.as_bytes().to_owned()
99                } else if data.trim_start().starts_with('{') && data.contains("messageID") {
100                    let patched_msg = data.replace("messageID", "messageId");
101
102                    // we can't easily find the end of the header in JSON, so just replace the first occurrence
103                    let patched_msg = patched_msg.replacen("stationID", "stationId", 1);
104
105                    patched_msg.as_bytes().to_owned()
106                } else {
107                    input.to_owned()
108                }
109            } else {
110                input.to_owned()
111            }
112        }
113        _ => input.to_owned(),
114    };
115
116    match (msg_type, protocol_version) {
117        #[cfg(feature = "denm_2_2_1")]
118        (1, 2) => encoding_rules
119            .codec()
120            .decode_from_binary::<standards::denm_2_2_1::denm_pdu_description::DENM>(&input)
121            .map(|etsi| ItsMessage::DenmV2 {
122                geonetworking,
123                transport,
124                etsi: alloc::boxed::Box::new(etsi)
125            }),
126        #[cfg(feature = "denm_1_3_1")]
127        (1, _) => encoding_rules
128            .codec()
129            .decode_from_binary::<standards::denm_1_3_1::denm_pdu_descriptions::DENM>(&input)
130            .map(|etsi| ItsMessage::DenmV1 {
131                geonetworking,
132                transport,
133                etsi: alloc::boxed::Box::new(etsi)
134            }),
135        #[cfg(feature = "cam_1_4_1")]
136        (2, _) => encoding_rules
137            .codec()
138            .decode_from_binary::<standards::cam_1_4_1::cam_pdu_descriptions::CAM>(&input)
139            .map(|etsi| ItsMessage::Cam {
140                geonetworking,
141                transport,
142                etsi: alloc::boxed::Box::new(etsi)
143            }),
144        #[cfg(feature = "spatem_2_2_1")]
145        (4, _) => encoding_rules
146            .codec()
147            .decode_from_binary::<standards::spatem_2_2_1::spatem_pdu_descriptions::SPATEM>(&input)
148            .map(|etsi| ItsMessage::Spatem {
149                geonetworking,
150                transport,
151                etsi: alloc::boxed::Box::new(etsi)
152            }),
153        #[cfg(feature = "mapem_2_2_1")]
154        (5, _) => encoding_rules
155            .codec()
156            .decode_from_binary::<standards::mapem_2_2_1::mapem_pdu_descriptions::MAPEM>(&input)
157            .map(|etsi| ItsMessage::Mapem {
158                geonetworking,
159                transport,
160                etsi: alloc::boxed::Box::new(etsi)
161            }),
162        #[cfg(feature = "ivim_2_2_1")]
163        (6, 2) => encoding_rules
164            .codec()
165            .decode_from_binary::<standards::ivim_2_2_1::ivim_pdu_descriptions::IVIM>(&input)
166            .map(|etsi| ItsMessage::IvimV2 {
167                geonetworking,
168                transport,
169                etsi: alloc::boxed::Box::new(etsi)
170            }),
171        #[cfg(feature = "ivim_2_1_1")]
172         (6, _) => encoding_rules
173            .codec()
174            .decode_from_binary::<standards::ivim_2_1_1::ivim_pdu_descriptions::IVIM>(&input)
175            .map(|etsi| ItsMessage::IvimV1 {
176                geonetworking,
177                transport,
178                etsi: alloc::boxed::Box::new(etsi)
179            }),
180        #[cfg(feature = "srem_2_2_1")]
181        (9, _) => encoding_rules
182            .codec()
183            .decode_from_binary::<standards::srem_2_2_1::srem_pdu_descriptions::SREM>(&input)
184            .map(|etsi| ItsMessage::Srem {
185                geonetworking,
186                transport,
187                etsi: alloc::boxed::Box::new(etsi)
188            }),
189        #[cfg(feature = "ssem_2_2_1")]
190        (10, _) => encoding_rules
191            .codec()
192            .decode_from_binary::<standards::ssem_2_2_1::ssem_pdu_descriptions::SSEM>(&input)
193            .map(|etsi| ItsMessage::Ssem {
194                geonetworking,
195                transport,
196                etsi: alloc::boxed::Box::new(etsi)
197            }),
198        #[cfg(feature = "cpm_2_1_1")]
199        (14, 2) => encoding_rules
200            .codec()
201            .decode_from_binary::<standards::cpm_2_1_1::cpm_pdu_descriptions::CollectivePerceptionMessage>(&input)
202            .map(|etsi| ItsMessage::CpmV2 {
203                geonetworking,
204                transport,
205                etsi: alloc::boxed::Box::new(etsi)
206            }),
207        #[cfg(feature = "cpm_1")]
208        (14, _) => encoding_rules
209            .codec()
210            .decode_from_binary::<standards::cpm_1::cpm_pdu_descriptions::CPM>(&input)
211            .map(|etsi| ItsMessage::CpmV1 {
212                geonetworking,
213                transport,
214                etsi: alloc::boxed::Box::new(etsi)
215            }),
216        (message_i_d, _) => {
217            return Err(alloc::format!(
218                "Unsupported ITS message type: Found message id {message_i_d}."
219            ))
220        }
221    }.map_err(crate::map_err_to_string)
222}
223
224#[cfg(feature = "transport")]
225/// Decodes the GeoNetworking and BTP headers and returns the remaining data
226///
227/// # Errors
228/// Returns human-readable error descriptions when decoding failed
229pub fn decode_gn_btp_headers(
230    input: &'_ [u8],
231) -> Result<
232    (
233        &'_ [u8],
234        alloc::boxed::Box<crate::transport::TransportHeader>,
235        geonetworking::Packet<'_>,
236    ),
237    alloc::string::String,
238> {
239    use alloc::string::ToString as _;
240
241    use geonetworking::Decode as _;
242
243    use crate::transport::decode::Decode as _;
244
245    let result = geonetworking::Packet::decode(input).map_err(crate::map_err_to_string)?;
246    let Some(payload) = result.decoded.payload() else {
247        return Err("No payload in secured geonetworking header!".to_string());
248    };
249    let (remaining, tp) = match result.decoded.common().next_header {
250        geonetworking::en302636_4_1::NextAfterCommon::Any => {
251            Err("Currently, only BTP and IPv6 Headers can be decoded!".to_string())
252        }
253        geonetworking::en302636_4_1::NextAfterCommon::BTPA => {
254            crate::transport::BasicTransportAHeader::decode(payload)
255                .map(|(rem, btpa)| (rem, crate::transport::TransportHeader::BtpA(btpa)))
256                .map_err(crate::map_err_to_string)
257        }
258        geonetworking::en302636_4_1::NextAfterCommon::BTPB => {
259            crate::transport::BasicTransportBHeader::decode(payload)
260                .map(|(rem, btpb)| (rem, crate::transport::TransportHeader::BtpB(btpb)))
261                .map_err(crate::map_err_to_string)
262        }
263        geonetworking::en302636_4_1::NextAfterCommon::IPv6 => {
264            crate::transport::IPv6Header::decode(payload)
265                .map(|(rem, ipv6)| {
266                    (
267                        rem,
268                        crate::transport::TransportHeader::IPv6(alloc::boxed::Box::new(ipv6)),
269                    )
270                })
271                .map_err(crate::map_err_to_string)
272        }
273    }?;
274    Ok((remaining, alloc::boxed::Box::new(tp), result.decoded))
275}
276
277#[cfg(all(not(target_arch = "wasm32"), feature = "_etsi"))]
278/// Decodes some ASN.1 data type from UPER buffer
279///
280/// # Errors
281/// Returns a human-readable string when decoding failed
282pub fn decode_from_uper<T: rasn::Decode>(input: &[u8]) -> Result<T, alloc::string::String> {
283    rasn::Codec::Uper
284        .decode_from_binary(input)
285        .map_err(crate::map_err_to_string)
286}
287
288#[cfg(all(not(target_arch = "wasm32"), feature = "_etsi"))]
289/// Decodes some ASN.1 data type from XER buffer
290///
291/// # Errors
292/// Returns a human-readable string when decoding failed
293pub fn decode_from_xer_buf<T: rasn::Decode>(input: &[u8]) -> Result<T, alloc::string::String> {
294    rasn::Codec::Xer
295        .decode_from_binary(input)
296        .map_err(crate::map_err_to_string)
297}
298
299#[cfg(all(not(target_arch = "wasm32"), feature = "_etsi"))]
300/// Decodes some ASN.1 data type from XER string
301///
302/// # Errors
303/// Returns a human-readable string when decoding failed
304pub fn decode_from_xer_str<T: rasn::Decode>(input: &str) -> Result<T, alloc::string::String> {
305    rasn::Codec::Xer
306        .decode_from_str(input)
307        .map_err(crate::map_err_to_string)
308}
309
310#[cfg(all(not(target_arch = "wasm32"), feature = "_etsi"))]
311/// Decodes some ASN.1 data type from JER buffer
312///
313/// # Errors
314/// Returns a human-readable string when decoding failed
315pub fn decode_from_jer_buf<T: rasn::Decode>(input: &[u8]) -> Result<T, alloc::string::String> {
316    rasn::Codec::Jer
317        .decode_from_binary(input)
318        .map_err(crate::map_err_to_string)
319}
320
321#[cfg(all(not(target_arch = "wasm32"), feature = "_etsi"))]
322/// Decodes some ASN.1 data type from JER string
323///
324/// # Errors
325/// Returns a human-readable string when decoding failed
326pub fn decode_from_jer_str<T: rasn::Decode>(input: &str) -> Result<T, alloc::string::String> {
327    rasn::Codec::Jer
328        .decode_from_str(input)
329        .map_err(crate::map_err_to_string)
330}
331
332#[cfg(all(not(target_arch = "wasm32"), feature = "_etsi"))]
333/// Adds decoding methods to decodable types
334///
335/// This doesn't provide anything more than the decode functions, but adds them as class methods for ease of use.
336macro_rules! decode_some_type {
337    ($t:ty) => {
338        impl $t {
339            /// Decodes [`Self`] from UPER buffer
340            ///
341            /// # Errors
342            /// Returns a human-readable string when decoding failed
343            pub fn decode_from_uper(input: &[u8]) -> Result<Self, alloc::string::String> {
344                crate::de::decode_from_uper::<Self>(input)
345            }
346
347            /// Decodes [`Self`] from XER buffer
348            ///
349            /// # Errors
350            /// Returns a human-readable string when decoding failed
351            pub fn decode_from_xer_buf(input: &[u8]) -> Result<Self, alloc::string::String> {
352                crate::de::decode_from_xer_buf::<Self>(input)
353            }
354
355            /// Decodes [`Self`] from XER string
356            ///
357            /// # Errors
358            /// Returns a human-readable string when decoding failed
359            pub fn decode_from_xer_str(input: &str) -> Result<Self, alloc::string::String> {
360                crate::de::decode_from_xer_str::<Self>(input)
361            }
362
363            /// Decodes [`Self`] from JER buffer
364            ///
365            /// # Errors
366            /// Returns a human-readable string when decoding failed
367            pub fn decode_from_jer_buf(input: &[u8]) -> Result<Self, alloc::string::String> {
368                crate::de::decode_from_jer_buf::<Self>(input)
369            }
370
371            /// Decodes [`Self`] from JER string
372            ///
373            /// # Errors
374            /// Returns a human-readable string when decoding failed
375            pub fn decode_from_jer_str(input: &str) -> Result<Self, alloc::string::String> {
376                crate::de::decode_from_jer_str::<Self>(input)
377            }
378        }
379    };
380}
381
382#[cfg(all(not(target_arch = "wasm32"), feature = "_cdd_1_3_1_1"))]
383decode_some_type!(crate::standards::cdd_1_3_1_1::its_container::ItsPduHeader);
384#[cfg(all(not(target_arch = "wasm32"), feature = "_cdd_2_2_1"))]
385decode_some_type!(crate::standards::cdd_2_2_1::etsi_its_cdd::ItsPduHeader);
386
387#[cfg(all(not(target_arch = "wasm32"), feature = "denm_2_2_1"))]
388decode_some_type!(crate::standards::denm_2_2_1::denm_pdu_description::DENM);
389#[cfg(all(not(target_arch = "wasm32"), feature = "denm_2_2_1"))]
390decode_some_type!(crate::standards::denm_2_2_1::denm_pdu_description::DenmPayload);
391
392#[cfg(all(not(target_arch = "wasm32"), feature = "denm_1_3_1"))]
393decode_some_type!(crate::standards::denm_1_3_1::denm_pdu_descriptions::DENM);
394#[cfg(all(not(target_arch = "wasm32"), feature = "denm_1_3_1"))]
395decode_some_type!(
396    crate::standards::denm_1_3_1::denm_pdu_descriptions::DecentralizedEnvironmentalNotificationMessage
397);
398
399#[cfg(all(not(target_arch = "wasm32"), feature = "cam_1_4_1"))]
400decode_some_type!(crate::standards::cam_1_4_1::cam_pdu_descriptions::CAM);
401#[cfg(all(not(target_arch = "wasm32"), feature = "cam_1_4_1"))]
402decode_some_type!(crate::standards::cam_1_4_1::cam_pdu_descriptions::CoopAwareness);
403
404#[cfg(all(not(target_arch = "wasm32"), feature = "spatem_2_2_1"))]
405decode_some_type!(crate::standards::spatem_2_2_1::spatem_pdu_descriptions::SPATEM);
406#[cfg(all(not(target_arch = "wasm32"), feature = "_dsrc_2_2_1"))]
407decode_some_type!(crate::standards::dsrc_2_2_1::etsi_its_dsrc::SPAT);
408
409#[cfg(all(not(target_arch = "wasm32"), feature = "mapem_2_2_1"))]
410decode_some_type!(crate::standards::mapem_2_2_1::mapem_pdu_descriptions::MAPEM);
411#[cfg(all(not(target_arch = "wasm32"), feature = "_dsrc_2_2_1"))]
412decode_some_type!(crate::standards::dsrc_2_2_1::etsi_its_dsrc::MapData);
413
414#[cfg(all(not(target_arch = "wasm32"), feature = "ivim_2_2_1"))]
415decode_some_type!(crate::standards::ivim_2_2_1::ivim_pdu_descriptions::IVIM);
416#[cfg(all(not(target_arch = "wasm32"), feature = "ivim_2_2_1"))]
417decode_some_type!(crate::standards::ivim_2_2_1::ivi::IviStructure);
418
419#[cfg(all(not(target_arch = "wasm32"), feature = "ivim_2_1_1"))]
420decode_some_type!(crate::standards::ivim_2_1_1::ivim_pdu_descriptions::IVIM);
421#[cfg(all(not(target_arch = "wasm32"), feature = "ivim_2_1_1"))]
422decode_some_type!(crate::standards::ivim_2_1_1::ivi::IviStructure);
423
424#[cfg(all(not(target_arch = "wasm32"), feature = "srem_2_2_1"))]
425decode_some_type!(crate::standards::srem_2_2_1::srem_pdu_descriptions::SREM);
426#[cfg(all(not(target_arch = "wasm32"), feature = "_dsrc_2_2_1"))]
427decode_some_type!(crate::standards::dsrc_2_2_1::etsi_its_dsrc::SignalRequestMessage);
428
429#[cfg(all(not(target_arch = "wasm32"), feature = "ssem_2_2_1"))]
430decode_some_type!(crate::standards::ssem_2_2_1::ssem_pdu_descriptions::SSEM);
431#[cfg(all(not(target_arch = "wasm32"), feature = "_dsrc_2_2_1"))]
432decode_some_type!(crate::standards::dsrc_2_2_1::etsi_its_dsrc::SignalStatusMessage);
433
434#[cfg(all(not(target_arch = "wasm32"), feature = "cpm_2_1_1"))]
435decode_some_type!(crate::standards::cpm_2_1_1::cpm_pdu_descriptions::CollectivePerceptionMessage);
436#[cfg(all(not(target_arch = "wasm32"), feature = "cpm_2_1_1"))]
437decode_some_type!(crate::standards::cpm_2_1_1::cpm_pdu_descriptions::CpmPayload);
438
439#[cfg(all(not(target_arch = "wasm32"), feature = "cpm_1"))]
440decode_some_type!(crate::standards::cpm_1::cpm_pdu_descriptions::CPM);
441#[cfg(all(not(target_arch = "wasm32"), feature = "cpm_1"))]
442decode_some_type!(crate::standards::cpm_1::cpm_pdu_descriptions::CollectivePerceptionMessage);
443
444#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
445#[wasm_bindgen(js_name = decode)]
446/// Decodes an ITS message of undefined type.
447/// Tries to parse the ITS PDU header to read the message ID that identifies the message type.
448/// ### Params
449///  - `message`: binary input containing the ITS message
450///  - `headersPresent`: indicate which headers are present in the binary input. GeoNetworking and transport headers will be decoded and returned, other headers will be skipped.
451///  - `outputEncodingRules`: ASN.1 encoding rules that will be used for re-encoding the message in the `JsonItsMessage`'s `its` field. (UPER output will be rendered as a UTF-8 hex string)
452/// Throws string error on decoding errors.
453pub fn decode_to(
454    message: &[u8],
455    headersPresent: crate::Headers,
456    outputEncodingRules: crate::EncodingRules,
457) -> Result<crate::JsonItsMessage, String> {
458    let (input, mut etsi_json) = optionally_decode_headers(message, headersPresent)?;
459    let (input_encoding_rules, protocol_version, message_type) = message_type(input)?;
460    let (msg_ty, decoded) = match (message_type, protocol_version) {
461        (1, 2) => (
462            1,
463            decode_denm(
464                input,
465                Some(211),
466                crate::Headers::None,
467                input_encoding_rules,
468                outputEncodingRules,
469            )?
470            .its,
471        ),
472        (1, _) => (
473            1,
474            decode_denm(
475                input,
476                Some(131),
477                crate::Headers::None,
478                input_encoding_rules,
479                outputEncodingRules,
480            )?
481            .its,
482        ),
483        (2, _) => (
484            2,
485            decode_cam(
486                input,
487                None,
488                crate::Headers::None,
489                input_encoding_rules,
490                outputEncodingRules,
491            )?
492            .its,
493        ),
494        (4, _) => (
495            4,
496            decode_spatem(
497                input,
498                None,
499                crate::Headers::None,
500                input_encoding_rules,
501                outputEncodingRules,
502            )?
503            .its,
504        ),
505        (5, _) => (
506            5,
507            decode_mapem(
508                input,
509                None,
510                crate::Headers::None,
511                input_encoding_rules,
512                outputEncodingRules,
513            )?
514            .its,
515        ),
516        (6, 2) => (
517            6,
518            decode_ivim(
519                input,
520                Some(221),
521                crate::Headers::None,
522                input_encoding_rules,
523                outputEncodingRules,
524            )?
525            .its,
526        ),
527        (6, _) => (
528            6,
529            decode_ivim(
530                input,
531                Some(131),
532                crate::Headers::None,
533                input_encoding_rules,
534                outputEncodingRules,
535            )?
536            .its,
537        ),
538        (9, _) => (
539            9,
540            decode_srem(
541                input,
542                None,
543                crate::Headers::None,
544                input_encoding_rules,
545                outputEncodingRules,
546            )?
547            .its,
548        ),
549        (10, _) => (
550            10,
551            decode_ssem(
552                input,
553                None,
554                crate::Headers::None,
555                input_encoding_rules,
556                outputEncodingRules,
557            )?
558            .its,
559        ),
560        (14, 2) => (
561            14,
562            decode_cpm(
563                input,
564                Some(211),
565                crate::Headers::None,
566                input_encoding_rules,
567                outputEncodingRules,
568            )?
569            .its,
570        ),
571        (14, _) => (
572            14,
573            decode_cpm(
574                input,
575                Some(131),
576                crate::Headers::None,
577                input_encoding_rules,
578                outputEncodingRules,
579            )?
580            .its,
581        ),
582        (message_i_d, _) => {
583            return Err(format!(
584                "Unsupported ITS message type: Found message id {message_i_d}."
585            ));
586        }
587    };
588    etsi_json.its = decoded;
589    etsi_json.message_type = msg_ty;
590    Ok(etsi_json)
591}
592
593#[cfg(any(
594    feature = "_etsi",
595    all(target_arch = "wasm32", feature = "v2x", feature = "json"),
596    all(test, feature = "_etsi")
597))]
598fn message_type(input: &[u8]) -> Result<(crate::EncodingRules, u8, u8), alloc::string::String> {
599    use nom::FindSubstring as _;
600
601    let encoding_rules = match core::primitive::str::from_utf8(input) {
602        Ok(s) if s.trim_start().starts_with('<') => crate::EncodingRules::XER,
603        Ok(s) if s.trim_start().starts_with('{') => crate::EncodingRules::JER,
604        _ => crate::EncodingRules::UPER,
605    };
606    match encoding_rules {
607        crate::EncodingRules::XER => {
608            let message_id_start = input
609                .find_substring("messageID>")
610                .or(input.find_substring("messageId>"))
611                .ok_or("Failed to determine message ID.")?
612                + 10;
613            let message_id_end = (&input[message_id_start..])
614                .find_substring("</")
615                .ok_or("Failed to determine message ID.")?
616                + message_id_start;
617            let message_id =
618                core::primitive::str::from_utf8(&input[message_id_start..message_id_end])
619                    .map_err(crate::map_err_to_string)?
620                    .trim()
621                    .parse()
622                    .map_err(crate::map_err_to_string)?;
623            let protocol_version_start = input
624                .find_substring("protocolVersion>")
625                .ok_or("Failed to determine protocol version.")?
626                + 16;
627            let protocol_version_end = (&input[protocol_version_start..])
628                .find_substring("</")
629                .ok_or("Failed to determine protocol version.")?
630                + protocol_version_start;
631            let protocol_version = core::primitive::str::from_utf8(
632                &input[protocol_version_start..protocol_version_end],
633            )
634            .map_err(crate::map_err_to_string)?
635            .trim()
636            .parse()
637            .map_err(crate::map_err_to_string)?;
638            Ok((encoding_rules, protocol_version, message_id))
639        }
640        crate::EncodingRules::JER => {
641            let message_id = input
642                .find_substring("messageID\":")
643                .or(input.find_substring("messageId\":"))
644                .ok_or(alloc::string::String::from(
645                    "Failed to determine message ID.",
646                ))
647                .and_then(|start| {
648                    let mut end = start + 11;
649                    let mut value = input[end] as char;
650                    while end < input.len() - 1 && (value.is_whitespace() || value.is_numeric()) {
651                        end += 1;
652                        value = input[end] as char;
653                    }
654                    core::primitive::str::from_utf8(&input[(start + 11)..end])
655                        .map_err(crate::map_err_to_string)
656                        .and_then(|s| s.trim().parse::<u8>().map_err(crate::map_err_to_string))
657                })?;
658            let protocol_version = input
659                .find_substring("protocolVersion\":")
660                .ok_or(alloc::string::String::from(
661                    "Failed to determine message ID.",
662                ))
663                .and_then(|start| {
664                    let mut end = start + 17;
665                    let mut value = input[end] as char;
666                    while end < input.len() - 1 && (value.is_whitespace() || value.is_numeric()) {
667                        end += 1;
668                        value = input[end] as char;
669                    }
670                    core::primitive::str::from_utf8(&input[(start + 17)..end])
671                        .map_err(crate::map_err_to_string)
672                        .and_then(|s| s.trim().parse::<u8>().map_err(crate::map_err_to_string))
673                })?;
674            Ok((encoding_rules, protocol_version, message_id))
675        }
676        crate::EncodingRules::UPER => crate::EncodingRules::UPER
677            .codec()
678            .decode_from_binary::<crate::standards::cdd_2_2_1::etsi_its_cdd::ItsPduHeader>(input)
679            .map(|header| {
680                (
681                    encoding_rules,
682                    header.protocol_version.0,
683                    header.message_id.0,
684                )
685            })
686            .map_err(crate::map_err_to_string),
687    }
688}
689
690#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
691fn decode_denm(
692    denm: &[u8],
693    mut version: Option<u32>,
694    headers_present: crate::Headers,
695    input_encoding_rules: crate::EncodingRules,
696    output_encoding_rules: crate::EncodingRules,
697) -> Result<crate::JsonItsMessage, String> {
698    let (input, mut etsi_json) = optionally_decode_headers(denm, headers_present)?;
699    if version.is_none() {
700        version = match input.first() {
701            Some(1) => Some(131),
702            Some(2) => Some(211),
703            _ => None,
704        };
705    }
706    etsi_json.its = match version {
707        Some(131) => Some(transcode::<
708            crate::standards::denm_1_3_1::denm_pdu_descriptions::DENM,
709        >(input, input_encoding_rules, output_encoding_rules))
710        .transpose(),
711        None | Some(221) => Some(transcode::<
712            crate::standards::denm_2_2_1::denm_pdu_description::DENM,
713        >(input, input_encoding_rules, output_encoding_rules))
714        .transpose(),
715        _ => {
716            return Err(
717                "Unsupported DENM version: Supported DENM versions are 131 and 221.".to_string(),
718            );
719        }
720    }?;
721    Ok(etsi_json)
722}
723
724#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
725fn decode_cam(
726    cam: &[u8],
727    version: Option<u32>,
728    headers_present: crate::Headers,
729    input_encoding_rules: crate::EncodingRules,
730    output_encoding_rules: crate::EncodingRules,
731) -> Result<crate::JsonItsMessage, String> {
732    let (input, mut etsi_json) = optionally_decode_headers(cam, headers_present)?;
733    etsi_json.its = match version {
734        None | Some(141) => Some(transcode::<
735            crate::standards::cam_1_4_1::cam_pdu_descriptions::CAM,
736        >(input, input_encoding_rules, output_encoding_rules))
737        .transpose(),
738        _ => return Err("Unsupported DENM version: Supported CAM version is 141.".to_string()),
739    }?;
740    Ok(etsi_json)
741}
742
743#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
744fn decode_mapem(
745    mapem: &[u8],
746    version: Option<u32>,
747    headers_present: crate::Headers,
748    input_encoding_rules: crate::EncodingRules,
749    output_encoding_rules: crate::EncodingRules,
750) -> Result<crate::JsonItsMessage, String> {
751    let (input, mut etsi_json) = optionally_decode_headers(mapem, headers_present)?;
752    etsi_json.its = match version {
753        None | Some(221) => Some(transcode::<
754            crate::standards::mapem_2_2_1::mapem_pdu_descriptions::MAPEM,
755        >(input, input_encoding_rules, output_encoding_rules))
756        .transpose(),
757        _ => return Err("Unsupported MAPEM version: Supported MAPEM version is 221.".to_string()),
758    }?;
759    Ok(etsi_json)
760}
761
762#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
763fn decode_spatem(
764    spatem: &[u8],
765    version: Option<u32>,
766    headers_present: crate::Headers,
767    input_encoding_rules: crate::EncodingRules,
768    output_encoding_rules: crate::EncodingRules,
769) -> Result<crate::JsonItsMessage, String> {
770    let (input, mut etsi_json) = optionally_decode_headers(spatem, headers_present)?;
771    etsi_json.its = match version {
772        None | Some(131) => Some(transcode::<
773            crate::standards::spatem_2_2_1::spatem_pdu_descriptions::SPATEM,
774        >(input, input_encoding_rules, output_encoding_rules))
775        .transpose(),
776        _ => {
777            return Err("Unsupported SPATEM version: Supported SPATEM version is 131.".to_string());
778        }
779    }?;
780    Ok(etsi_json)
781}
782
783#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
784fn decode_ivim(
785    ivim: &[u8],
786    mut version: Option<u32>,
787    headers_present: crate::Headers,
788    input_encoding_rules: crate::EncodingRules,
789    output_encoding_rules: crate::EncodingRules,
790) -> Result<crate::JsonItsMessage, String> {
791    let (input, mut etsi_json) = optionally_decode_headers(ivim, headers_present)?;
792    if version.is_none() {
793        version = match input.first() {
794            Some(1) => Some(211),
795            Some(2) => Some(221),
796            _ => None,
797        };
798    }
799    etsi_json.its = match version {
800        Some(131) | Some(211) => Some(transcode::<
801            crate::standards::ivim_2_1_1::ivim_pdu_descriptions::IVIM,
802        >(input, input_encoding_rules, output_encoding_rules))
803        .transpose(),
804        None | Some(221) => Some(transcode::<
805            crate::standards::ivim_2_2_1::ivim_pdu_descriptions::IVIM,
806        >(input, input_encoding_rules, output_encoding_rules))
807        .transpose(),
808        _ => {
809            return Err(
810                "Unsupported IVIM version: Supported IVIM versions are 131, 211 and 221."
811                    .to_string(),
812            );
813        }
814    }?;
815    Ok(etsi_json)
816}
817
818#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
819fn decode_srem(
820    srem: &[u8],
821    version: Option<u32>,
822    headers_present: crate::Headers,
823    input_encoding_rules: crate::EncodingRules,
824    output_encoding_rules: crate::EncodingRules,
825) -> Result<crate::JsonItsMessage, String> {
826    let (input, mut etsi_json) = optionally_decode_headers(srem, headers_present)?;
827    etsi_json.its = match version {
828        None | Some(221) => Some(transcode::<
829            crate::standards::srem_2_2_1::srem_pdu_descriptions::SREM,
830        >(input, input_encoding_rules, output_encoding_rules))
831        .transpose(),
832        _ => return Err("Unsupported SREM version: Supported SREM version is 221.".to_string()),
833    }?;
834    Ok(etsi_json)
835}
836
837#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
838fn decode_cpm(
839    cpm: &[u8],
840    mut version: Option<u32>,
841    headers_present: crate::Headers,
842    input_encoding_rules: crate::EncodingRules,
843    output_encoding_rules: crate::EncodingRules,
844) -> Result<crate::JsonItsMessage, String> {
845    let (input, mut etsi_json) = optionally_decode_headers(cpm, headers_present)?;
846    if version.is_none() {
847        version = match input.first() {
848            Some(1) => Some(131),
849            Some(2) => Some(211),
850            _ => None,
851        };
852    }
853    etsi_json.its = match version {
854        None | Some(211) => Some(transcode::<
855            crate::standards::cpm_2_1_1::cpm_pdu_descriptions::CollectivePerceptionMessage,
856        >(input, input_encoding_rules, output_encoding_rules))
857        .transpose(),
858        Some(131) => Some(transcode::<
859            crate::standards::cpm_1::cpm_pdu_descriptions::CPM,
860        >(input, input_encoding_rules, output_encoding_rules))
861        .transpose(),
862        _ => {
863            return Err(
864                "Unsupported CPM version: Supported CPM versions are 131 and 211.".to_string(),
865            );
866        }
867    }?;
868    Ok(etsi_json)
869}
870
871#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
872fn decode_ssem(
873    ssem: &[u8],
874    version: Option<u32>,
875    headers_present: crate::Headers,
876    input_encoding_rules: crate::EncodingRules,
877    output_encoding_rules: crate::EncodingRules,
878) -> Result<crate::JsonItsMessage, String> {
879    let (input, mut etsi_json) = optionally_decode_headers(ssem, headers_present)?;
880    etsi_json.its = match version {
881        None | Some(221) => Some(transcode::<
882            crate::standards::ssem_2_2_1::ssem_pdu_descriptions::SSEM,
883        >(input, input_encoding_rules, output_encoding_rules))
884        .transpose(),
885        _ => return Err("Unsupported SSEM version: Supported SSEM version is 221.".to_string()),
886    }?;
887    Ok(etsi_json)
888}
889
890#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
891pub fn optionally_decode_headers(
892    input: &[u8],
893    headers: crate::Headers,
894) -> Result<(&[u8], crate::JsonItsMessage), String> {
895    match headers {
896        crate::Headers::None => Ok((input, crate::JsonItsMessage::default())),
897        crate::Headers::GnBtp => transcode_gn_tp_to_json(input),
898        crate::Headers::IEEE802LlcGnBtp => {
899            crate::pcap::remove_wlan_headers(input).and_then(transcode_gn_tp_to_json)
900        }
901        crate::Headers::RadioTap802LlcGnBtp => {
902            crate::pcap::remove_pcap_headers(input).and_then(transcode_gn_tp_to_json)
903        }
904    }
905}
906
907#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
908fn transcode_gn_tp_to_json(input: &[u8]) -> Result<(&[u8], crate::JsonItsMessage), String> {
909    decode_geonetworking_header(input).and_then(|(remaining, gn_json, next_header)| {
910        decode_transport_header(remaining, next_header).map(|(rem, tp)| {
911            (
912                rem,
913                crate::JsonItsMessage {
914                    geonetworking: Some(gn_json),
915                    transport: Some(tp),
916                    ..Default::default()
917                },
918            )
919        })
920    })
921}
922
923#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
924fn decode_geonetworking_header(
925    input: &[u8],
926) -> Result<(&[u8], String, geonetworking::en302636_4_1::NextAfterCommon), String> {
927    use geonetworking::{Decode as _, Encode as _};
928
929    let result = geonetworking::Packet::decode(input).map_err(crate::map_err_to_string)?;
930    let gn_json = result
931        .decoded
932        .encode_to_json()
933        .map_err(crate::map_err_to_string)?;
934    match result.decoded {
935        geonetworking::Packet::Unsecured {
936            common, payload, ..
937        } => Ok((payload, gn_json, common.next_header)),
938        p => p
939            .secured_payload_after_gn()
940            .ok_or("Secured GeoNetworking Packet carries no data!".into())
941            .map(|payload| (payload, gn_json, p.common().next_header)),
942    }
943}
944
945#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
946fn decode_transport_header(
947    input: &[u8],
948    header_type: geonetworking::en302636_4_1::NextAfterCommon,
949) -> Result<(&[u8], String), String> {
950    match header_type {
951        geonetworking::en302636_4_1::NextAfterCommon::Any => {
952            Err("Currently, only BTP and IPv6 Headers can be decoded!".to_string())
953        }
954        geonetworking::en302636_4_1::NextAfterCommon::BTPA => {
955            btp![crate::transport::BasicTransportAHeader, input]
956        }
957        geonetworking::en302636_4_1::NextAfterCommon::BTPB => {
958            btp![crate::transport::BasicTransportBHeader, input]
959        }
960        geonetworking::en302636_4_1::NextAfterCommon::IPv6 => {
961            let (remaining, ipv6) =
962                crate::transport::IPv6Header::decode(input).map_err(crate::map_err_to_string)?;
963            Ok((remaining, to_ipv6_debug(ipv6)))
964        }
965    }
966}
967
968#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
969fn transcode<T: rasn::Decode + rasn::Encode>(
970    input: &[u8],
971    input_encoding_rules: crate::EncodingRules,
972    output_encoding_rules: crate::EncodingRules,
973) -> Result<String, String> {
974    use core::fmt::Write as _;
975
976    if let (crate::EncodingRules::UPER, crate::EncodingRules::UPER) =
977        (input_encoding_rules, output_encoding_rules)
978    {
979        return input.iter().try_fold(String::new(), |mut acc, byte| {
980            write!(&mut acc, "{byte:02X?}")
981                .map_err(crate::map_err_to_string)
982                .map(|_| acc)
983        });
984    }
985    let decoded: T = input_encoding_rules
986        .codec()
987        .decode_from_binary(input)
988        .map_err(crate::map_err_to_string)?;
989    match output_encoding_rules {
990        crate::EncodingRules::UPER => rasn::uper::encode(&decoded)
991            .map(hex::encode)
992            .map_err(crate::map_err_to_string),
993        o => o
994            .codec()
995            .encode_to_string(&decoded)
996            .map_err(crate::map_err_to_string),
997    }
998}
999
1000#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
1001fn to_ipv6_debug(ipv6: crate::transport::IPv6Header) -> String {
1002    alloc::format!(r#"{{"ipv6Debug":"{ipv6:?}"}}"#)
1003}
1004
1005#[cfg(all(test, feature = "_etsi"))]
1006mod tests {
1007    use crate::de::message_type;
1008
1009    #[test]
1010    fn recognizes_message_type_and_version() {
1011        assert_eq!((crate::EncodingRules::XER, 2,14), message_type("<CPM><header><protocolVersion>2</protocolVersion><messageID>14</messageID><stationID>".as_bytes()).unwrap());
1012        assert_eq!(
1013            (crate::EncodingRules::XER, 1, 5),
1014            message_type(
1015                r#"<?xml version="1.0"?><MAPEM><header><protocolVersion>  1  </protocolVersion><messageID>
1016        5
1017        </messageID><stationID>"#
1018                    .as_bytes()
1019            )
1020            .unwrap()
1021        );
1022        assert_eq!(
1023            (crate::EncodingRules::JER, 2, 2),
1024            message_type(
1025                r#"{"header":{"protocolVersion":2,"messageID":2,"stationID":2624309139}"#
1026                    .as_bytes()
1027            )
1028            .unwrap()
1029        );
1030        assert_eq!(
1031            (crate::EncodingRules::JER, 1, 9),
1032            message_type(
1033                r#"{
1034                    "header": {
1035                            "protocolVersion": 1,
1036                            "messageID": 9,
1037                            "stationID": 2624309139
1038                    }"#
1039                .as_bytes()
1040            )
1041            .unwrap()
1042        );
1043    }
1044}