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