c-its-parser 2.2.1

Tools for encoding and decoding ETSI messages (GN + Transport + CAM/DENM/IVIM/SSEM/SREM/MAPEM/SPATEM)
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
//! Parser and encoder for ETSI C-ITS (V2X) messages including GeoNetworking headers and optionally Radiotap and IEEE 802.11 headers.
//! It supports UPER, XER and JER ASN.1 encodings for parsing and encoding.
//!
//! Supported messages/ standards: See documentation of the [`ItsMessage`] variants.
//!
//! ## Decoding
//!
//! A packet can be decoded using the [`de::decode`] function:
//!
//! ```
//! let data = &[0x02, 0x02, 0xde, 0x14, 0x0c, 0xe5]; // provide actual message buffer here
//! #[cfg(feature = "_etsi")]
//! match c_its_parser::de::decode(data, c_its_parser::Headers::RadioTap802LlcGnBtp) {
//!     #[cfg(feature = "cam_1_4_1")]
//!     Ok(c_its_parser::ItsMessage::Cam {
//!         geonetworking: _,
//!         transport: _,
//!         etsi: cam,
//!     }) => {
//!         println!("Got a CAM: {cam:?}")
//!     }
//!     Ok(msg) => println!("Got: {msg:?}"),
//!     Err(err) => println!("Failed to parse message: {err}"),
//! }
//! ```
//!
//! The `headers` argument needs to specify which headers are present: None, GeoNetworking + BTP or Radiotap + 802.11p + LLC + GeoNetworking + BTP.
//! Headers are expected to be present in binary form.
//! When no headers are present, it can auto-detect the ASN.1 encoding (UPER/ XER/ JER) and decodes the message.
//! This means, that XER and JER message buffers can only be decoded without headers.
//!
//! ## Encoding
//!
//! To encode an [`ItsMessage`] struct, call the [`encode()`](`ItsMessage::encode`) method supplying the intended encoding rules.
//! Again, XER and JER messages can only be encoded without headers.
//! GeoNetworking and transport (BTP) headers will be added when present if UPER encoding is used.
//!
//! ## Feature Flags
//!
//! This library has several feature flags to allow fine-grained control over the feature set and additional dependencies.
//!
//! By default, all V2X messages and conversion to and from JSON are enabled.
//! 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.
//!
//! When no parsing of the geonetworking and pcap headers is needed, the `transport` feature can be disabled.
//!
//! Besides parsing, the Rust API also provides helper functions to convert between ETSI data types and "normal"/ SI units.
//! Additional conversions are only available by adding some feature flags:
//!
//! - `time`: Enable conversions to [chrono](https://crates.io/crates/chrono) timestamps
//! - `geo`: Enable conversions to [geo-types](https://crates.io/crates/geo-types) (as lon/lat coordinates in degrees)

#![cfg_attr(not(target_arch = "wasm32"), no_std)]
#![cfg_attr(docsrs, feature(doc_cfg))]

extern crate alloc;

#[cfg(any(feature = "std", target_arch = "wasm32", test))]
extern crate std;

pub mod de;
#[cfg(feature = "_etsi")]
pub mod en;
#[cfg(feature = "_etsi")]
#[allow(clippy::all, clippy::pedantic, clippy::nursery, dead_code)]
pub mod standards;

#[cfg(feature = "geo")]
pub mod geo_utils;
#[cfg(feature = "time")]
pub mod time_utils;

#[cfg(feature = "transport")]
pub(crate) mod pcap;
#[cfg(feature = "transport")]
pub mod transport;

#[cfg(feature = "transport")]
pub use geonetworking::{Decode, Packet};
#[cfg(feature = "transport")]
pub use pcap::remove_pcap_headers;
#[cfg(feature = "_etsi")]
use transport::TransportHeader;
#[cfg(all(target_arch = "wasm32", any(feature = "json", feature = "_etsi")))]
use wasm_bindgen::prelude::*;

