#![allow(non_snake_case)]
#[cfg(feature = "_etsi")]
use alloc::borrow::ToOwned;
#[cfg(any(
feature = "_etsi",
all(target_arch = "wasm32", feature = "v2x", feature = "json"),
all(test, feature = "_etsi")
))]
use alloc::string::String;
#[cfg(any(feature = "_etsi", feature = "transport"))]
use alloc::string::ToString;
#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
use core::fmt::Write;
#[cfg(any(
feature = "_etsi",
all(target_arch = "wasm32", feature = "v2x", feature = "json"),
all(test, feature = "_etsi")
))]
use core::primitive::str;
#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
use geonetworking::Encode;
#[cfg(feature = "transport")]
use geonetworking::{Decode, NextAfterCommon, Packet};
#[cfg(any(
feature = "_etsi",
all(target_arch = "wasm32", feature = "v2x", feature = "json"),
all(test, feature = "_etsi")
))]
use nom::FindSubstring;
#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
use wasm_bindgen::prelude::*;
#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
use crate::JsonItsMessage;
#[cfg(any(feature = "transport", feature = "_etsi"))]
use crate::map_err_to_string;
#[cfg(feature = "_etsi")]
use crate::pcap::{remove_pcap_headers, remove_wlan_headers};
#[cfg(feature = "transport")]
use crate::transport::TransportHeader;
#[cfg(feature = "transport")]
use crate::transport::{
BasicTransportAHeader,
BasicTransportBHeader,
IPv6Header,
decode::Decode as TransportDecode,
};
#[cfg(any(
feature = "_etsi",
all(target_arch = "wasm32", feature = "v2x", feature = "json"),
all(test, feature = "_etsi")
))]
use crate::{EncodingRules, standards::cdd_2_2_1::etsi_its_cdd::ItsPduHeader};
#[cfg(feature = "_etsi")]
use crate::{Headers, ItsMessage, standards};
#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
macro_rules! btp {
($btp_ty:ty, $input:ident) => {
<$btp_ty>::decode($input)
.map_err(map_err_to_string)
.and_then(|(rem, tp)| {
tp.encode_to_json()
.map_err(map_err_to_string)
.map(|json| (rem, json))
})
};
}
#[cfg(feature = "_etsi")]
#[allow(clippy::too_many_lines)]
pub fn decode(input: &'_ [u8], headers: Headers) -> Result<ItsMessage<'_>, alloc::string::String> {
let (input, transport, geonetworking) = match headers {
Headers::None => Ok((input, None, None)),
Headers::GnBtp => {
decode_gn_btp_headers(input).map(|(rem, tp, gn)| (rem, Some(tp), Some(gn)))
}
Headers::IEEE802LlcGnBtp => remove_wlan_headers(input)
.and_then(decode_gn_btp_headers)
.map(|(rem, tp, gn)| (rem, Some(tp), Some(gn))),
Headers::RadioTap802LlcGnBtp => remove_pcap_headers(input)
.and_then(decode_gn_btp_headers)
.map(|(rem, tp, gn)| (rem, Some(tp), Some(gn))),
}?;
let (encoding_rules, mut protocol_version, msg_type) = message_type(input)?;
let input = match msg_type {
1 | 6 => {
if let Ok(data) = str::from_utf8(input)
&& (data.trim_start().starts_with('<') || data.trim_start().starts_with('{'))
&& data.contains("messageID")
{
protocol_version = 1;
}
input.to_owned()
}
4 | 5 | 9 | 10 => {
if let Ok(data) = str::from_utf8(input) {
if data.trim_start().starts_with('<') && data.contains("messageID") {
let patched_msg = match data.find("</header") {
Some(header_end_pos) => {
let (header, remains) = data.split_at(header_end_pos);
let patched_msg = header.replace("messageID", "messageId");
let patched_msg = patched_msg.replace("stationID", "stationId");
patched_msg + remains
}
None => data.to_string(),
};
patched_msg.as_bytes().to_owned()
} else if data.trim_start().starts_with('{') && data.contains("messageID") {
let patched_msg = data.replace("messageID", "messageId");
let patched_msg = patched_msg.replacen("stationID", "stationId", 1);
patched_msg.as_bytes().to_owned()
} else {
input.to_owned()
}
} else {
input.to_owned()
}
}
_ => input.to_owned(),
};
match (msg_type, protocol_version) {
#[cfg(feature = "denm_2_2_1")]
(1, 2) => encoding_rules
.codec()
.decode_from_binary::<standards::denm_2_2_1::denm_pdu_description::DENM>(&input)
.map(|etsi| ItsMessage::DenmV2 {
geonetworking,
transport,
etsi: alloc::boxed::Box::new(etsi)
}),
#[cfg(feature = "denm_1_3_1")]
(1, _) => encoding_rules
.codec()
.decode_from_binary::<standards::denm_1_3_1::denm_pdu_descriptions::DENM>(&input)
.map(|etsi| ItsMessage::DenmV1 {
geonetworking,
transport,
etsi: alloc::boxed::Box::new(etsi)
}),
#[cfg(feature = "cam_1_4_1")]
(2, _) => encoding_rules
.codec()
.decode_from_binary::<standards::cam_1_4_1::cam_pdu_descriptions::CAM>(&input)
.map(|etsi| ItsMessage::Cam {
geonetworking,
transport,
etsi: alloc::boxed::Box::new(etsi)
}),
#[cfg(feature = "spatem_2_2_1")]
(4, _) => encoding_rules
.codec()
.decode_from_binary::<standards::spatem_2_2_1::spatem_pdu_descriptions::SPATEM>(&input)
.map(|etsi| ItsMessage::Spatem {
geonetworking,
transport,
etsi: alloc::boxed::Box::new(etsi)
}),
#[cfg(feature = "mapem_2_2_1")]
(5, _) => encoding_rules
.codec()
.decode_from_binary::<standards::mapem_2_2_1::mapem_pdu_descriptions::MAPEM>(&input)
.map(|etsi| ItsMessage::Mapem {
geonetworking,
transport,
etsi: alloc::boxed::Box::new(etsi)
}),
#[cfg(feature = "ivim_2_2_1")]
(6, 2) => encoding_rules
.codec()
.decode_from_binary::<standards::ivim_2_2_1::ivim_pdu_descriptions::IVIM>(&input)
.map(|etsi| ItsMessage::IvimV2 {
geonetworking,
transport,
etsi: alloc::boxed::Box::new(etsi)
}),
#[cfg(feature = "ivim_2_1_1")]
(6, _) => encoding_rules
.codec()
.decode_from_binary::<standards::ivim_2_1_1::ivim_pdu_descriptions::IVIM>(&input)
.map(|etsi| ItsMessage::IvimV1 {
geonetworking,
transport,
etsi: alloc::boxed::Box::new(etsi)
}),
#[cfg(feature = "srem_2_2_1")]
(9, _) => encoding_rules
.codec()
.decode_from_binary::<standards::srem_2_2_1::srem_pdu_descriptions::SREM>(&input)
.map(|etsi| ItsMessage::Srem {
geonetworking,
transport,
etsi: alloc::boxed::Box::new(etsi)
}),
#[cfg(feature = "ssem_2_2_1")]
(10, _) => encoding_rules
.codec()
.decode_from_binary::<standards::ssem_2_2_1::ssem_pdu_descriptions::SSEM>(&input)
.map(|etsi| ItsMessage::Ssem {
geonetworking,
transport,
etsi: alloc::boxed::Box::new(etsi)
}),
#[cfg(feature = "cpm_2_1_1")]
(14, 2) => encoding_rules
.codec()
.decode_from_binary::<standards::cpm_2_1_1::cpm_pdu_descriptions::CollectivePerceptionMessage>(&input)
.map(|etsi| ItsMessage::CpmV2 {
geonetworking,
transport,
etsi: alloc::boxed::Box::new(etsi)
}),
#[cfg(feature = "cpm_1")]
(14, _) => encoding_rules
.codec()
.decode_from_binary::<standards::cpm_1::cpm_pdu_descriptions::CPM>(&input)
.map(|etsi| ItsMessage::CpmV1 {
geonetworking,
transport,
etsi: alloc::boxed::Box::new(etsi)
}),
(message_i_d, _) => {
return Err(alloc::format!(
"Unsupported ITS message type: Found message id {message_i_d}."
))
}
}.map_err(map_err_to_string)
}
#[cfg(feature = "transport")]
pub fn decode_gn_btp_headers(
input: &'_ [u8],
) -> Result<(&'_ [u8], alloc::boxed::Box<TransportHeader>, Packet<'_>), alloc::string::String> {
let result = Packet::decode(input).map_err(map_err_to_string)?;
let payload = match &result.decoded {
Packet::Unsecured { payload, .. } => *payload,
s @ Packet::Secured { .. } => s
.secured_payload_after_gn()
.ok_or("No payload in secured geonetworking header!")?,
};
let (remaining, tp) = match result.decoded.common().next_header {
NextAfterCommon::Any => {
Err("Currently, only BTP and IPv6 Headers can be decoded!".to_string())
}
NextAfterCommon::BTPA => BasicTransportAHeader::decode(payload)
.map(|(rem, btpa)| (rem, TransportHeader::BtpA(btpa)))
.map_err(map_err_to_string),
NextAfterCommon::BTPB => BasicTransportBHeader::decode(payload)
.map(|(rem, btpb)| (rem, TransportHeader::BtpB(btpb)))
.map_err(map_err_to_string),
NextAfterCommon::IPv6 => IPv6Header::decode(payload)
.map(|(rem, ipv6)| (rem, TransportHeader::IPv6(alloc::boxed::Box::new(ipv6))))
.map_err(map_err_to_string),
}?;
Ok((remaining, alloc::boxed::Box::new(tp), result.decoded))
}
#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
#[wasm_bindgen(js_name = decode)]
pub fn decode_to(
message: &[u8],
headersPresent: Headers,
outputEncodingRules: EncodingRules,
) -> Result<JsonItsMessage, String> {
let (input, mut etsi_json) = optionally_decode_headers(message, headersPresent)?;
let (input_encoding_rules, protocol_version, message_type) = message_type(input)?;
let (msg_ty, decoded) = match (message_type, protocol_version) {
(1, 2) => (
1,
decode_denm(
input,
Some(211),
Headers::None,
input_encoding_rules,
outputEncodingRules,
)?
.its,
),
(1, _) => (
1,
decode_denm(
input,
Some(131),
Headers::None,
input_encoding_rules,
outputEncodingRules,
)?
.its,
),
(2, _) => (
2,
decode_cam(
input,
None,
Headers::None,
input_encoding_rules,
outputEncodingRules,
)?
.its,
),
(4, _) => (
4,
decode_spatem(
input,
None,
Headers::None,
input_encoding_rules,
outputEncodingRules,
)?
.its,
),
(5, _) => (
5,
decode_mapem(
input,
None,
Headers::None,
input_encoding_rules,
outputEncodingRules,
)?
.its,
),
(6, 2) => (
6,
decode_ivim(
input,
Some(221),
Headers::None,
input_encoding_rules,
outputEncodingRules,
)?
.its,
),
(6, _) => (
6,
decode_ivim(
input,
Some(131),
Headers::None,
input_encoding_rules,
outputEncodingRules,
)?
.its,
),
(9, _) => (
9,
decode_srem(
input,
None,
Headers::None,
input_encoding_rules,
outputEncodingRules,
)?
.its,
),
(10, _) => (
10,
decode_ssem(
input,
None,
Headers::None,
input_encoding_rules,
outputEncodingRules,
)?
.its,
),
(14, 2) => (
14,
decode_cpm(
input,
Some(211),
Headers::None,
input_encoding_rules,
outputEncodingRules,
)?
.its,
),
(14, _) => (
14,
decode_cpm(
input,
Some(131),
Headers::None,
input_encoding_rules,
outputEncodingRules,
)?
.its,
),
(message_i_d, _) => {
return Err(format!(
"Unsupported ITS message type: Found message id {message_i_d}."
));
}
};
etsi_json.its = decoded;
etsi_json.message_type = msg_ty;
Ok(etsi_json)
}
#[cfg(any(
feature = "_etsi",
all(target_arch = "wasm32", feature = "v2x", feature = "json"),
all(test, feature = "_etsi")
))]
fn message_type(input: &[u8]) -> Result<(EncodingRules, u8, u8), String> {
let encoding_rules = match str::from_utf8(input) {
Ok(s) if s.trim_start().starts_with('<') => EncodingRules::XER,
Ok(s) if s.trim_start().starts_with('{') => EncodingRules::JER,
_ => EncodingRules::UPER,
};
match encoding_rules {
EncodingRules::XER => {
let message_id_start = input
.find_substring("messageID>")
.or(input.find_substring("messageId>"))
.ok_or("Failed to determine message ID.")?
+ 10;
let message_id_end = (&input[message_id_start..])
.find_substring("</")
.ok_or("Failed to determine message ID.")?
+ message_id_start;
let message_id = str::from_utf8(&input[message_id_start..message_id_end])
.map_err(map_err_to_string)?
.trim()
.parse()
.map_err(map_err_to_string)?;
let protocol_version_start = input
.find_substring("protocolVersion>")
.ok_or("Failed to determine protocol version.")?
+ 16;
let protocol_version_end = (&input[protocol_version_start..])
.find_substring("</")
.ok_or("Failed to determine protocol version.")?
+ protocol_version_start;
let protocol_version =
str::from_utf8(&input[protocol_version_start..protocol_version_end])
.map_err(map_err_to_string)?
.trim()
.parse()
.map_err(map_err_to_string)?;
Ok((encoding_rules, protocol_version, message_id))
}
EncodingRules::JER => {
let message_id = input
.find_substring("messageID\":")
.or(input.find_substring("messageId\":"))
.ok_or(String::from("Failed to determine message ID."))
.and_then(|start| {
let mut end = start + 11;
let mut value = input[end] as char;
while end < input.len() - 1 && (value.is_whitespace() || value.is_numeric()) {
end += 1;
value = input[end] as char;
}
str::from_utf8(&input[(start + 11)..end])
.map_err(map_err_to_string)
.and_then(|s| s.trim().parse::<u8>().map_err(map_err_to_string))
})?;
let protocol_version = input
.find_substring("protocolVersion\":")
.ok_or(String::from("Failed to determine message ID."))
.and_then(|start| {
let mut end = start + 17;
let mut value = input[end] as char;
while end < input.len() - 1 && (value.is_whitespace() || value.is_numeric()) {
end += 1;
value = input[end] as char;
}
str::from_utf8(&input[(start + 17)..end])
.map_err(map_err_to_string)
.and_then(|s| s.trim().parse::<u8>().map_err(map_err_to_string))
})?;
Ok((encoding_rules, protocol_version, message_id))
}
EncodingRules::UPER => EncodingRules::UPER
.codec()
.decode_from_binary::<ItsPduHeader>(input)
.map(|header| {
(
encoding_rules,
header.protocol_version.0,
header.message_id.0,
)
})
.map_err(map_err_to_string),
}
}
#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
fn decode_denm(
denm: &[u8],
mut version: Option<u32>,
headers_present: Headers,
input_encoding_rules: EncodingRules,
output_encoding_rules: EncodingRules,
) -> Result<JsonItsMessage, String> {
let (input, mut etsi_json) = optionally_decode_headers(denm, headers_present)?;
if version.is_none() {
version = match input.first() {
Some(1) => Some(131),
Some(2) => Some(211),
_ => None,
};
}
etsi_json.its = match version {
Some(131) => Some(transcode::<
standards::denm_1_3_1::denm_pdu_descriptions::DENM,
>(input, input_encoding_rules, output_encoding_rules))
.transpose(),
None | Some(221) => Some(
transcode::<standards::denm_2_2_1::denm_pdu_description::DENM>(
input,
input_encoding_rules,
output_encoding_rules,
),
)
.transpose(),
_ => {
return Err(
"Unsupported DENM version: Supported DENM versions are 131 and 221.".to_string(),
);
}
}?;
Ok(etsi_json)
}
#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
fn decode_cam(
cam: &[u8],
version: Option<u32>,
headers_present: Headers,
input_encoding_rules: EncodingRules,
output_encoding_rules: EncodingRules,
) -> Result<JsonItsMessage, String> {
let (input, mut etsi_json) = optionally_decode_headers(cam, headers_present)?;
etsi_json.its = match version {
None | Some(141) => Some(
transcode::<standards::cam_1_4_1::cam_pdu_descriptions::CAM>(
input,
input_encoding_rules,
output_encoding_rules,
),
)
.transpose(),
_ => return Err("Unsupported DENM version: Supported CAM version is 141.".to_string()),
}?;
Ok(etsi_json)
}
#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
fn decode_mapem(
mapem: &[u8],
version: Option<u32>,
headers_present: Headers,
input_encoding_rules: EncodingRules,
output_encoding_rules: EncodingRules,
) -> Result<JsonItsMessage, String> {
let (input, mut etsi_json) = optionally_decode_headers(mapem, headers_present)?;
etsi_json.its = match version {
None | Some(221) => Some(transcode::<
standards::mapem_2_2_1::mapem_pdu_descriptions::MAPEM,
>(input, input_encoding_rules, output_encoding_rules))
.transpose(),
_ => return Err("Unsupported MAPEM version: Supported MAPEM version is 221.".to_string()),
}?;
Ok(etsi_json)
}
#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
fn decode_spatem(
spatem: &[u8],
version: Option<u32>,
headers_present: Headers,
input_encoding_rules: EncodingRules,
output_encoding_rules: EncodingRules,
) -> Result<JsonItsMessage, String> {
let (input, mut etsi_json) = optionally_decode_headers(spatem, headers_present)?;
etsi_json.its = match version {
None | Some(131) => Some(transcode::<
standards::spatem_2_2_1::spatem_pdu_descriptions::SPATEM,
>(input, input_encoding_rules, output_encoding_rules))
.transpose(),
_ => {
return Err("Unsupported SPATEM version: Supported SPATEM version is 131.".to_string());
}
}?;
Ok(etsi_json)
}
#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
fn decode_ivim(
ivim: &[u8],
mut version: Option<u32>,
headers_present: Headers,
input_encoding_rules: EncodingRules,
output_encoding_rules: EncodingRules,
) -> Result<JsonItsMessage, String> {
let (input, mut etsi_json) = optionally_decode_headers(ivim, headers_present)?;
if version.is_none() {
version = match input.first() {
Some(1) => Some(211),
Some(2) => Some(221),
_ => None,
};
}
etsi_json.its = match version {
Some(131) | Some(211) => Some(transcode::<
standards::ivim_2_1_1::ivim_pdu_descriptions::IVIM,
>(input, input_encoding_rules, output_encoding_rules))
.transpose(),
None | Some(221) => Some(transcode::<
standards::ivim_2_2_1::ivim_pdu_descriptions::IVIM,
>(input, input_encoding_rules, output_encoding_rules))
.transpose(),
_ => {
return Err(
"Unsupported IVIM version: Supported IVIM versions are 131, 211 and 221."
.to_string(),
);
}
}?;
Ok(etsi_json)
}
#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
fn decode_srem(
srem: &[u8],
version: Option<u32>,
headers_present: Headers,
input_encoding_rules: EncodingRules,
output_encoding_rules: EncodingRules,
) -> Result<JsonItsMessage, String> {
let (input, mut etsi_json) = optionally_decode_headers(srem, headers_present)?;
etsi_json.its = match version {
None | Some(221) => Some(transcode::<
standards::srem_2_2_1::srem_pdu_descriptions::SREM,
>(input, input_encoding_rules, output_encoding_rules))
.transpose(),
_ => return Err("Unsupported SREM version: Supported SREM version is 221.".to_string()),
}?;
Ok(etsi_json)
}
#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
fn decode_cpm(
cpm: &[u8],
mut version: Option<u32>,
headers_present: Headers,
input_encoding_rules: EncodingRules,
output_encoding_rules: EncodingRules,
) -> Result<JsonItsMessage, String> {
let (input, mut etsi_json) = optionally_decode_headers(cpm, headers_present)?;
if version.is_none() {
version = match input.first() {
Some(1) => Some(131),
Some(2) => Some(211),
_ => None,
};
}
etsi_json.its = match version {
None | Some(211) => Some(transcode::<
standards::cpm_2_1_1::cpm_pdu_descriptions::CollectivePerceptionMessage,
>(input, input_encoding_rules, output_encoding_rules))
.transpose(),
Some(131) => Some(transcode::<standards::cpm_1::cpm_pdu_descriptions::CPM>(
input,
input_encoding_rules,
output_encoding_rules,
))
.transpose(),
_ => {
return Err(
"Unsupported CPM version: Supported CPM versions are 131 and 211.".to_string(),
);
}
}?;
Ok(etsi_json)
}
#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
fn decode_ssem(
ssem: &[u8],
version: Option<u32>,
headers_present: Headers,
input_encoding_rules: EncodingRules,
output_encoding_rules: EncodingRules,
) -> Result<JsonItsMessage, String> {
let (input, mut etsi_json) = optionally_decode_headers(ssem, headers_present)?;
etsi_json.its = match version {
None | Some(221) => Some(transcode::<
standards::ssem_2_2_1::ssem_pdu_descriptions::SSEM,
>(input, input_encoding_rules, output_encoding_rules))
.transpose(),
_ => return Err("Unsupported SSEM version: Supported SSEM version is 221.".to_string()),
}?;
Ok(etsi_json)
}
#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
pub fn optionally_decode_headers(
input: &[u8],
headers: Headers,
) -> Result<(&[u8], JsonItsMessage), String> {
match headers {
Headers::None => Ok((input, JsonItsMessage::default())),
Headers::GnBtp => transcode_gn_tp_to_json(input),
Headers::IEEE802LlcGnBtp => remove_wlan_headers(input).and_then(transcode_gn_tp_to_json),
Headers::RadioTap802LlcGnBtp => {
remove_pcap_headers(input).and_then(transcode_gn_tp_to_json)
}
}
}
#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
fn transcode_gn_tp_to_json(input: &[u8]) -> Result<(&[u8], JsonItsMessage), String> {
decode_geonetworking_header(input).and_then(|(remaining, gn_json, next_header)| {
decode_transport_header(remaining, next_header).map(|(rem, tp)| {
(
rem,
JsonItsMessage {
geonetworking: Some(gn_json),
transport: Some(tp),
..Default::default()
},
)
})
})
}
#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
fn decode_geonetworking_header(input: &[u8]) -> Result<(&[u8], String, NextAfterCommon), String> {
let result = Packet::decode(input).map_err(map_err_to_string)?;
let gn_json = result.decoded.encode_to_json().map_err(map_err_to_string)?;
match result.decoded {
Packet::Unsecured {
common, payload, ..
} => Ok((payload, gn_json, common.next_header)),
p => p
.secured_payload_after_gn()
.ok_or("Secured GeoNetworking Packet carries no data!".into())
.map(|payload| (payload, gn_json, p.common().next_header)),
}
}
#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
fn decode_transport_header(
input: &[u8],
header_type: NextAfterCommon,
) -> Result<(&[u8], String), String> {
match header_type {
NextAfterCommon::Any => {
Err("Currently, only BTP and IPv6 Headers can be decoded!".to_string())
}
NextAfterCommon::BTPA => btp![BasicTransportAHeader, input],
NextAfterCommon::BTPB => btp![BasicTransportBHeader, input],
NextAfterCommon::IPv6 => {
let (remaining, ipv6) = IPv6Header::decode(input).map_err(map_err_to_string)?;
Ok((remaining, to_ipv6_debug(ipv6)))
}
}
}
#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
fn transcode<T: rasn::Decode + rasn::Encode>(
input: &[u8],
input_encoding_rules: EncodingRules,
output_encoding_rules: EncodingRules,
) -> Result<String, String> {
if let (EncodingRules::UPER, EncodingRules::UPER) =
(input_encoding_rules, output_encoding_rules)
{
return input.iter().try_fold(String::new(), |mut acc, byte| {
write!(&mut acc, "{byte:02X?}")
.map_err(map_err_to_string)
.map(|_| acc)
});
}
let decoded: T = input_encoding_rules
.codec()
.decode_from_binary(input)
.map_err(map_err_to_string)?;
match output_encoding_rules {
EncodingRules::UPER => rasn::uper::encode(&decoded)
.map(hex::encode)
.map_err(map_err_to_string),
o => o
.codec()
.encode_to_string(&decoded)
.map_err(map_err_to_string),
}
}
#[cfg(all(target_arch = "wasm32", feature = "v2x", feature = "json"))]
fn to_ipv6_debug(ipv6: IPv6Header) -> String {
alloc::format!(r#"{{"ipv6Debug":"{ipv6:?}"}}"#)
}
#[cfg(all(test, feature = "_etsi"))]
mod tests {
use crate::de::message_type;
#[test]
fn recognizes_message_type_and_version() {
assert_eq!((crate::EncodingRules::XER, 2,14), message_type("<CPM><header><protocolVersion>2</protocolVersion><messageID>14</messageID><stationID>".as_bytes()).unwrap());
assert_eq!(
(crate::EncodingRules::XER, 1, 5),
message_type(
r#"<?xml version="1.0"?><MAPEM><header><protocolVersion> 1 </protocolVersion><messageID>
5
</messageID><stationID>"#
.as_bytes()
)
.unwrap()
);
assert_eq!(
(crate::EncodingRules::JER, 2, 2),
message_type(
r#"{"header":{"protocolVersion":2,"messageID":2,"stationID":2624309139}"#
.as_bytes()
)
.unwrap()
);
assert_eq!(
(crate::EncodingRules::JER, 1, 9),
message_type(
r#"{
"header": {
"protocolVersion": 1,
"messageID": 9,
"stationID": 2624309139
}"#
.as_bytes()
)
.unwrap()
);
}
}