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