Skip to main content

c_its_parser/
en.rs

1//! C-ITS Message Encoding
2//!
3//! Provides Rust and wasm functions to encode C-ITS messages.
4//! The Rust API just needs one method ([`ItsMessage::encode`]) for the [`ItsMessage`] struct while wasm has one function per message type.
5
6#[cfg(not(target_arch = "wasm32"))]
7use alloc::string::ToString;
8
9#[cfg(any(
10    all(target_arch = "wasm32", feature = "json"),
11    not(target_arch = "wasm32")
12))]
13use geonetworking::{Encode, ExtendedHeader, HeaderType, UnsecuredHeader};
14#[cfg(all(target_arch = "wasm32", feature = "json"))]
15use wasm_bindgen::prelude::*;
16
17#[cfg(not(target_arch = "wasm32"))]
18use crate::ItsMessage;
19#[cfg(all(target_arch = "wasm32", feature = "json"))]
20use crate::JsonItsMessage;
21#[cfg(any(
22    all(target_arch = "wasm32", feature = "json"),
23    not(target_arch = "wasm32")
24))]
25use crate::map_err_to_string;
26#[cfg(all(target_arch = "wasm32", feature = "json"))]
27use crate::transport::encode::Encode as TpEncode;
28#[cfg(not(target_arch = "wasm32"))]
29use crate::{EncodingRules, Packet};
30
31#[cfg(all(target_arch = "wasm32", feature = "json"))]
32/// Wasm output is a Javascript uint8 array
33pub type Encoded = js_sys::Uint8Array;
34#[cfg(not(target_arch = "wasm32"))]
35/// Rust output is a `Vec<u8>`
36pub type Encoded = alloc::vec::Vec<u8>;
37
38#[cfg(not(target_arch = "wasm32"))]
39impl ItsMessage<'_> {
40    #[allow(clippy::too_many_lines)]
41    /// Encodes an ITS message with optional headers.
42    ///
43    /// Supports XER, JER, and UPER encoding rules.
44    /// XER and JER values are returned as UTF-8 buffers.
45    ///
46    /// Note: When given a secured packet in the `geonetworking` option, this will always generate an unsecured packet.
47    /// (Since the geonetworking crate doesn't support creating messages signatures.)
48    ///
49    /// Note: When selecting JER or XER rules, geonetworking and transport headers will be omitted.
50    /// If a JSON representation is required call `encode_to_json` on the geonetworking packet and transport headers.
51    ///
52    /// # Errors
53    /// Gives a human-readable error description when ASN.1 parsing failed or an unexpected set of headers was found.
54    pub fn encode(self, encoding_rules: EncodingRules) -> Result<Encoded, alloc::string::String> {
55        let (geo, tp, mut etsi_uper) = match self {
56            #[cfg(feature = "denm_1_3_1")]
57            ItsMessage::DenmV1 {
58                geonetworking,
59                transport,
60                etsi,
61            } => encoding_rules
62                .codec()
63                .encode_to_binary(&etsi)
64                .map(|enc| (geonetworking, transport, enc)),
65            #[cfg(feature = "denm_2_2_1")]
66            ItsMessage::DenmV2 {
67                geonetworking,
68                transport,
69                etsi,
70            } => encoding_rules
71                .codec()
72                .encode_to_binary(&etsi)
73                .map(|enc| (geonetworking, transport, enc)),
74            #[cfg(feature = "cam_1_4_1")]
75            ItsMessage::Cam {
76                geonetworking,
77                transport,
78                etsi,
79            } => encoding_rules
80                .codec()
81                .encode_to_binary(&etsi)
82                .map(|enc| (geonetworking, transport, enc)),
83            #[cfg(feature = "spatem_2_2_1")]
84            ItsMessage::Spatem {
85                geonetworking,
86                transport,
87                etsi,
88            } => encoding_rules
89                .codec()
90                .encode_to_binary(&etsi)
91                .map(|enc| (geonetworking, transport, enc)),
92            #[cfg(feature = "mapem_2_2_1")]
93            ItsMessage::Mapem {
94                geonetworking,
95                transport,
96                etsi,
97            } => encoding_rules
98                .codec()
99                .encode_to_binary(&etsi)
100                .map(|enc| (geonetworking, transport, enc)),
101            #[cfg(feature = "ivim_2_1_1")]
102            ItsMessage::IvimV1 {
103                geonetworking,
104                transport,
105                etsi,
106            } => encoding_rules
107                .codec()
108                .encode_to_binary(&etsi)
109                .map(|enc| (geonetworking, transport, enc)),
110            #[cfg(feature = "ivim_2_2_1")]
111            ItsMessage::IvimV2 {
112                geonetworking,
113                transport,
114                etsi,
115            } => encoding_rules
116                .codec()
117                .encode_to_binary(&etsi)
118                .map(|enc| (geonetworking, transport, enc)),
119            #[cfg(feature = "srem_2_2_1")]
120            ItsMessage::Srem {
121                geonetworking,
122                transport,
123                etsi,
124            } => encoding_rules
125                .codec()
126                .encode_to_binary(&etsi)
127                .map(|enc| (geonetworking, transport, enc)),
128            #[cfg(feature = "ssem_2_2_1")]
129            ItsMessage::Ssem {
130                geonetworking,
131                transport,
132                etsi,
133            } => encoding_rules
134                .codec()
135                .encode_to_binary(&etsi)
136                .map(|enc| (geonetworking, transport, enc)),
137            #[cfg(feature = "cpm_1")]
138            ItsMessage::CpmV1 {
139                geonetworking,
140                transport,
141                etsi,
142            } => encoding_rules
143                .codec()
144                .encode_to_binary(&etsi)
145                .map(|enc| (geonetworking, transport, enc)),
146            #[cfg(feature = "cpm_2_1_1")]
147            ItsMessage::CpmV2 {
148                geonetworking,
149                transport,
150                etsi,
151            } => encoding_rules
152                .codec()
153                .encode_to_binary(&etsi)
154                .map(|enc| (geonetworking, transport, enc)),
155        }
156        .map_err(map_err_to_string)?;
157
158        if encoding_rules == EncodingRules::UPER {
159            match (tp, geo) {
160            (None, None) => Ok(etsi_uper),
161            (
162                Some(tp),
163                Some(Packet::Unsecured {
164                    basic,
165                    common,
166                    extended,
167                    ..
168                } | Packet::Secured {
169                    basic,
170                    common,
171                    extended,
172                    ..
173                }),
174            ) => {
175                let mut encoded = tp.encode()?;
176                encoded.append(&mut etsi_uper);
177                fill_gn_and_encode(UnsecuredHeader { basic, common, extended }, &encoded)
178            }
179            _ => Err(
180                "Expecting either both or neither GeoNetworking and Transport headers to be present!"
181                    .to_string(),
182            ),
183        }
184        } else {
185            Ok(etsi_uper)
186        }
187    }
188}
189
190#[cfg(all(target_arch = "wasm32", feature = "json"))]
191#[wasm_bindgen(js_name = encodeDenm)]
192/// Encodes a DENM message into binary UPER with optional headers
193/// The encoder expects either both (GeoNetworking and Transport) headers or none
194/// Currently, denms of the following versions are supported: v2.2.1 (221) and v1.3.1 (131)
195/// Throws string error on encoding error
196pub fn encode_denm(denm: &JsonItsMessage, version: u32) -> Result<Encoded, String> {
197    let mut payload = vec![];
198    match (&denm.its, version) {
199        (None, 131) | (None, 221) => return Err("No DENM JSON provided.".to_string()),
200        (Some(denm_json), 131) => {
201            payload.append(&mut transcode_jer_to_uper::<
202                crate::standards::denm_1_3_1::denm_pdu_descriptions::DENM,
203            >(denm_json)?);
204        }
205        (Some(denm_json), 221) => {
206            payload.append(&mut transcode_jer_to_uper::<
207                crate::standards::denm_2_2_1::denm_pdu_description::DENM,
208            >(denm_json)?);
209        }
210        _ => {
211            return Err(
212                "Unsupported DENM version: Supported DENM versions are 131 and 221.".to_string(),
213            );
214        }
215    };
216    let encoded = optionally_encode_headers(&denm.geonetworking, &denm.transport, payload)?;
217    Ok(Encoded::from(encoded.as_slice()))
218}
219
220#[cfg(all(target_arch = "wasm32", feature = "json"))]
221#[wasm_bindgen(js_name = encodeCam)]
222/// Encodes a CAM message into binary UPER with optional headers
223/// The encoder expects either both (GeoNetworking and Transport) headers or none
224/// Currently, cams of the following versions are supported: v1.4.1 (141)
225/// Throws string error on encoding error
226pub fn encode_cam(cam: &JsonItsMessage, version: u32) -> Result<Encoded, String> {
227    let mut payload = vec![];
228    match (&cam.its, version) {
229        (None, 141) => return Err("No CAM JSON provided.".to_string()),
230        (Some(json), 141) => {
231            payload.append(&mut transcode_jer_to_uper::<
232                crate::standards::cam_1_4_1::cam_pdu_descriptions::CAM,
233            >(json)?);
234        }
235        _ => return Err("Unsupported CAM version: Supported CAM version is 141.".to_string()),
236    };
237    let encoded = optionally_encode_headers(&cam.geonetworking, &cam.transport, payload)?;
238    Ok(Encoded::from(encoded.as_slice()))
239}
240
241#[cfg(all(target_arch = "wasm32", feature = "json"))]
242#[wasm_bindgen(js_name = encodeMapem)]
243/// Encodes a MAPEM message into binary UPER with optional headers
244/// The encoder expects either both (GeoNetworking and Transport) headers or none
245/// Currently, mapems of the following versions are supported: v2.2.1 (221)
246/// Throws string error on encoding error
247pub fn encode_mapem(mapem: &JsonItsMessage, version: u32) -> Result<Encoded, String> {
248    let mut payload = vec![];
249    match (&mapem.its, version) {
250        (None, 221) => return Err("No MAPEM JSON provided.".to_string()),
251        (Some(json), 221) => {
252            payload.append(&mut transcode_jer_to_uper::<
253                crate::standards::mapem_2_2_1::mapem_pdu_descriptions::MAPEM,
254            >(json)?);
255        }
256        _ => return Err("Unsupported MAPEM version: Supported MAPEM version is 221.".to_string()),
257    };
258    let encoded = optionally_encode_headers(&mapem.geonetworking, &mapem.transport, payload)?;
259    Ok(Encoded::from(encoded.as_slice()))
260}
261
262#[cfg(all(target_arch = "wasm32", feature = "json"))]
263#[wasm_bindgen(js_name = encodeSpatem)]
264/// Encodes a SPATEM message into binary UPER with optional headers
265/// The encoder expects either both (GeoNetworking and Transport) headers or none
266/// Currently, spatems of the following versions are supported: v2.2.1 (221)
267/// Throws string error on encoding error
268pub fn encode_spatem(spatem: &JsonItsMessage, version: u32) -> Result<Encoded, String> {
269    let mut payload = vec![];
270    match (&spatem.its, version) {
271        (None, 221) => return Err("No SPATEM JSON provided.".to_string()),
272        (Some(json), 221) => {
273            payload.append(&mut transcode_jer_to_uper::<
274                crate::standards::spatem_2_2_1::spatem_pdu_descriptions::SPATEM,
275            >(json)?);
276        }
277        _ => {
278            return Err("Unsupported SPATEM version: Supported SPATEM version is 221.".to_string());
279        }
280    };
281    let encoded = optionally_encode_headers(&spatem.geonetworking, &spatem.transport, payload)?;
282    Ok(Encoded::from(encoded.as_slice()))
283}
284
285#[cfg(all(target_arch = "wasm32", feature = "json"))]
286#[wasm_bindgen(js_name = encodeIvim)]
287/// Encodes a IVIM message into binary UPER with optional headers
288/// The encoder expects either both (GeoNetworking and Transport) headers or none
289/// Currently, ivims of the following versions are supported: v1.3.1 (131)/ v2.1.1 (211), v2.2.1 (221)
290/// Throws string error on encoding error
291pub fn encode_ivim(ivim: &JsonItsMessage, version: u32) -> Result<Encoded, String> {
292    let mut payload = vec![];
293    match (&ivim.its, version) {
294        (None, 131) | (None, 211) | (None, 221) => return Err("No IVIM JSON provided.".to_string()),
295        (Some(json), 211) | (Some(json), 131) => {
296            payload.append(&mut transcode_jer_to_uper::<
297                crate::standards::ivim_2_1_1::ivim_pdu_descriptions::IVIM,
298            >(json)?);
299        }
300        (Some(json), 221) => {
301            payload.append(&mut transcode_jer_to_uper::<
302                crate::standards::ivim_2_2_1::ivim_pdu_descriptions::IVIM,
303            >(json)?);
304        }
305        _ => {
306            return Err(
307                "Unsupported IVIM version: Supported IVIM versions are 131, 211 and 221."
308                    .to_string(),
309            );
310        }
311    };
312    let encoded = optionally_encode_headers(&ivim.geonetworking, &ivim.transport, payload)?;
313    Ok(Encoded::from(encoded.as_slice()))
314}
315
316#[cfg(all(target_arch = "wasm32", feature = "json"))]
317#[wasm_bindgen(js_name = encodeSrem)]
318/// Encodes a SREM message into binary UPER with optional headers
319/// The encoder expects either both (GeoNetworking and Transport) headers or none
320/// Currently, srems of the following versions are supported: v2.2.1 (221)
321/// Throws string error on encoding error
322pub fn encode_srem(srem: &JsonItsMessage, version: u32) -> Result<Encoded, String> {
323    let mut payload = vec![];
324    match (&srem.its, version) {
325        (None, 221) => return Err("No SREM JSON provided.".to_string()),
326        (Some(json), 221) => {
327            payload.append(&mut transcode_jer_to_uper::<
328                crate::standards::srem_2_2_1::srem_pdu_descriptions::SREM,
329            >(json)?);
330        }
331        _ => return Err("Unsupported SREM version: Supported SREM version is 221.".to_string()),
332    };
333    let encoded = optionally_encode_headers(&srem.geonetworking, &srem.transport, payload)?;
334    Ok(Encoded::from(encoded.as_slice()))
335}
336
337#[cfg(all(target_arch = "wasm32", feature = "json"))]
338#[wasm_bindgen(js_name = encodeCpm)]
339/// Encodes a CPM message into binary UPER with optional headers
340/// The encoder expects either both (GeoNetworking and Transport) headers or none
341/// Currently, cpms of the following versions are supported: v1.3.1 (131), v2.1.1 (211)
342/// Throws string error on encoding error
343pub fn encode_cpm(cpm: &JsonItsMessage, version: u32) -> Result<Encoded, String> {
344    let mut payload = vec![];
345    match (&cpm.its, version) {
346        (None, 131) | (None, 211) => return Err("No CPM JSON provided.".to_string()),
347        (Some(json), 131) => {
348            payload.append(&mut transcode_jer_to_uper::<
349                crate::standards::cpm_1::cpm_pdu_descriptions::CPM,
350            >(json)?);
351        }
352        (Some(json), 211) => {
353            payload.append(&mut transcode_jer_to_uper::<
354                crate::standards::cpm_2_1_1::cpm_pdu_descriptions::CollectivePerceptionMessage,
355            >(json)?);
356        }
357        _ => {
358            return Err(
359                "Unsupported CPM version: Supported CPM versions are 131 and 211.".to_string(),
360            );
361        }
362    };
363    let encoded = optionally_encode_headers(&cpm.geonetworking, &cpm.transport, payload)?;
364    Ok(Encoded::from(encoded.as_slice()))
365}
366
367#[cfg(all(target_arch = "wasm32", feature = "json"))]
368#[wasm_bindgen(js_name = encodeSsem)]
369/// Encodes a SSEM message into binary UPER with optional headers
370/// The encoder expects either both (GeoNetworking and Transport) headers or none
371/// Currently, ssems of the following versions are supported: v2.2.1 (221)
372/// Throws string error on encoding error
373pub fn encode_ssem(ssem: &JsonItsMessage, version: u32) -> Result<Encoded, String> {
374    let mut payload = vec![];
375    match (&ssem.its, version) {
376        (None, 221) => return Err("No SSEM JSON provided.".to_string()),
377        (Some(json), 221) => {
378            payload.append(&mut transcode_jer_to_uper::<
379                crate::standards::ssem_2_2_1::ssem_pdu_descriptions::SSEM,
380            >(json)?);
381        }
382        _ => return Err("Unsupported SSEM version: Supported SSEM version is 221.".to_string()),
383    };
384    let encoded = optionally_encode_headers(&ssem.geonetworking, &ssem.transport, payload)?;
385    Ok(Encoded::from(encoded.as_slice()))
386}
387
388#[cfg(all(target_arch = "wasm32", feature = "json"))]
389fn optionally_encode_headers(
390    gn_json: &Option<String>,
391    tp_json: &Option<String>,
392    mut its: alloc::vec::Vec<u8>,
393) -> Result<alloc::vec::Vec<u8>, String> {
394    match (gn_json, tp_json) {
395        (Some(_), None) | (None, Some(_)) => Err(
396            "Expecting either both or neither GeoNetworking and Transport headers to be present!"
397                .to_string(),
398        ),
399        (Some(gn), Some(tp)) => {
400            let geonetworking = UnsecuredHeader::from_json(gn).map_err(map_err_to_string)?;
401            let mut transport = match geonetworking.common.next_header {
402                geonetworking::NextAfterCommon::BTPA => {
403                    crate::transport::BasicTransportAHeader::decode_from_json(tp)
404                        .map_err(map_err_to_string)?
405                        .encode()
406                        .map_err(map_err_to_string)?
407                }
408                geonetworking::NextAfterCommon::BTPB => {
409                    crate::transport::BasicTransportBHeader::decode_from_json(tp)
410                        .map_err(map_err_to_string)?
411                        .encode()
412                        .map_err(map_err_to_string)?
413                }
414                h => {
415                    return Err(alloc::format!(
416                        "Currently only BTP-A and BTP-B headers can be encoded: Encountered {h:?}"
417                    ));
418                }
419            };
420            transport.append(&mut its);
421            fill_gn_and_encode(geonetworking, &transport)
422        }
423        _ => Ok(its),
424    }
425}
426
427#[cfg(any(
428    all(target_arch = "wasm32", feature = "json"),
429    not(target_arch = "wasm32")
430))]
431fn fill_gn_and_encode(
432    mut geonetworking: UnsecuredHeader,
433    payload: &[u8],
434) -> Result<alloc::vec::Vec<u8>, alloc::string::String> {
435    #[allow(clippy::cast_possible_truncation)]
436    let gn_payload_length = payload.len() as u16;
437
438    geonetworking.common.payload_length = gn_payload_length;
439    geonetworking.common.header_type_and_subtype = match geonetworking.extended {
440        Some(ExtendedHeader::Beacon(_)) => HeaderType::Beacon,
441        Some(ExtendedHeader::GAC(_)) => HeaderType::GeoAnycast(geonetworking::AreaType::Circular),
442        Some(ExtendedHeader::GBC(_)) => HeaderType::GeoBroadcast(geonetworking::AreaType::Circular),
443        Some(ExtendedHeader::GUC(_)) => HeaderType::GeoUnicast,
444        Some(ExtendedHeader::TSB(_)) => {
445            HeaderType::TopologicallyScopedBroadcast(geonetworking::BroadcastType::MultiHop)
446        }
447        Some(ExtendedHeader::SHB(_)) => {
448            HeaderType::TopologicallyScopedBroadcast(geonetworking::BroadcastType::SingleHop)
449        }
450        Some(ExtendedHeader::LSRequest(_)) => {
451            HeaderType::LocationService(geonetworking::LocationServiceType::Request)
452        }
453        Some(ExtendedHeader::LSReply(_)) => {
454            HeaderType::LocationService(geonetworking::LocationServiceType::Reply)
455        }
456        None => HeaderType::Any,
457    };
458    geonetworking
459        .with_payload(payload)
460        .map_err(map_err_to_string)?
461        .encode_to_vec()
462        .map_err(map_err_to_string)
463}
464
465#[cfg(all(target_arch = "wasm32", feature = "json"))]
466fn transcode_jer_to_uper<T: rasn::Decode + rasn::Encode>(
467    input: &String,
468) -> Result<alloc::vec::Vec<u8>, String> {
469    rasn::uper::encode(&rasn::jer::decode::<T>(input).map_err(map_err_to_string)?)
470        .map_err(map_err_to_string)
471}