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(crate) mod pcap;
75#[cfg(feature = "transport")]
76pub mod transport;
77
78#[cfg(feature = "transport")]
79pub use geonetworking::{Decode, Packet};
80#[cfg(feature = "transport")]
81pub use pcap::remove_pcap_headers;
82#[cfg(feature = "_etsi")]
83use transport::TransportHeader;
84#[cfg(all(target_arch = "wasm32", any(feature = "json", feature = "_etsi")))]
85use wasm_bindgen::prelude::*;
86
87#[cfg(all(target_arch = "wasm32", feature = "json"))]
88#[wasm_bindgen(getter_with_clone)]
89#[derive(Debug, Clone, PartialEq, Default)]
90/// Wrapper for the stringified JSON of headers and ITS ETSI message
91pub struct JsonItsMessage {
92 /// Optional GeoNetworking header, encoded as stringified JSON
93 pub geonetworking: Option<String>,
94 /// Optional transport header, encoded as stringified JSON
95 pub transport: Option<String>,
96 /// Optional ITS ETSI message, encoded as a UTF-8 String for JER and XER, as a hex string for UPER
97 pub its: Option<String>,
98 /// Optional ITS ETSI message type, as specified in ETSI TS 102 894-2
99 /// - 1 - `denm` - for Decentralized Environmental Notification Message (DENM) as specified in ETSI EN 302 637-3 [2],
100 /// - 2 - `cam` - for Cooperative Awareness Message (CAM) as specified in ETSI EN 302 637-2 [1],
101 /// - 3 - `poi` - for Point of Interest message as specified in ETSI TS 101 556-1 [9],
102 /// - 4 - `spatem` - for Signal Phase And Timing Extended Message (SPATEM) as specified in ETSI TS 103 301 [15],
103 /// - 5 - `mapem` - for MAP Extended Message (MAPEM) as specified in ETSI TS 103 301 [15],
104 /// - 6 - `ivim` - for in Vehicle Information Message (IVIM) as specified in ETSI TS 103 301 [15],
105 /// - 7 - `ev-rsr` - for Electric vehicle recharging spot reservation message, as defined in ETSI TS 101 556-3 [11],
106 /// - 8 - `tistpgtransaction` - for messages for Tyre Information System (TIS) and Tyre Pressure Gauge (TPG) interoperability, as specified in ETSI TS 101 556-2 [10],
107 /// - 9 - `srem` - for Signal Request Extended Message as specified in ETSI TS 103 301 [15],
108 /// - 10 - `ssem` - for Signal request Status Extended Message as specified in ETSI TS 103 301 [15],
109 /// - 11 - `evcsn` - for Electrical Vehicle Charging Spot Notification message as specified in ETSI TS 101 556-1 [9],
110 /// - 12 - `saem` - for Services Announcement Extended Message as specified in ETSI EN 302 890-1 [17],
111 /// - 13 - `rtcmem` - for Radio Technical Commission for Maritime Services Extended Message (RTCMEM) as specified in ETSI TS 103 301 [15],
112 /// - 14 - `cpm` - reserved for Collective Perception Message (CPM),
113 /// - 15 - `imzm` - for Interference Management Zone Message (IMZM) as specified in ETSI TS 103 724 [13],
114 /// - 16 - `vam` - for Vulnerable Road User Awareness Message as specified in ETSI TS 130 300-3 [12],
115 /// - 17 - `dsm` - reserved for Diagnosis, logging and Status Message,
116 /// - 18 - `pcim` - reserved for Parking Control Infrastructure Message,
117 /// - 19 - `pcvm` - reserved for Parking Control Vehicle Message,
118 /// - 20 - `mcm` - reserved for Manoeuver Coordination Message,
119 /// - 21 - `pam` - reserved for Parking Availability Message,
120 /// - 22-255 - reserved for future usage.
121 pub message_type: u8,
122}
123
124#[cfg(all(target_arch = "wasm32", feature = "json"))]
125#[wasm_bindgen]
126impl JsonItsMessage {
127 #[wasm_bindgen(constructor)]
128 pub fn from(
129 its: Option<String>,
130 geonetworking: Option<String>,
131 transport: Option<String>,
132 message_type: u8,
133 ) -> Self {
134 Self {
135 its,
136 geonetworking,
137 transport,
138 message_type,
139 }
140 }
141}
142
143#[cfg(feature = "_etsi")]
144#[derive(Debug, Clone, PartialEq)]
145/// Wrapper for C-ITS messages
146///
147/// Each message consists of the `etsi` data and can optionally contain a `transport` (BTP) and a `geonetworking` header.
148pub enum ItsMessage<'a> {
149 #[cfg(feature = "denm_1_3_1")]
150 /// ETSI EN 302 637-3 v1.3.1 DENM
151 DenmV1 {
152 geonetworking: Option<Packet<'a>>,
153 transport: Option<alloc::boxed::Box<TransportHeader>>,
154 etsi: alloc::boxed::Box<standards::denm_1_3_1::denm_pdu_descriptions::DENM>,
155 },
156 #[cfg(feature = "denm_2_2_1")]
157 /// ETSI TS 103 831 v2.2.1 (or v2.1.1) DENM
158 DenmV2 {
159 geonetworking: Option<Packet<'a>>,
160 transport: Option<alloc::boxed::Box<TransportHeader>>,
161 etsi: alloc::boxed::Box<standards::denm_2_2_1::denm_pdu_description::DENM>,
162 },
163 #[cfg(feature = "cam_1_4_1")]
164 /// ETSI TS 103 301 v2.2.1 (or v2.1.1 or v1.3.1) CAM
165 Cam {
166 geonetworking: Option<Packet<'a>>,
167 transport: Option<alloc::boxed::Box<TransportHeader>>,
168 etsi: alloc::boxed::Box<standards::cam_1_4_1::cam_pdu_descriptions::CAM>,
169 },
170 #[cfg(feature = "spatem_2_2_1")]
171 /// ETSI TS 103 301 v2.2.1 (or v2.1.1 or v1.3.1) SPATEM
172 Spatem {
173 geonetworking: Option<Packet<'a>>,
174 transport: Option<alloc::boxed::Box<TransportHeader>>,
175 etsi: alloc::boxed::Box<standards::spatem_2_2_1::spatem_pdu_descriptions::SPATEM>,
176 },
177 #[cfg(feature = "mapem_2_2_1")]
178 /// ETSI TS 103 301 v2.2.1 (or v2.1.1 or v1.3.1) MAPEM
179 Mapem {
180 geonetworking: Option<Packet<'a>>,
181 transport: Option<alloc::boxed::Box<TransportHeader>>,
182 etsi: alloc::boxed::Box<standards::mapem_2_2_1::mapem_pdu_descriptions::MAPEM>,
183 },
184 #[cfg(feature = "ivim_2_1_1")]
185 /// ETSI TS 103 301 v2.1.1 (or v1.3.1) IVIM
186 IvimV1 {
187 geonetworking: Option<Packet<'a>>,
188 transport: Option<alloc::boxed::Box<TransportHeader>>,
189 etsi: alloc::boxed::Box<standards::ivim_2_1_1::ivim_pdu_descriptions::IVIM>,
190 },
191 #[cfg(feature = "ivim_2_2_1")]
192 /// ETSI TS 103 301 v2.2.1 IVIM
193 IvimV2 {
194 geonetworking: Option<Packet<'a>>,
195 transport: Option<alloc::boxed::Box<TransportHeader>>,
196 etsi: alloc::boxed::Box<standards::ivim_2_2_1::ivim_pdu_descriptions::IVIM>,
197 },
198 #[cfg(feature = "srem_2_2_1")]
199 /// ETSI TS 103 301 v2.2.1 (or v2.1.1 or v1.3.1) SREM
200 Srem {
201 geonetworking: Option<Packet<'a>>,
202 transport: Option<alloc::boxed::Box<TransportHeader>>,
203 etsi: alloc::boxed::Box<standards::srem_2_2_1::srem_pdu_descriptions::SREM>,
204 },
205 #[cfg(feature = "ssem_2_2_1")]
206 /// ETSI TS 103 301 v2.2.1 (or v2.1.1 or v1.3.1) SSEM
207 Ssem {
208 geonetworking: Option<Packet<'a>>,
209 transport: Option<alloc::boxed::Box<TransportHeader>>,
210 etsi: alloc::boxed::Box<standards::ssem_2_2_1::ssem_pdu_descriptions::SSEM>,
211 },
212 #[cfg(feature = "cpm_1")]
213 /// ETSI TR 103 562 v2.1.1 CPM
214 CpmV1 {
215 geonetworking: Option<Packet<'a>>,
216 transport: Option<alloc::boxed::Box<TransportHeader>>,
217 etsi: alloc::boxed::Box<standards::cpm_1::cpm_pdu_descriptions::CPM>,
218 },
219 #[cfg(feature = "cpm_2_1_1")]
220 /// ETSI TS 103 324 v2.1.1 CPM
221 CpmV2 {
222 geonetworking: Option<Packet<'a>>,
223 transport: Option<alloc::boxed::Box<TransportHeader>>,
224 etsi: alloc::boxed::Box<
225 standards::cpm_2_1_1::cpm_pdu_descriptions::CollectivePerceptionMessage,
226 >,
227 },
228}
229
230#[cfg(feature = "_etsi")]
231impl<'a> ItsMessage<'a> {
232 /// Returns the `geonetworking` field of the `ItsMessage` variant
233 #[must_use]
234 pub fn get_geonetworking(&self) -> Option<Packet<'a>> {
235 let (gn, _) = self.get_headers();
236 gn.clone()
237 }
238
239 /// Returns the `transport` field of the `ItsMessage` variant
240 #[must_use]
241 pub fn get_transport(&self) -> Option<alloc::boxed::Box<TransportHeader>> {
242 let (_, tp) = self.get_headers();
243 tp.clone()
244 }
245
246 /// Returns the `geonetworking` and `transport` fields of the `ItsMessage` variant
247 #[must_use]
248 fn get_headers(
249 &self,
250 ) -> (
251 &Option<Packet<'a>>,
252 &Option<alloc::boxed::Box<TransportHeader>>,
253 ) {
254 match self {
255 #[cfg(feature = "denm_1_3_1")]
256 ItsMessage::DenmV1 {
257 geonetworking,
258 transport,
259 etsi: _,
260 } => (geonetworking, transport),
261 #[cfg(feature = "denm_2_2_1")]
262 ItsMessage::DenmV2 {
263 geonetworking,
264 transport,
265 etsi: _,
266 } => (geonetworking, transport),
267 #[cfg(feature = "cam_1_4_1")]
268 ItsMessage::Cam {
269 geonetworking,
270 transport,
271 etsi: _,
272 } => (geonetworking, transport),
273 #[cfg(feature = "spatem_2_2_1")]
274 ItsMessage::Spatem {
275 geonetworking,
276 transport,
277 etsi: _,
278 } => (geonetworking, transport),
279 #[cfg(feature = "mapem_2_2_1")]
280 ItsMessage::Mapem {
281 geonetworking,
282 transport,
283 etsi: _,
284 } => (geonetworking, transport),
285 #[cfg(feature = "ivim_2_1_1")]
286 ItsMessage::IvimV1 {
287 geonetworking,
288 transport,
289 etsi: _,
290 } => (geonetworking, transport),
291 #[cfg(feature = "ivim_2_2_1")]
292 ItsMessage::IvimV2 {
293 geonetworking,
294 transport,
295 etsi: _,
296 } => (geonetworking, transport),
297 #[cfg(feature = "srem_2_2_1")]
298 ItsMessage::Srem {
299 geonetworking,
300 transport,
301 etsi: _,
302 } => (geonetworking, transport),
303 #[cfg(feature = "ssem_2_2_1")]
304 ItsMessage::Ssem {
305 geonetworking,
306 transport,
307 etsi: _,
308 } => (geonetworking, transport),
309 #[cfg(feature = "cpm_1")]
310 ItsMessage::CpmV1 {
311 geonetworking,
312 transport,
313 etsi: _,
314 } => (geonetworking, transport),
315 #[cfg(feature = "cpm_2_1_1")]
316 ItsMessage::CpmV2 {
317 geonetworking,
318 transport,
319 etsi: _,
320 } => (geonetworking, transport),
321 }
322 }
323}
324
325#[cfg(feature = "_etsi")]
326impl From<&ItsMessage<'_>> for standards::extensions::ItsMessageId {
327 fn from(val: &ItsMessage<'_>) -> Self {
328 match val {
329 #[cfg(feature = "denm_1_3_1")]
330 ItsMessage::DenmV1 {
331 geonetworking: _,
332 transport: _,
333 etsi: _,
334 } => Self::Denm,
335 #[cfg(feature = "denm_2_2_1")]
336 ItsMessage::DenmV2 {
337 geonetworking: _,
338 transport: _,
339 etsi: _,
340 } => Self::Denm,
341 #[cfg(feature = "cam_1_4_1")]
342 ItsMessage::Cam {
343 geonetworking: _,
344 transport: _,
345 etsi: _,
346 } => Self::Cam,
347 #[cfg(feature = "spatem_2_2_1")]
348 ItsMessage::Spatem {
349 geonetworking: _,
350 transport: _,
351 etsi: _,
352 } => Self::Spatem,
353 #[cfg(feature = "mapem_2_2_1")]
354 ItsMessage::Mapem {
355 geonetworking: _,
356 transport: _,
357 etsi: _,
358 } => Self::Mapem,
359 #[cfg(feature = "ivim_2_1_1")]
360 ItsMessage::IvimV1 {
361 geonetworking: _,
362 transport: _,
363 etsi: _,
364 } => Self::Ivim,
365 #[cfg(feature = "ivim_2_2_1")]
366 ItsMessage::IvimV2 {
367 geonetworking: _,
368 transport: _,
369 etsi: _,
370 } => Self::Ivim,
371 #[cfg(feature = "srem_2_2_1")]
372 ItsMessage::Srem {
373 geonetworking: _,
374 transport: _,
375 etsi: _,
376 } => Self::Srem,
377 #[cfg(feature = "ssem_2_2_1")]
378 ItsMessage::Ssem {
379 geonetworking: _,
380 transport: _,
381 etsi: _,
382 } => Self::Ssem,
383 #[cfg(feature = "cpm_1")]
384 ItsMessage::CpmV1 {
385 geonetworking: _,
386 transport: _,
387 etsi: _,
388 } => Self::Cpm,
389 #[cfg(feature = "cpm_2_1_1")]
390 ItsMessage::CpmV2 {
391 geonetworking: _,
392 transport: _,
393 etsi: _,
394 } => Self::Cpm,
395 }
396 }
397}
398
399#[cfg(feature = "_etsi")]
400#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
401#[derive(Copy, Clone, Debug, PartialEq)]
402/// Choice which message headers are present in the binary message buffer
403pub enum Headers {
404 /// No headers before V2X message
405 None,
406 /// Binary message with GeoNetworking and BTP headers
407 GnBtp,
408 /// Binary message with Radiotap, IEEE 802.11p, LLC, GeoNetworking and BTP headers
409 RadioTap802LlcGnBtp,
410 /// Binary message with IEEE 802.11p, LLC, GeoNetworking and BTP headers
411 IEEE802LlcGnBtp,
412}
413
414#[cfg(feature = "_etsi")]
415#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
416#[derive(Copy, Clone, Debug, PartialEq)]
417/// Choice of ASN.1 encoding rule
418pub enum EncodingRules {
419 UPER,
420 XER,
421 JER,
422}
423
424#[cfg(any(
425 feature = "_etsi",
426 all(target_arch = "wasm32", feature = "_etsi", feature = "json"),
427 all(test, feature = "_etsi")
428))]
429impl EncodingRules {
430 pub(crate) fn codec(self) -> rasn::Codec {
431 match self {
432 EncodingRules::UPER => rasn::Codec::Uper,
433 EncodingRules::XER => rasn::Codec::Xer,
434 EncodingRules::JER => rasn::Codec::Jer,
435 }
436 }
437}
438
439#[cfg(any(feature = "transport", feature = "_etsi"))]
440pub(crate) fn map_err_to_string<E: core::fmt::Debug>(error: E) -> alloc::string::String {
441 alloc::format!("{error:?}")
442}