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::NextAfterCommon::Any => {
251            Err("Currently, only BTP and IPv6 Headers can be decoded!".to_string())
252        }
253        geonetworking::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::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::NextAfterCommon::IPv6 => crate::transport::IPv6Header::decode(payload)
264            .map(|(rem, ipv6)| {
265                (
266                    rem,
267                    crate::transport::TransportHeader::IPv6(alloc::boxed::Box::new(ipv6)),
268                )
269            })
270            .map_err(crate::map_err_to_string),
271    }?;
272    Ok((remaining, alloc::boxed::Box::new(tp), result.decoded))
273}
274
275#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
276#[wasm_bindgen(js_name = decode)]
277/// Decodes an ITS message of undefined type.
278/// Tries to parse the ITS PDU header to read the message ID that identifies the message type.
279/// ### Params
280///  - `message`: binary input containing the ITS message
281///  - `headersPresent`: indicate which headers are present in the binary input. GeoNetworking and transport headers will be decoded and returned, other headers will be skipped.
282///  - `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)
283/// Throws string error on decoding errors.
284pub fn decode_to(
285    message: &[u8],
286    headersPresent: crate::Headers,
287    outputEncodingRules: crate::EncodingRules,
288) -> Result<crate::JsonItsMessage, String> {
289    let (input, mut etsi_json) = optionally_decode_headers(message, headersPresent)?;
290    let (input_encoding_rules, protocol_version, message_type) = message_type(input)?;
291    let (msg_ty, decoded) = match (message_type, protocol_version) {
292        (1, 2) => (
293            1,
294            decode_denm(
295                input,
296                Some(211),
297                crate::Headers::None,
298                input_encoding_rules,
299                outputEncodingRules,
300            )?
301            .its,
302        ),
303        (1, _) => (
304            1,
305            decode_denm(
306                input,
307                Some(131),
308                crate::Headers::None,
309                input_encoding_rules,
310                outputEncodingRules,
311            )?
312            .its,
313        ),
314        (2, _) => (
315            2,
316            decode_cam(
317                input,
318                None,
319                crate::Headers::None,
320                input_encoding_rules,
321                outputEncodingRules,
322            )?
323            .its,
324        ),
325        (4, _) => (
326            4,
327            decode_spatem(
328                input,
329                None,
330                crate::Headers::None,
331                input_encoding_rules,
332                outputEncodingRules,
333            )?
334            .its,
335        ),
336        (5, _) => (
337            5,
338            decode_mapem(
339                input,
340                None,
341                crate::Headers::None,
342                input_encoding_rules,
343                outputEncodingRules,
344            )?
345            .its,
346        ),
347        (6, 2) => (
348            6,
349            decode_ivim(
350                input,
351                Some(221),
352                crate::Headers::None,
353                input_encoding_rules,
354                outputEncodingRules,
355            )?
356            .its,
357        ),
358        (6, _) => (
359            6,
360            decode_ivim(
361                input,
362                Some(131),
363                crate::Headers::None,
364                input_encoding_rules,
365                outputEncodingRules,
366            )?
367            .its,
368        ),
369        (9, _) => (
370            9,
371            decode_srem(
372                input,
373                None,
374                crate::Headers::None,
375                input_encoding_rules,
376                outputEncodingRules,
377            )?
378            .its,
379        ),
380        (10, _) => (
381            10,
382            decode_ssem(
383                input,
384                None,
385                crate::Headers::None,
386                input_encoding_rules,
387                outputEncodingRules,
388            )?
389            .its,
390        ),
391        (14, 2) => (
392            14,
393            decode_cpm(
394                input,
395                Some(211),
396                crate::Headers::None,
397                input_encoding_rules,
398                outputEncodingRules,
399            )?
400            .its,
401        ),
402        (14, _) => (
403            14,
404            decode_cpm(
405                input,
406                Some(131),
407                crate::Headers::None,
408                input_encoding_rules,
409                outputEncodingRules,
410            )?
411            .its,
412        ),
413        (message_i_d, _) => {
414            return Err(format!(
415                "Unsupported ITS message type: Found message id {message_i_d}."
416            ));
417        }
418    };
419    etsi_json.its = decoded;
420    etsi_json.message_type = msg_ty;
421    Ok(etsi_json)
422}
423
424#[cfg(any(
425    feature = "_etsi",
426    all(target_arch = "wasm32", feature = "v2x", feature = "json"),
427    all(test, feature = "_etsi")
428))]
429fn message_type(input: &[u8]) -> Result<(crate::EncodingRules, u8, u8), alloc::string::String> {
430    use nom::FindSubstring as _;
431
432    let encoding_rules = match core::primitive::str::from_utf8(input) {
433        Ok(s) if s.trim_start().starts_with('<') => crate::EncodingRules::XER,
434        Ok(s) if s.trim_start().starts_with('{') => crate::EncodingRules::JER,
435        _ => crate::EncodingRules::UPER,
436    };
437    match encoding_rules {
438        crate::EncodingRules::XER => {
439            let message_id_start = input
440                .find_substring("messageID>")
441                .or(input.find_substring("messageId>"))
442                .ok_or("Failed to determine message ID.")?
443                + 10;
444            let message_id_end = (&input[message_id_start..])
445                .find_substring("</")
446                .ok_or("Failed to determine message ID.")?
447                + message_id_start;
448            let message_id =
449                core::primitive::str::from_utf8(&input[message_id_start..message_id_end])
450                    .map_err(crate::map_err_to_string)?
451                    .trim()
452                    .parse()
453                    .map_err(crate::map_err_to_string)?;
454            let protocol_version_start = input
455                .find_substring("protocolVersion>")
456                .ok_or("Failed to determine protocol version.")?
457                + 16;
458            let protocol_version_end = (&input[protocol_version_start..])
459                .find_substring("</")
460                .ok_or("Failed to determine protocol version.")?
461                + protocol_version_start;
462            let protocol_version = core::primitive::str::from_utf8(
463                &input[protocol_version_start..protocol_version_end],
464            )
465            .map_err(crate::map_err_to_string)?
466            .trim()
467            .parse()
468            .map_err(crate::map_err_to_string)?;
469            Ok((encoding_rules, protocol_version, message_id))
470        }
471        crate::EncodingRules::JER => {
472            let message_id = input
473                .find_substring("messageID\":")
474                .or(input.find_substring("messageId\":"))
475                .ok_or(alloc::string::String::from(
476                    "Failed to determine message ID.",
477                ))
478                .and_then(|start| {
479                    let mut end = start + 11;
480                    let mut value = input[end] as char;
481                    while end < input.len() - 1 && (value.is_whitespace() || value.is_numeric()) {
482                        end += 1;
483                        value = input[end] as char;
484                    }
485                    core::primitive::str::from_utf8(&input[(start + 11)..end])
486                        .map_err(crate::map_err_to_string)
487                        .and_then(|s| s.trim().parse::<u8>().map_err(crate::map_err_to_string))
488                })?;
489            let protocol_version = input
490                .find_substring("protocolVersion\":")
491                .ok_or(alloc::string::String::from(
492                    "Failed to determine message ID.",
493                ))
494                .and_then(|start| {
495                    let mut end = start + 17;
496                    let mut value = input[end] as char;
497                    while end < input.len() - 1 && (value.is_whitespace() || value.is_numeric()) {
498                        end += 1;
499                        value = input[end] as char;
500                    }
501                    core::primitive::str::from_utf8(&input[(start + 17)..end])
502                        .map_err(crate::map_err_to_string)
503                        .and_then(|s| s.trim().parse::<u8>().map_err(crate::map_err_to_string))
504                })?;
505            Ok((encoding_rules, protocol_version, message_id))
506        }
507        crate::EncodingRules::UPER => crate::EncodingRules::UPER
508            .codec()
509            .decode_from_binary::<crate::standards::cdd_2_2_1::etsi_its_cdd::ItsPduHeader>(input)
510            .map(|header| {
511                (
512                    encoding_rules,
513                    header.protocol_version.0,
514                    header.message_id.0,
515                )
516            })
517            .map_err(crate::map_err_to_string),
518    }
519}
520
521#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
522fn decode_denm(
523    denm: &[u8],
524    mut version: Option<u32>,
525    headers_present: crate::Headers,
526    input_encoding_rules: crate::EncodingRules,
527    output_encoding_rules: crate::EncodingRules,
528) -> Result<crate::JsonItsMessage, String> {
529    let (input, mut etsi_json) = optionally_decode_headers(denm, headers_present)?;
530    if version.is_none() {
531        version = match input.first() {
532            Some(1) => Some(131),
533            Some(2) => Some(211),
534            _ => None,
535        };
536    }
537    etsi_json.its = match version {
538        Some(131) => Some(transcode::<
539            crate::standards::denm_1_3_1::denm_pdu_descriptions::DENM,
540        >(input, input_encoding_rules, output_encoding_rules))
541        .transpose(),
542        None | Some(221) => Some(transcode::<
543            crate::standards::denm_2_2_1::denm_pdu_description::DENM,
544        >(input, input_encoding_rules, output_encoding_rules))
545        .transpose(),
546        _ => {
547            return Err(
548                "Unsupported DENM version: Supported DENM versions are 131 and 221.".to_string(),
549            );
550        }
551    }?;
552    Ok(etsi_json)
553}
554
555#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
556fn decode_cam(
557    cam: &[u8],
558    version: Option<u32>,
559    headers_present: crate::Headers,
560    input_encoding_rules: crate::EncodingRules,
561    output_encoding_rules: crate::EncodingRules,
562) -> Result<crate::JsonItsMessage, String> {
563    let (input, mut etsi_json) = optionally_decode_headers(cam, headers_present)?;
564    etsi_json.its = match version {
565        None | Some(141) => Some(transcode::<
566            crate::standards::cam_1_4_1::cam_pdu_descriptions::CAM,
567        >(input, input_encoding_rules, output_encoding_rules))
568        .transpose(),
569        _ => return Err("Unsupported DENM version: Supported CAM version is 141.".to_string()),
570    }?;
571    Ok(etsi_json)
572}
573
574#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
575fn decode_mapem(
576    mapem: &[u8],
577    version: Option<u32>,
578    headers_present: crate::Headers,
579    input_encoding_rules: crate::EncodingRules,
580    output_encoding_rules: crate::EncodingRules,
581) -> Result<crate::JsonItsMessage, String> {
582    let (input, mut etsi_json) = optionally_decode_headers(mapem, headers_present)?;
583    etsi_json.its = match version {
584        None | Some(221) => Some(transcode::<
585            crate::standards::mapem_2_2_1::mapem_pdu_descriptions::MAPEM,
586        >(input, input_encoding_rules, output_encoding_rules))
587        .transpose(),
588        _ => return Err("Unsupported MAPEM version: Supported MAPEM version is 221.".to_string()),
589    }?;
590    Ok(etsi_json)
591}
592
593#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
594fn decode_spatem(
595    spatem: &[u8],
596    version: Option<u32>,
597    headers_present: crate::Headers,
598    input_encoding_rules: crate::EncodingRules,
599    output_encoding_rules: crate::EncodingRules,
600) -> Result<crate::JsonItsMessage, String> {
601    let (input, mut etsi_json) = optionally_decode_headers(spatem, headers_present)?;
602    etsi_json.its = match version {
603        None | Some(131) => Some(transcode::<
604            crate::standards::spatem_2_2_1::spatem_pdu_descriptions::SPATEM,
605        >(input, input_encoding_rules, output_encoding_rules))
606        .transpose(),
607        _ => {
608            return Err("Unsupported SPATEM version: Supported SPATEM version is 131.".to_string());
609        }
610    }?;
611    Ok(etsi_json)
612}
613
614#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
615fn decode_ivim(
616    ivim: &[u8],
617    mut version: Option<u32>,
618    headers_present: crate::Headers,
619    input_encoding_rules: crate::EncodingRules,
620    output_encoding_rules: crate::EncodingRules,
621) -> Result<crate::JsonItsMessage, String> {
622    let (input, mut etsi_json) = optionally_decode_headers(ivim, headers_present)?;
623    if version.is_none() {
624        version = match input.first() {
625            Some(1) => Some(211),
626            Some(2) => Some(221),
627            _ => None,
628        };
629    }
630    etsi_json.its = match version {
631        Some(131) | Some(211) => Some(transcode::<
632            crate::standards::ivim_2_1_1::ivim_pdu_descriptions::IVIM,
633        >(input, input_encoding_rules, output_encoding_rules))
634        .transpose(),
635        None | Some(221) => Some(transcode::<
636            crate::standards::ivim_2_2_1::ivim_pdu_descriptions::IVIM,
637        >(input, input_encoding_rules, output_encoding_rules))
638        .transpose(),
639        _ => {
640            return Err(
641                "Unsupported IVIM version: Supported IVIM versions are 131, 211 and 221."
642                    .to_string(),
643            );
644        }
645    }?;
646    Ok(etsi_json)
647}
648
649#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
650fn decode_srem(
651    srem: &[u8],
652    version: Option<u32>,
653    headers_present: crate::Headers,
654    input_encoding_rules: crate::EncodingRules,
655    output_encoding_rules: crate::EncodingRules,
656) -> Result<crate::JsonItsMessage, String> {
657    let (input, mut etsi_json) = optionally_decode_headers(srem, headers_present)?;
658    etsi_json.its = match version {
659        None | Some(221) => Some(transcode::<
660            crate::standards::srem_2_2_1::srem_pdu_descriptions::SREM,
661        >(input, input_encoding_rules, output_encoding_rules))
662        .transpose(),
663        _ => return Err("Unsupported SREM version: Supported SREM version is 221.".to_string()),
664    }?;
665    Ok(etsi_json)
666}
667
668#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
669fn decode_cpm(
670    cpm: &[u8],
671    mut version: Option<u32>,
672    headers_present: crate::Headers,
673    input_encoding_rules: crate::EncodingRules,
674    output_encoding_rules: crate::EncodingRules,
675) -> Result<crate::JsonItsMessage, String> {
676    let (input, mut etsi_json) = optionally_decode_headers(cpm, headers_present)?;
677    if version.is_none() {
678        version = match input.first() {
679            Some(1) => Some(131),
680            Some(2) => Some(211),
681            _ => None,
682        };
683    }
684    etsi_json.its = match version {
685        None | Some(211) => Some(transcode::<
686            crate::standards::cpm_2_1_1::cpm_pdu_descriptions::CollectivePerceptionMessage,
687        >(input, input_encoding_rules, output_encoding_rules))
688        .transpose(),
689        Some(131) => Some(transcode::<
690            crate::standards::cpm_1::cpm_pdu_descriptions::CPM,
691        >(input, input_encoding_rules, output_encoding_rules))
692        .transpose(),
693        _ => {
694            return Err(
695                "Unsupported CPM version: Supported CPM versions are 131 and 211.".to_string(),
696            );
697        }
698    }?;
699    Ok(etsi_json)
700}
701
702#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
703fn decode_ssem(
704    ssem: &[u8],
705    version: Option<u32>,
706    headers_present: crate::Headers,
707    input_encoding_rules: crate::EncodingRules,
708    output_encoding_rules: crate::EncodingRules,
709) -> Result<crate::JsonItsMessage, String> {
710    let (input, mut etsi_json) = optionally_decode_headers(ssem, headers_present)?;
711    etsi_json.its = match version {
712        None | Some(221) => Some(transcode::<
713            crate::standards::ssem_2_2_1::ssem_pdu_descriptions::SSEM,
714        >(input, input_encoding_rules, output_encoding_rules))
715        .transpose(),
716        _ => return Err("Unsupported SSEM version: Supported SSEM version is 221.".to_string()),
717    }?;
718    Ok(etsi_json)
719}
720
721#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
722pub fn optionally_decode_headers(
723    input: &[u8],
724    headers: crate::Headers,
725) -> Result<(&[u8], crate::JsonItsMessage), String> {
726    match headers {
727        crate::Headers::None => Ok((input, crate::JsonItsMessage::default())),
728        crate::Headers::GnBtp => transcode_gn_tp_to_json(input),
729        crate::Headers::IEEE802LlcGnBtp => {
730            crate::pcap::remove_wlan_headers(input).and_then(transcode_gn_tp_to_json)
731        }
732        crate::Headers::RadioTap802LlcGnBtp => {
733            crate::pcap::remove_pcap_headers(input).and_then(transcode_gn_tp_to_json)
734        }
735    }
736}
737
738#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
739fn transcode_gn_tp_to_json(input: &[u8]) -> Result<(&[u8], crate::JsonItsMessage), String> {
740    decode_geonetworking_header(input).and_then(|(remaining, gn_json, next_header)| {
741        decode_transport_header(remaining, next_header).map(|(rem, tp)| {
742            (
743                rem,
744                crate::JsonItsMessage {
745                    geonetworking: Some(gn_json),
746                    transport: Some(tp),
747                    ..Default::default()
748                },
749            )
750        })
751    })
752}
753
754#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
755fn decode_geonetworking_header(
756    input: &[u8],
757) -> Result<(&[u8], String, geonetworking::NextAfterCommon), String> {
758    use geonetworking::{Decode as _, Encode as _};
759
760    let result = geonetworking::Packet::decode(input).map_err(crate::map_err_to_string)?;
761    let gn_json = result
762        .decoded
763        .encode_to_json()
764        .map_err(crate::map_err_to_string)?;
765    match result.decoded {
766        geonetworking::Packet::Unsecured {
767            common, payload, ..
768        } => Ok((payload, gn_json, common.next_header)),
769        p => p
770            .secured_payload_after_gn()
771            .ok_or("Secured GeoNetworking Packet carries no data!".into())
772            .map(|payload| (payload, gn_json, p.common().next_header)),
773    }
774}
775
776#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
777fn decode_transport_header(
778    input: &[u8],
779    header_type: geonetworking::NextAfterCommon,
780) -> Result<(&[u8], String), String> {
781    match header_type {
782        geonetworking::NextAfterCommon::Any => {
783            Err("Currently, only BTP and IPv6 Headers can be decoded!".to_string())
784        }
785        geonetworking::NextAfterCommon::BTPA => {
786            btp![crate::transport::BasicTransportAHeader, input]
787        }
788        geonetworking::NextAfterCommon::BTPB => {
789            btp![crate::transport::BasicTransportBHeader, input]
790        }
791        geonetworking::NextAfterCommon::IPv6 => {
792            let (remaining, ipv6) =
793                crate::transport::IPv6Header::decode(input).map_err(crate::map_err_to_string)?;
794            Ok((remaining, to_ipv6_debug(ipv6)))
795        }
796    }
797}
798
799#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
800fn transcode<T: rasn::Decode + rasn::Encode>(
801    input: &[u8],
802    input_encoding_rules: crate::EncodingRules,
803    output_encoding_rules: crate::EncodingRules,
804) -> Result<String, String> {
805    use core::fmt::Write as _;
806
807    if let (crate::EncodingRules::UPER, crate::EncodingRules::UPER) =
808        (input_encoding_rules, output_encoding_rules)
809    {
810        return input.iter().try_fold(String::new(), |mut acc, byte| {
811            write!(&mut acc, "{byte:02X?}")
812                .map_err(crate::map_err_to_string)
813                .map(|_| acc)
814        });
815    }
816    let decoded: T = input_encoding_rules
817        .codec()
818        .decode_from_binary(input)
819        .map_err(crate::map_err_to_string)?;
820    match output_encoding_rules {
821        crate::EncodingRules::UPER => rasn::uper::encode(&decoded)
822            .map(hex::encode)
823            .map_err(crate::map_err_to_string),
824        o => o
825            .codec()
826            .encode_to_string(&decoded)
827            .map_err(crate::map_err_to_string),
828    }
829}
830
831#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
832fn to_ipv6_debug(ipv6: crate::transport::IPv6Header) -> String {
833    alloc::format!(r#"{{"ipv6Debug":"{ipv6:?}"}}"#)
834}
835
836#[cfg(all(test, feature = "_etsi"))]
837mod tests {
838    use crate::de::message_type;
839
840    #[test]
841    fn recognizes_message_type_and_version() {
842        assert_eq!((crate::EncodingRules::XER, 2,14), message_type("<CPM><header><protocolVersion>2</protocolVersion><messageID>14</messageID><stationID>".as_bytes()).unwrap());
843        assert_eq!(
844            (crate::EncodingRules::XER, 1, 5),
845            message_type(
846                r#"<?xml version="1.0"?><MAPEM><header><protocolVersion>  1  </protocolVersion><messageID>
847        5
848        </messageID><stationID>"#
849                    .as_bytes()
850            )
851            .unwrap()
852        );
853        assert_eq!(
854            (crate::EncodingRules::JER, 2, 2),
855            message_type(
856                r#"{"header":{"protocolVersion":2,"messageID":2,"stationID":2624309139}"#
857                    .as_bytes()
858            )
859            .unwrap()
860        );
861        assert_eq!(
862            (crate::EncodingRules::JER, 1, 9),
863            message_type(
864                r#"{
865                    "header": {
866                            "protocolVersion": 1,
867                            "messageID": 9,
868                            "stationID": 2624309139
869                    }"#
870                .as_bytes()
871            )
872            .unwrap()
873        );
874    }
875}