1#![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)]
27pub 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 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 protocol_version = 1;
76 }
77 input.to_owned()
78 }
79 4 | 5 | 9 | 10 => {
81 if let Ok(data) = core::primitive::str::from_utf8(input) {
82 if data.trim_start().starts_with('<') && data.contains("messageID") {
85 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 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")]
225pub 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)]
277pub 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}