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