Skip to main content

c_its_parser/
lib.rs

1//! Parser and encoder for ETSI C-ITS (V2X) messages including GeoNetworking headers and optionally Radiotap and IEEE 802.11 headers.
2//! It supports UPER, XER and JER ASN.1 encodings for parsing and encoding.
3//!
4//! Supported messages/ standards: See documentation of the [`ItsMessage`] variants.
5//!
6//! ## Decoding
7//!
8//! A packet can be decoded using the [`de::decode`] function:
9//!
10//! ```
11//! let data = &[0x02, 0x02, 0xde, 0x14, 0x0c, 0xe5]; // provide actual message buffer here
12//! #[cfg(feature = "_etsi")]
13//! match c_its_parser::de::decode(data, c_its_parser::Headers::RadioTap802LlcGnBtp) {
14//!     #[cfg(feature = "cam_1_4_1")]
15//!     Ok(c_its_parser::ItsMessage::Cam {
16//!         geonetworking: _,
17//!         transport: _,
18//!         etsi: cam,
19//!     }) => {
20//!         println!("Got a CAM: {cam:?}")
21//!     }
22//!     Ok(msg) => println!("Got: {msg:?}"),
23//!     Err(err) => println!("Failed to parse message: {err}"),
24//! }
25//! ```
26//!
27//! The `headers` argument needs to specify which headers are present: None, GeoNetworking + BTP or Radiotap + 802.11p + LLC + GeoNetworking + BTP.
28//! Headers are expected to be present in binary form.
29//! When no headers are present, it can auto-detect the ASN.1 encoding (UPER/ XER/ JER) and decodes the message.
30//! This means, that XER and JER message buffers can only be decoded without headers.
31//!
32//! ## Encoding
33//!
34//! To encode an [`ItsMessage`] struct, call the [`encode()`](`ItsMessage::encode`) method supplying the intended encoding rules.
35//! Again, XER and JER messages can only be encoded without headers.
36//! GeoNetworking and transport (BTP) headers will be added when present if UPER encoding is used.
37//!
38//! ## Feature Flags
39//!
40//! This library has several feature flags to allow fine-grained control over the feature set and additional dependencies.
41//!
42//! By default, all V2X messages and conversion to and from JSON are enabled.
43//! If only some messages, or even just specific versions of messages are needed, they can be enabled one-by-one, e.g. using `denm` to enable both `denm_1_3_1` and `denm_2_2_1` support.
44//!
45//! When no parsing of the geonetworking and pcap headers is needed, the `transport` feature can be disabled.
46//!
47//! Besides parsing, the Rust API also provides helper functions to convert between ETSI data types and "normal"/ SI units.
48//! Additional conversions are only available by adding some feature flags:
49//!
50//! - `time`: Enable conversions to [chrono](https://crates.io/crates/chrono) timestamps
51//! - `geo`: Enable conversions to [geo-types](https://crates.io/crates/geo-types) (as lon/lat coordinates in degrees)
52
53#![cfg_attr(not(target_arch = "wasm32"), no_std)]
54#![cfg_attr(docsrs, feature(doc_cfg))]
55
56extern crate alloc;
57
58#[cfg(any(feature = "std", target_arch = "wasm32", test))]
59extern crate std;
60
61pub mod de;
62#[cfg(feature = "_etsi")]
63pub mod en;
64#[cfg(feature = "_etsi")]
65#[allow(clippy::all, clippy::pedantic, clippy::nursery, dead_code)]
66pub mod standards;
67
68#[cfg(feature = "geo")]
69pub mod geo_utils;
70#[cfg(feature = "time")]
71pub mod time_utils;
72
73#[cfg(feature = "transport")]
74pub mod pcap;
75#[cfg(feature = "transport")]
76pub mod transport;
77
78#[cfg(feature = "transport")]
79pub use geonetworking as gn;
80#[cfg(feature = "transport")]
81#[deprecated = "use `pcap::remove_pcap_headers` instead"]
82pub use pcap::remove_pcap_headers;
83#[cfg(all(target_arch = "wasm32", any(feature = "json", feature = "_etsi")))]
84use wasm_bindgen::prelude::*;
85
86#[cfg(all(target_arch = "wasm32", feature = "json"))]
87#[wasm_bindgen(getter_with_clone)]
88#[derive(Debug, Clone, PartialEq, Default)]
89/// Wrapper for the stringified JSON of headers and ITS ETSI message
90pub struct JsonItsMessage {
91    /// Optional GeoNetworking header, encoded as stringified JSON
92    pub geonetworking: Option<String>,
93    /// Optional transport header, encoded as stringified JSON
94    pub transport: Option<String>,
95    /// Optional ITS ETSI message, encoded as a UTF-8 String for JER and XER, as a hex string for UPER
96    pub its: Option<String>,
97    /// Optional ITS ETSI message type, as specified in ETSI TS 102 894-2
98    /// - 1  - `denm`              - for Decentralized Environmental Notification Message (DENM) as specified in ETSI EN 302 637-3 [2],
99    /// - 2  - `cam`               - for Cooperative Awareness Message (CAM) as specified in ETSI EN 302 637-2 [1],
100    /// - 3  - `poi`               - for Point of Interest message as specified in ETSI TS 101 556-1 [9],
101    /// - 4  - `spatem`            - for Signal Phase And Timing Extended Message (SPATEM) as specified in ETSI TS 103 301 [15],
102    /// - 5  - `mapem`             - for MAP Extended Message (MAPEM) as specified in ETSI TS 103 301 [15],
103    /// - 6  - `ivim`              - for in Vehicle Information Message (IVIM) as specified in ETSI TS 103 301 [15],
104    /// - 7  - `ev-rsr`            - for Electric vehicle recharging spot reservation message, as defined in ETSI TS 101 556-3 [11],
105    /// - 8  - `tistpgtransaction` - for messages for Tyre Information System (TIS) and Tyre Pressure Gauge (TPG) interoperability, as specified in ETSI TS 101 556-2 [10],
106    /// - 9  - `srem`              - for Signal Request Extended Message as specified in ETSI TS 103 301 [15],
107    /// - 10 - `ssem`              - for Signal request Status Extended Message as specified in ETSI TS 103 301 [15],
108    /// - 11 - `evcsn`             - for Electrical Vehicle Charging Spot Notification message as specified in ETSI TS 101 556-1 [9],
109    /// - 12 - `saem`              - for Services Announcement Extended Message as specified in ETSI EN 302 890-1 [17],
110    /// - 13 - `rtcmem`            - for Radio Technical Commission for Maritime Services Extended Message (RTCMEM) as specified in ETSI TS 103 301 [15],
111    /// - 14 - `cpm`               - reserved for Collective Perception Message (CPM),
112    /// - 15 - `imzm`              - for Interference Management Zone Message (IMZM) as specified in ETSI TS 103 724 [13],
113    /// - 16 - `vam`               - for Vulnerable Road User Awareness Message as specified in ETSI TS 130 300-3 [12],
114    /// - 17 - `dsm`               - reserved for Diagnosis, logging and Status Message,
115    /// - 18 - `pcim`              - reserved for Parking Control Infrastructure Message,
116    /// - 19 - `pcvm`              - reserved for Parking Control Vehicle Message,
117    /// - 20 - `mcm`               - reserved for Manoeuver Coordination Message,
118    /// - 21 - `pam`               - reserved for Parking Availability Message,
119    /// - 22-255                   - reserved for future usage.
120    pub message_type: u8,
121}
122
123#[cfg(all(target_arch = "wasm32", feature = "json"))]
124#[wasm_bindgen]
125impl JsonItsMessage {
126    #[wasm_bindgen(constructor)]
127    pub fn from(
128        its: Option<String>,
129        geonetworking: Option<String>,
130        transport: Option<String>,
131        message_type: u8,
132    ) -> Self {
133        Self {
134            its,
135            geonetworking,
136            transport,
137            message_type,
138        }
139    }
140}
141
142#[cfg(feature = "_etsi")]
143#[derive(Debug, Clone, PartialEq)]
144/// Wrapper for C-ITS messages
145///
146/// Each message consists of the `etsi` data and can optionally contain a `transport` (BTP) and a `geonetworking` header.
147pub enum ItsMessage<'a> {
148    #[cfg(feature = "denm_1_3_1")]
149    /// ETSI EN 302 637-3 v1.3.1 DENM
150    DenmV1 {
151        geonetworking: Option<geonetworking::Packet<'a>>,
152        transport: Option<alloc::boxed::Box<transport::TransportHeader>>,
153        etsi: alloc::boxed::Box<standards::denm_1_3_1::denm_pdu_descriptions::DENM>,
154    },
155    #[cfg(feature = "denm_2_2_1")]
156    /// ETSI TS 103 831 v2.2.1 (or v2.1.1) DENM
157    DenmV2 {
158        geonetworking: Option<geonetworking::Packet<'a>>,
159        transport: Option<alloc::boxed::Box<transport::TransportHeader>>,
160        etsi: alloc::boxed::Box<standards::denm_2_2_1::denm_pdu_description::DENM>,
161    },
162    #[cfg(feature = "cam_1_4_1")]
163    /// ETSI TS 103 301 v2.2.1 (or v2.1.1 or v1.3.1) CAM
164    Cam {
165        geonetworking: Option<geonetworking::Packet<'a>>,
166        transport: Option<alloc::boxed::Box<transport::TransportHeader>>,
167        etsi: alloc::boxed::Box<standards::cam_1_4_1::cam_pdu_descriptions::CAM>,
168    },
169    #[cfg(feature = "spatem_2_2_1")]
170    /// ETSI TS 103 301 v2.2.1 (or v2.1.1 or v1.3.1) SPATEM
171    Spatem {
172        geonetworking: Option<geonetworking::Packet<'a>>,
173        transport: Option<alloc::boxed::Box<transport::TransportHeader>>,
174        etsi: alloc::boxed::Box<standards::spatem_2_2_1::spatem_pdu_descriptions::SPATEM>,
175    },
176    #[cfg(feature = "mapem_2_2_1")]
177    /// ETSI TS 103 301 v2.2.1 (or v2.1.1 or v1.3.1) MAPEM
178    Mapem {
179        geonetworking: Option<geonetworking::Packet<'a>>,
180        transport: Option<alloc::boxed::Box<transport::TransportHeader>>,
181        etsi: alloc::boxed::Box<standards::mapem_2_2_1::mapem_pdu_descriptions::MAPEM>,
182    },
183    #[cfg(feature = "ivim_2_1_1")]
184    /// ETSI TS 103 301 v2.1.1 (or v1.3.1) IVIM
185    IvimV1 {
186        geonetworking: Option<geonetworking::Packet<'a>>,
187        transport: Option<alloc::boxed::Box<transport::TransportHeader>>,
188        etsi: alloc::boxed::Box<standards::ivim_2_1_1::ivim_pdu_descriptions::IVIM>,
189    },
190    #[cfg(feature = "ivim_2_2_1")]
191    /// ETSI TS 103 301 v2.2.1 IVIM
192    IvimV2 {
193        geonetworking: Option<geonetworking::Packet<'a>>,
194        transport: Option<alloc::boxed::Box<transport::TransportHeader>>,
195        etsi: alloc::boxed::Box<standards::ivim_2_2_1::ivim_pdu_descriptions::IVIM>,
196    },
197    #[cfg(feature = "srem_2_2_1")]
198    /// ETSI TS 103 301 v2.2.1 (or v2.1.1 or v1.3.1) SREM
199    Srem {
200        geonetworking: Option<geonetworking::Packet<'a>>,
201        transport: Option<alloc::boxed::Box<transport::TransportHeader>>,
202        etsi: alloc::boxed::Box<standards::srem_2_2_1::srem_pdu_descriptions::SREM>,
203    },
204    #[cfg(feature = "ssem_2_2_1")]
205    /// ETSI TS 103 301 v2.2.1 (or v2.1.1 or v1.3.1) SSEM
206    Ssem {
207        geonetworking: Option<geonetworking::Packet<'a>>,
208        transport: Option<alloc::boxed::Box<transport::TransportHeader>>,
209        etsi: alloc::boxed::Box<standards::ssem_2_2_1::ssem_pdu_descriptions::SSEM>,
210    },
211    #[cfg(feature = "cpm_1")]
212    /// ETSI TR 103 562 v2.1.1 CPM
213    CpmV1 {
214        geonetworking: Option<geonetworking::Packet<'a>>,
215        transport: Option<alloc::boxed::Box<transport::TransportHeader>>,
216        etsi: alloc::boxed::Box<standards::cpm_1::cpm_pdu_descriptions::CPM>,
217    },
218    #[cfg(feature = "cpm_2_1_1")]
219    /// ETSI TS 103 324 v2.1.1 CPM
220    CpmV2 {
221        geonetworking: Option<geonetworking::Packet<'a>>,
222        transport: Option<alloc::boxed::Box<transport::TransportHeader>>,
223        etsi: alloc::boxed::Box<
224            standards::cpm_2_1_1::cpm_pdu_descriptions::CollectivePerceptionMessage,
225        >,
226    },
227}
228
229#[cfg(feature = "_etsi")]
230impl<'a> ItsMessage<'a> {
231    /// Returns the `geonetworking` field of the `ItsMessage` variant
232    #[must_use]
233    pub fn get_geonetworking(&self) -> Option<geonetworking::Packet<'a>> {
234        let (gn, _) = self.get_headers();
235        gn.clone()
236    }
237
238    /// Returns the `transport` field of the `ItsMessage` variant
239    #[must_use]
240    pub fn get_transport(&self) -> Option<alloc::boxed::Box<transport::TransportHeader>> {
241        let (_, tp) = self.get_headers();
242        tp.clone()
243    }
244
245    /// Returns the `geonetworking` and `transport` fields of the `ItsMessage` variant
246    #[must_use]
247    fn get_headers(
248        &self,
249    ) -> (
250        &Option<geonetworking::Packet<'a>>,
251        &Option<alloc::boxed::Box<transport::TransportHeader>>,
252    ) {
253        match self {
254            #[cfg(feature = "denm_1_3_1")]
255            ItsMessage::DenmV1 {
256                geonetworking,
257                transport,
258                etsi: _,
259            } => (geonetworking, transport),
260            #[cfg(feature = "denm_2_2_1")]
261            ItsMessage::DenmV2 {
262                geonetworking,
263                transport,
264                etsi: _,
265            } => (geonetworking, transport),
266            #[cfg(feature = "cam_1_4_1")]
267            ItsMessage::Cam {
268                geonetworking,
269                transport,
270                etsi: _,
271            } => (geonetworking, transport),
272            #[cfg(feature = "spatem_2_2_1")]
273            ItsMessage::Spatem {
274                geonetworking,
275                transport,
276                etsi: _,
277            } => (geonetworking, transport),
278            #[cfg(feature = "mapem_2_2_1")]
279            ItsMessage::Mapem {
280                geonetworking,
281                transport,
282                etsi: _,
283            } => (geonetworking, transport),
284            #[cfg(feature = "ivim_2_1_1")]
285            ItsMessage::IvimV1 {
286                geonetworking,
287                transport,
288                etsi: _,
289            } => (geonetworking, transport),
290            #[cfg(feature = "ivim_2_2_1")]
291            ItsMessage::IvimV2 {
292                geonetworking,
293                transport,
294                etsi: _,
295            } => (geonetworking, transport),
296            #[cfg(feature = "srem_2_2_1")]
297            ItsMessage::Srem {
298                geonetworking,
299                transport,
300                etsi: _,
301            } => (geonetworking, transport),
302            #[cfg(feature = "ssem_2_2_1")]
303            ItsMessage::Ssem {
304                geonetworking,
305                transport,
306                etsi: _,
307            } => (geonetworking, transport),
308            #[cfg(feature = "cpm_1")]
309            ItsMessage::CpmV1 {
310                geonetworking,
311                transport,
312                etsi: _,
313            } => (geonetworking, transport),
314            #[cfg(feature = "cpm_2_1_1")]
315            ItsMessage::CpmV2 {
316                geonetworking,
317                transport,
318                etsi: _,
319            } => (geonetworking, transport),
320        }
321    }
322}
323
324#[cfg(feature = "_etsi")]
325impl From<&ItsMessage<'_>> for standards::extensions::ItsMessageId {
326    fn from(val: &ItsMessage<'_>) -> Self {
327        match val {
328            #[cfg(feature = "denm_1_3_1")]
329            ItsMessage::DenmV1 {
330                geonetworking: _,
331                transport: _,
332                etsi: _,
333            } => Self::Denm,
334            #[cfg(feature = "denm_2_2_1")]
335            ItsMessage::DenmV2 {
336                geonetworking: _,
337                transport: _,
338                etsi: _,
339            } => Self::Denm,
340            #[cfg(feature = "cam_1_4_1")]
341            ItsMessage::Cam {
342                geonetworking: _,
343                transport: _,
344                etsi: _,
345            } => Self::Cam,
346            #[cfg(feature = "spatem_2_2_1")]
347            ItsMessage::Spatem {
348                geonetworking: _,
349                transport: _,
350                etsi: _,
351            } => Self::Spatem,
352            #[cfg(feature = "mapem_2_2_1")]
353            ItsMessage::Mapem {
354                geonetworking: _,
355                transport: _,
356                etsi: _,
357            } => Self::Mapem,
358            #[cfg(feature = "ivim_2_1_1")]
359            ItsMessage::IvimV1 {
360                geonetworking: _,
361                transport: _,
362                etsi: _,
363            } => Self::Ivim,
364            #[cfg(feature = "ivim_2_2_1")]
365            ItsMessage::IvimV2 {
366                geonetworking: _,
367                transport: _,
368                etsi: _,
369            } => Self::Ivim,
370            #[cfg(feature = "srem_2_2_1")]
371            ItsMessage::Srem {
372                geonetworking: _,
373                transport: _,
374                etsi: _,
375            } => Self::Srem,
376            #[cfg(feature = "ssem_2_2_1")]
377            ItsMessage::Ssem {
378                geonetworking: _,
379                transport: _,
380                etsi: _,
381            } => Self::Ssem,
382            #[cfg(feature = "cpm_1")]
383            ItsMessage::CpmV1 {
384                geonetworking: _,
385                transport: _,
386                etsi: _,
387            } => Self::Cpm,
388            #[cfg(feature = "cpm_2_1_1")]
389            ItsMessage::CpmV2 {
390                geonetworking: _,
391                transport: _,
392                etsi: _,
393            } => Self::Cpm,
394        }
395    }
396}
397
398#[cfg(feature = "_etsi")]
399#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
400#[derive(Copy, Clone, Debug, PartialEq)]
401/// Choice which message headers are present in the binary message buffer
402pub enum Headers {
403    /// No headers before V2X message
404    None,
405    /// Binary message with GeoNetworking and BTP headers
406    GnBtp,
407    /// Binary message with Radiotap, IEEE 802.11p, LLC, GeoNetworking and BTP headers
408    RadioTap802LlcGnBtp,
409    /// Binary message with IEEE 802.11p, LLC, GeoNetworking and BTP headers
410    IEEE802LlcGnBtp,
411}
412
413#[cfg(feature = "_etsi")]
414#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
415#[derive(Copy, Clone, Debug, PartialEq)]
416/// Choice of ASN.1 encoding rule
417pub enum EncodingRules {
418    UPER,
419    XER,
420    JER,
421}
422
423#[cfg(any(
424    feature = "_etsi",
425    all(target_arch = "wasm32", feature = "_etsi", feature = "json"),
426    all(test, feature = "_etsi")
427))]
428impl EncodingRules {
429    pub(crate) fn codec(self) -> rasn::Codec {
430        match self {
431            EncodingRules::UPER => rasn::Codec::Uper,
432            EncodingRules::XER => rasn::Codec::Xer,
433            EncodingRules::JER => rasn::Codec::Jer,
434        }
435    }
436}
437
438#[cfg(any(feature = "transport", feature = "_etsi"))]
439pub(crate) fn map_err_to_string<E: core::fmt::Debug>(error: E) -> alloc::string::String {
440    alloc::format!("{error:?}")
441}