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, 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)]
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::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 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 protocol_version = 1;
119 }
120 input.to_owned()
121 }
122 4 | 5 | 9 | 10 => {
124 if let Ok(data) = str::from_utf8(input) {
125 if data.trim_start().starts_with('<') && data.contains("messageID") {
128 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 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")]
268pub 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)]
301pub 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}