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
55extern crate alloc;
56
57#[cfg(any(feature = "std", target_arch = "wasm32", test))]
58extern crate std;
59
60pub mod de;
61#[cfg(feature = "_etsi")]
62pub mod en;
63#[cfg(feature = "_etsi")]
64#[allow(clippy::all, clippy::pedantic, clippy::nursery, dead_code)]
65pub mod standards;
66
67#[cfg(feature = "geo")]
68pub mod geo_utils;
69#[cfg(feature = "time")]
70pub mod time_utils;
71
72#[cfg(feature = "transport")]
73pub(crate) mod pcap;
74#[cfg(feature = "transport")]
75pub mod transport;
76
77#[cfg(feature = "transport")]
78pub use geonetworking::{Decode, Packet};
79#[cfg(feature = "transport")]
80pub use pcap::remove_pcap_headers;
81#[cfg(feature = "_etsi")]
82use transport::TransportHeader;
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<Packet<'a>>,
152 transport: Option<alloc::boxed::Box<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<Packet<'a>>,
159 transport: Option<alloc::boxed::Box<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<Packet<'a>>,
166 transport: Option<alloc::boxed::Box<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<Packet<'a>>,
173 transport: Option<alloc::boxed::Box<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<Packet<'a>>,
180 transport: Option<alloc::boxed::Box<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<Packet<'a>>,
187 transport: Option<alloc::boxed::Box<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<Packet<'a>>,
194 transport: Option<alloc::boxed::Box<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<Packet<'a>>,
201 transport: Option<alloc::boxed::Box<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<Packet<'a>>,
208 transport: Option<alloc::boxed::Box<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<Packet<'a>>,
215 transport: Option<alloc::boxed::Box<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<Packet<'a>>,
222 transport: Option<alloc::boxed::Box<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 From<&ItsMessage<'_>> for standards::extensions::ItsMessageId {
231 fn from(val: &ItsMessage<'_>) -> Self {
232 match val {
233 #[cfg(feature = "denm_1_3_1")]
234 ItsMessage::DenmV1 {
235 geonetworking: _,
236 transport: _,
237 etsi: _,
238 } => Self::Denm,
239 #[cfg(feature = "denm_2_2_1")]
240 ItsMessage::DenmV2 {
241 geonetworking: _,
242 transport: _,
243 etsi: _,
244 } => Self::Denm,
245 #[cfg(feature = "cam_1_4_1")]
246 ItsMessage::Cam {
247 geonetworking: _,
248 transport: _,
249 etsi: _,
250 } => Self::Cam,
251 #[cfg(feature = "spatem_2_2_1")]
252 ItsMessage::Spatem {
253 geonetworking: _,
254 transport: _,
255 etsi: _,
256 } => Self::Spatem,
257 #[cfg(feature = "mapem_2_2_1")]
258 ItsMessage::Mapem {
259 geonetworking: _,
260 transport: _,
261 etsi: _,
262 } => Self::Mapem,
263 #[cfg(feature = "ivim_2_1_1")]
264 ItsMessage::IvimV1 {
265 geonetworking: _,
266 transport: _,
267 etsi: _,
268 } => Self::Ivim,
269 #[cfg(feature = "ivim_2_2_1")]
270 ItsMessage::IvimV2 {
271 geonetworking: _,
272 transport: _,
273 etsi: _,
274 } => Self::Ivim,
275 #[cfg(feature = "srem_2_2_1")]
276 ItsMessage::Srem {
277 geonetworking: _,
278 transport: _,
279 etsi: _,
280 } => Self::Srem,
281 #[cfg(feature = "ssem_2_2_1")]
282 ItsMessage::Ssem {
283 geonetworking: _,
284 transport: _,
285 etsi: _,
286 } => Self::Ssem,
287 #[cfg(feature = "cpm_1")]
288 ItsMessage::CpmV1 {
289 geonetworking: _,
290 transport: _,
291 etsi: _,
292 } => Self::Cpm,
293 #[cfg(feature = "cpm_2_1_1")]
294 ItsMessage::CpmV2 {
295 geonetworking: _,
296 transport: _,
297 etsi: _,
298 } => Self::Cpm,
299 }
300 }
301}
302
303#[cfg(feature = "_etsi")]
304#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
305#[derive(Copy, Clone, Debug, PartialEq)]
306/// Choice which message headers are present in the binary message buffer
307pub enum Headers {
308 /// No headers before V2X message
309 None,
310 /// Binary message with GeoNetworking and BTP headers
311 GnBtp,
312 /// Binary message with Radiotap, IEEE 802.11p, LLC, GeoNetworking and BTP headers
313 RadioTap802LlcGnBtp,
314 /// Binary message with IEEE 802.11p, LLC, GeoNetworking and BTP headers
315 IEEE802LlcGnBtp,
316}
317
318#[cfg(feature = "_etsi")]
319#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
320#[derive(Copy, Clone, Debug, PartialEq)]
321/// Choice of ASN.1 encoding rule
322pub enum EncodingRules {
323 UPER,
324 XER,
325 JER,
326}
327
328#[cfg(any(
329 feature = "_etsi",
330 all(target_arch = "wasm32", feature = "_etsi", feature = "json"),
331 all(test, feature = "_etsi")
332))]
333impl EncodingRules {
334 pub(crate) fn codec(self) -> rasn::Codec {
335 match self {
336 EncodingRules::UPER => rasn::Codec::Uper,
337 EncodingRules::XER => rasn::Codec::Xer,
338 EncodingRules::JER => rasn::Codec::Jer,
339 }
340 }
341}
342
343#[cfg(any(feature = "transport", feature = "_etsi"))]
344pub(crate) fn map_err_to_string<E: core::fmt::Debug>(error: E) -> alloc::string::String {
345 alloc::format!("{error:?}")
346}