1#![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)]
78pub 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 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 protocol_version = 1;
116 }
117 input.to_owned()
118 }
119 4 | 5 | 9 | 10 => {
121 if let Ok(data) = str::from_utf8(input) {
122 if data.trim_start().starts_with('<') && data.contains("messageID") {
125 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 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")]
265pub 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)]
298pub 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}