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