#[cfg(all(target_arch = "wasm32", feature = "json"))]
#[wasm_bindgen(getter_with_clone)]
#[derive(Debug, Clone, PartialEq, Default)]
/// Wrapper for the stringified JSON of headers and ITS ETSI message
pub struct JsonItsMessage {
    /// Optional GeoNetworking header, encoded as stringified JSON
    pub geonetworking: Option<String>,
    /// Optional transport header, encoded as stringified JSON
    pub transport: Option<String>,
    /// Optional ITS ETSI message, encoded as a UTF-8 String for JER and XER, as a hex string for UPER
    pub its: Option<String>,
    /// Optional ITS ETSI message type, as specified in ETSI TS 102 894-2
    /// - 1  - `denm`              - for Decentralized Environmental Notification Message (DENM) as specified in ETSI EN 302 637-3 [2],
    /// - 2  - `cam`               - for Cooperative Awareness Message (CAM) as specified in ETSI EN 302 637-2 [1],
    /// - 3  - `poi`               - for Point of Interest message as specified in ETSI TS 101 556-1 [9],
    /// - 4  - `spatem`            - for Signal Phase And Timing Extended Message (SPATEM) as specified in ETSI TS 103 301 [15],
    /// - 5  - `mapem`             - for MAP Extended Message (MAPEM) as specified in ETSI TS 103 301 [15],
    /// - 6  - `ivim`              - for in Vehicle Information Message (IVIM) as specified in ETSI TS 103 301 [15],
    /// - 7  - `ev-rsr`            - for Electric vehicle recharging spot reservation message, as defined in ETSI TS 101 556-3 [11],
    /// - 8  - `tistpgtransaction` - for messages for Tyre Information System (TIS) and Tyre Pressure Gauge (TPG) interoperability, as specified in ETSI TS 101 556-2 [10],
    /// - 9  - `srem`              - for Signal Request Extended Message as specified in ETSI TS 103 301 [15],
    /// - 10 - `ssem`              - for Signal request Status Extended Message as specified in ETSI TS 103 301 [15],
    /// - 11 - `evcsn`             - for Electrical Vehicle Charging Spot Notification message as specified in ETSI TS 101 556-1 [9],
    /// - 12 - `saem`              - for Services Announcement Extended Message as specified in ETSI EN 302 890-1 [17],
    /// - 13 - `rtcmem`            - for Radio Technical Commission for Maritime Services Extended Message (RTCMEM) as specified in ETSI TS 103 301 [15],
    /// - 14 - `cpm`               - reserved for Collective Perception Message (CPM),
    /// - 15 - `imzm`              - for Interference Management Zone Message (IMZM) as specified in ETSI TS 103 724 [13],
    /// - 16 - `vam`               - for Vulnerable Road User Awareness Message as specified in ETSI TS 130 300-3 [12],
    /// - 17 - `dsm`               - reserved for Diagnosis, logging and Status Message,
    /// - 18 - `pcim`              - reserved for Parking Control Infrastructure Message,
    /// - 19 - `pcvm`              - reserved for Parking Control Vehicle Message,
    /// - 20 - `mcm`               - reserved for Manoeuver Coordination Message,
    /// - 21 - `pam`               - reserved for Parking Availability Message,
    /// - 22-255                   - reserved for future usage.
    pub message_type: u8,
}

#[cfg(all(target_arch = "wasm32", feature = "json"))]
#[wasm_bindgen]
impl JsonItsMessage {
    #[wasm_bindgen(constructor)]
    pub fn from(
        its: Option<String>,
        geonetworking: Option<String>,
        transport: Option<String>,
        message_type: u8,
    ) -> Self {
        Self {
            its,
            geonetworking,
            transport,
            message_type,
        }
    }
}

#[cfg(feature = "_etsi")]
#[derive(Debug, Clone, PartialEq)]
/// Wrapper for C-ITS messages
///
/// Each message consists of the `etsi` data and can optionally contain a `transport` (BTP) and a `geonetworking` header.
pub enum ItsMessage<'a> {
    #[cfg(feature = "denm_1_3_1")]
    /// ETSI EN 302 637-3 v1.3.1 DENM
    DenmV1 {
        geonetworking: Option<Packet<'a>>,
        transport: Option<alloc::boxed::Box<TransportHeader>>,
        etsi: alloc::boxed::Box<standards::denm_1_3_1::denm_pdu_descriptions::DENM>,
    },
    #[cfg(feature = "denm_2_2_1")]
    /// ETSI TS 103 831 v2.2.1 (or v2.1.1) DENM
    DenmV2 {
        geonetworking: Option<Packet<'a>>,
        transport: Option<alloc::boxed::Box<TransportHeader>>,
        etsi: alloc::boxed::Box<standards::denm_2_2_1::denm_pdu_description::DENM>,
    },
    #[cfg(feature = "cam_1_4_1")]
    /// ETSI TS 103 301 v2.2.1 (or v2.1.1 or v1.3.1) CAM
    Cam {
        geonetworking: Option<Packet<'a>>,
        transport: Option<alloc::boxed::Box<TransportHeader>>,
        etsi: alloc::boxed::Box<standards::cam_1_4_1::cam_pdu_descriptions::CAM>,
    },
    #[cfg(feature = "spatem_2_2_1")]
    /// ETSI TS 103 301 v2.2.1 (or v2.1.1 or v1.3.1) SPATEM
    Spatem {
        geonetworking: Option<Packet<'a>>,
        transport: Option<alloc::boxed::Box<TransportHeader>>,
        etsi: alloc::boxed::Box<standards::spatem_2_2_1::spatem_pdu_descriptions::SPATEM>,
    },
    #[cfg(feature = "mapem_2_2_1")]
    /// ETSI TS 103 301 v2.2.1 (or v2.1.1 or v1.3.1) MAPEM
    Mapem {
        geonetworking: Option<Packet<'a>>,
        transport: Option<alloc::boxed::Box<TransportHeader>>,
        etsi: alloc::boxed::Box<standards::mapem_2_2_1::mapem_pdu_descriptions::MAPEM>,
    },
    #[cfg(feature = "ivim_2_1_1")]
    /// ETSI TS 103 301 v2.1.1 (or v1.3.1) IVIM
    IvimV1 {
        geonetworking: Option<Packet<'a>>,
        transport: Option<alloc::boxed::Box<TransportHeader>>,
        etsi: alloc::boxed::Box<standards::ivim_2_1_1::ivim_pdu_descriptions::IVIM>,
    },
    #[cfg(feature = "ivim_2_2_1")]
    /// ETSI TS 103 301 v2.2.1 IVIM
    IvimV2 {
        geonetworking: Option<Packet<'a>>,
        transport: Option<alloc::boxed::Box<TransportHeader>>,
        etsi: alloc::boxed::Box<standards::ivim_2_2_1::ivim_pdu_descriptions::IVIM>,
    },
    #[cfg(feature = "srem_2_2_1")]
    /// ETSI TS 103 301 v2.2.1 (or v2.1.1 or v1.3.1) SREM
    Srem {
        geonetworking: Option<Packet<'a>>,
        transport: Option<alloc::boxed::Box<TransportHeader>>,
        etsi: alloc::boxed::Box<standards::srem_2_2_1::srem_pdu_descriptions::SREM>,
    },
    #[cfg(feature = "ssem_2_2_1")]
    /// ETSI TS 103 301 v2.2.1 (or v2.1.1 or v1.3.1) SSEM
    Ssem {
        geonetworking: Option<Packet<'a>>,
        transport: Option<alloc::boxed::Box<TransportHeader>>,
        etsi: alloc::boxed::Box<standards::ssem_2_2_1::ssem_pdu_descriptions::SSEM>,
    },
    #[cfg(feature = "cpm_1")]
    /// ETSI TR 103 562 v2.1.1 CPM
    CpmV1 {
        geonetworking: Option<Packet<'a>>,
        transport: Option<alloc::boxed::Box<TransportHeader>>,
        etsi: alloc::boxed::Box<standards::cpm_1::cpm_pdu_descriptions::CPM>,
    },
    #[cfg(feature = "cpm_2_1_1")]
    /// ETSI TS 103 324 v2.1.1 CPM
    CpmV2 {
        geonetworking: Option<Packet<'a>>,
        transport: Option<alloc::boxed::Box<TransportHeader>>,
        etsi: alloc::boxed::Box<
            standards::cpm_2_1_1::cpm_pdu_descriptions::CollectivePerceptionMessage,
        >,
    },
}

#[cfg(feature = "_etsi")]
impl<'a> ItsMessage<'a> {
    /// Returns the `geonetworking` field of the `ItsMessage` variant
    #[must_use]
    pub fn get_geonetworking(&self) -> Option<Packet<'a>> {
        let (gn, _) = self.get_headers();
        gn.clone()
    }

    /// Returns the `transport` field of the `ItsMessage` variant
    #[must_use]
    pub fn get_transport(&self) -> Option<alloc::boxed::Box<TransportHeader>> {
        let (_, tp) = self.get_headers();
        tp.clone()
    }

    /// Returns the `geonetworking` and `transport` fields of the `ItsMessage` variant
    #[must_use]
    fn get_headers(
        &self,
    ) -> (
        &Option<Packet<'a>>,
        &Option<alloc::boxed::Box<TransportHeader>>,
    ) {
        match self {
            #[cfg(feature = "denm_1_3_1")]
            ItsMessage::DenmV1 {
                geonetworking,
                transport,
                etsi: _,
            } => (geonetworking, transport),
            #[cfg(feature = "denm_2_2_1")]
            ItsMessage::DenmV2 {
                geonetworking,
                transport,
                etsi: _,
            } => (geonetworking, transport),
            #[cfg(feature = "cam_1_4_1")]
            ItsMessage::Cam {
                geonetworking,
                transport,
                etsi: _,
            } => (geonetworking, transport),
            #[cfg(feature = "spatem_2_2_1")]
            ItsMessage::Spatem {
                geonetworking,
                transport,
                etsi: _,
            } => (geonetworking, transport),
            #[cfg(feature = "mapem_2_2_1")]
            ItsMessage::Mapem {
                geonetworking,
                transport,
                etsi: _,
            } => (geonetworking, transport),
            #[cfg(feature = "ivim_2_1_1")]
            ItsMessage::IvimV1 {
                geonetworking,
                transport,
                etsi: _,
            } => (geonetworking, transport),
            #[cfg(feature = "ivim_2_2_1")]
            ItsMessage::IvimV2 {
                geonetworking,
                transport,
                etsi: _,
            } => (geonetworking, transport),
            #[cfg(feature = "srem_2_2_1")]
            ItsMessage::Srem {
                geonetworking,
                transport,
                etsi: _,
            } => (geonetworking, transport),
            #[cfg(feature = "ssem_2_2_1")]
            ItsMessage::Ssem {
                geonetworking,
                transport,
                etsi: _,
            } => (geonetworking, transport),
            #[cfg(feature = "cpm_1")]
            ItsMessage::CpmV1 {
                geonetworking,
                transport,
                etsi: _,
            } => (geonetworking, transport),
            #[cfg(feature = "cpm_2_1_1")]
            ItsMessage::CpmV2 {
                geonetworking,
                transport,
                etsi: _,
            } => (geonetworking, transport),
        }
    }
}

#[cfg(feature = "_etsi")]
impl From<&ItsMessage<'_>> for standards::extensions::ItsMessageId {
    fn from(val: &ItsMessage<'_>) -> Self {
        match val {
            #[cfg(feature = "denm_1_3_1")]
            ItsMessage::DenmV1 {
                geonetworking: _,
                transport: _,
                etsi: _,
            } => Self::Denm,
            #[cfg(feature = "denm_2_2_1")]
            ItsMessage::DenmV2 {
                geonetworking: _,
                transport: _,
                etsi: _,
            } => Self::Denm,
            #[cfg(feature = "cam_1_4_1")]
            ItsMessage::Cam {
                geonetworking: _,
                transport: _,
                etsi: _,
            } => Self::Cam,
            #[cfg(feature = "spatem_2_2_1")]
            ItsMessage::Spatem {
                geonetworking: _,
                transport: _,
                etsi: _,
            } => Self::Spatem,
            #[cfg(feature = "mapem_2_2_1")]
            ItsMessage::Mapem {
                geonetworking: _,
                transport: _,
                etsi: _,
            } => Self::Mapem,
            #[cfg(feature = "ivim_2_1_1")]
            ItsMessage::IvimV1 {
                geonetworking: _,
                transport: _,
                etsi: _,
            } => Self::Ivim,
            #[cfg(feature = "ivim_2_2_1")]
            ItsMessage::IvimV2 {
                geonetworking: _,
                transport: _,
                etsi: _,
            } => Self::Ivim,
            #[cfg(feature = "srem_2_2_1")]
            ItsMessage::Srem {
                geonetworking: _,
                transport: _,
                etsi: _,
            } => Self::Srem,
            #[cfg(feature = "ssem_2_2_1")]
            ItsMessage::Ssem {
                geonetworking: _,
                transport: _,
                etsi: _,
            } => Self::Ssem,
            #[cfg(feature = "cpm_1")]
            ItsMessage::CpmV1 {
                geonetworking: _,
                transport: _,
                etsi: _,
            } => Self::Cpm,
            #[cfg(feature = "cpm_2_1_1")]
            ItsMessage::CpmV2 {
                geonetworking: _,
                transport: _,
                etsi: _,
            } => Self::Cpm,
        }
    }
}

#[cfg(feature = "_etsi")]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
#[derive(Copy, Clone, Debug, PartialEq)]
/// Choice which message headers are present in the binary message buffer
pub enum Headers {
    /// No headers before V2X message
    None,
    /// Binary message with GeoNetworking and BTP headers
    GnBtp,
    /// Binary message with Radiotap, IEEE 802.11p, LLC, GeoNetworking and BTP headers
    RadioTap802LlcGnBtp,
    /// Binary message with IEEE 802.11p, LLC, GeoNetworking and BTP headers
    IEEE802LlcGnBtp,
}

#[cfg(feature = "_etsi")]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
#[derive(Copy, Clone, Debug, PartialEq)]
/// Choice of ASN.1 encoding rule
pub enum EncodingRules {
    UPER,
    XER,
    JER,
}

#[cfg(any(
    feature = "_etsi",
    all(target_arch = "wasm32", feature = "_etsi", feature = "json"),
    all(test, feature = "_etsi")
))]
impl EncodingRules {
    pub(crate) fn codec(self) -> rasn::Codec {
        match self {
            EncodingRules::UPER => rasn::Codec::Uper,
            EncodingRules::XER => rasn::Codec::Xer,
            EncodingRules::JER => rasn::Codec::Jer,
        }
    }
}

#[cfg(any(feature = "transport", feature = "_etsi"))]
pub(crate) fn map_err_to_string<E: core::fmt::Debug>(error: E) -> alloc::string::String {
    alloc::format!("{error:?}")
}