initial commit
This commit is contained in:
commit
72762ca558
11 changed files with 796 additions and 0 deletions
19
src/lib.rs
Normal file
19
src/lib.rs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
//! Common ESP32 C-ITS Utilities
|
||||
|
||||
#![cfg_attr(not(test), no_std)]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
/// Position and Velocity Vector
|
||||
#[allow(unused)]
|
||||
#[derive(Debug, Default, Clone, PartialEq)]
|
||||
pub struct PosVel {
|
||||
pub position: geo_types::Point,
|
||||
pub heading_deg: Option<f32>,
|
||||
pub speed_mps: Option<f32>,
|
||||
}
|
||||
|
||||
pub mod radio;
|
||||
pub mod rx;
|
||||
pub mod tx;
|
||||
294
src/radio.rs
Normal file
294
src/radio.rs
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
//! IEEE 802.11p C-ITS Capture with ESP32-C5
|
||||
#![allow(unused)]
|
||||
|
||||
// -----------------------------
|
||||
// missing interface definitions
|
||||
// -----------------------------
|
||||
|
||||
pub type Channel = core::ffi::c_int;
|
||||
|
||||
unsafe extern "C" {
|
||||
/// Changes the 802.11 PHY channel
|
||||
///
|
||||
/// C function signature: `void phy_change_channel(int,int,int,int)`
|
||||
///
|
||||
/// Options:
|
||||
/// - `primary`: Channel frequency in MHz
|
||||
/// - `ignored1`: Ignored (set to 1)
|
||||
/// - `ignored2`: Ignored (set to 0)
|
||||
/// - `ht_mode`: Probably HT mode
|
||||
unsafe fn phy_change_channel(
|
||||
primary: Channel,
|
||||
ignored1: core::ffi::c_int,
|
||||
ignored2: core::ffi::c_int,
|
||||
ht_mode: core::ffi::c_int,
|
||||
) -> core::ffi::c_int;
|
||||
|
||||
/// Enables 802.11p mode on the ESP32-C5
|
||||
///
|
||||
/// C function signature: `void phy_11p_set(int,int)`
|
||||
///
|
||||
/// Options:
|
||||
/// - `enable`: Boolean option to enable 802.11p mode
|
||||
/// - `half_rate`: 5 MHz mode
|
||||
unsafe fn phy_11p_set(enable: u8, half_rate: u8);
|
||||
|
||||
}
|
||||
|
||||
/// Enum values for `wifi_promiscuous_pkt_type_t` used in `esp_radio::wifi::sniffer::PromiscuousPkt::frame_type`
|
||||
///
|
||||
/// Original value is actually `core::ffi::c_uint`, but that can't be used in `#[repr()]` attributes,
|
||||
/// so we're using `u8` as the smallest suitable type here that can be casted to a bigger type.
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[allow(dead_code)]
|
||||
pub enum PromiscuousPktType {
|
||||
/// Management frame, indicates 'buf' argument is `wifi_promiscuous_pkt_t`
|
||||
Mgmt = 0,
|
||||
/// Control frame, indicates 'buf' argument is `wifi_promiscuous_pkt_t`
|
||||
Ctrl = 1,
|
||||
/// Data frame, indicates 'buf' argument is `wifi_promiscuous_pkt_t`
|
||||
Data = 2,
|
||||
/// Other type, such as MIMO etc. 'buf' argument is `wifi_promiscuous_pkt_t` but the payload is zero length.
|
||||
Misc = 3,
|
||||
}
|
||||
|
||||
impl PromiscuousPktType {
|
||||
#[allow(dead_code)]
|
||||
#[must_use]
|
||||
pub fn as_repr(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
|
||||
/// Converts to underlying type of `wifi_promiscuous_pkt_type_t` (used by `esp_radio::wifi::sniffer::PromiscuousPkt::frame_type`)
|
||||
#[must_use]
|
||||
pub fn as_frame_type(self) -> core::ffi::c_uint {
|
||||
core::ffi::c_uint::from(self.as_repr())
|
||||
}
|
||||
|
||||
/// Determines if we are the same frame type as `esp_radio::wifi::sniffer::PromiscuousPkt::frame_type`
|
||||
#[must_use]
|
||||
pub fn matches(self, frame_type: core::ffi::c_uint) -> bool {
|
||||
self.as_frame_type() == frame_type
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "esp32c5")]
|
||||
/// Configure WiFi Sniffer to a certain 802.11p channel
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns a human-readable error description if setup failed
|
||||
pub fn setup_wifi_sniffer(
|
||||
channel: Channel,
|
||||
sniffer: &mut esp_radio::wifi::sniffer::Sniffer,
|
||||
callback: fn(esp_radio::wifi::sniffer::PromiscuousPkt<'_>),
|
||||
) -> Result<(), alloc::string::String> {
|
||||
sniffer
|
||||
.set_promiscuous_mode(true)
|
||||
.map_err(|err| alloc::format!("Failed to enable promiscuous mode: {err:?}"))?;
|
||||
|
||||
sniffer.set_receive_cb(callback);
|
||||
|
||||
// convert channel number to frequency in MHz
|
||||
let primary = 5000 + (channel * 5);
|
||||
|
||||
unsafe {
|
||||
phy_11p_set(1, 0);
|
||||
match phy_change_channel(primary, 1, 0, 0) {
|
||||
0 => Ok(()),
|
||||
ret => Err(alloc::format!("Failed to change channel: {ret}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "esp32c5")]
|
||||
/// Sends 802.11p broadcast frame
|
||||
///
|
||||
/// Expects `data` to contain GN header (BTP header and ITS payload) to be a valid C-ITS/ ITS-G5 packet/ frame.
|
||||
///
|
||||
/// # Errors
|
||||
/// Fails when MAC is not the right size or returns frame send error
|
||||
pub fn send_80211_bcast_frame(
|
||||
sniffer: &mut esp_radio::wifi::sniffer::Sniffer,
|
||||
mac_addr: &[u8],
|
||||
data: &[u8],
|
||||
) -> Result<(), alloc::string::String> {
|
||||
const HEADER_SIZE: usize = 24 + 8;
|
||||
|
||||
let mac = mac_addr
|
||||
.try_into()
|
||||
.map_err(|err| alloc::format!("Unexpected MAC address size: {err}"))?;
|
||||
|
||||
// assemble frame
|
||||
let mut frame_buf = alloc::vec::Vec::with_capacity(HEADER_SIZE + data.len());
|
||||
frame_buf.extend_from_slice(&DataFrameHeader::new_80211p(mac).to_binary());
|
||||
frame_buf.extend_from_slice(&LlcHeader::new_80211p().to_binary());
|
||||
frame_buf.extend_from_slice(data);
|
||||
|
||||
// send
|
||||
sniffer
|
||||
.send_raw_frame(true, &frame_buf, true)
|
||||
.map_err(|err| alloc::format!("Failed to send 802.11p frame: {err:?}"))
|
||||
}
|
||||
|
||||
/// IEEE 802.11p frame header
|
||||
#[derive(Debug, Default)]
|
||||
struct DataFrameHeader {
|
||||
/// byte 0: subtype in upper nibble, type in lower nibble
|
||||
/// "normal" data frame: 0x08, QoS data frame: 0x88
|
||||
pub type_and_subtype: u8,
|
||||
/// byte 1: frame contol field, 0x00 for 802.11p
|
||||
pub fcf_flags: u8,
|
||||
/// byte 2..3: duration in micros, usually 0x0000 for 802.11p (toDS = 0, fromDS = 0)
|
||||
pub duration: u16,
|
||||
/// byte 4..9: receiver/ destination for 802.11p
|
||||
pub address_1: [u8; 6],
|
||||
/// byte 10..15: transmitter/ source for 802.11p
|
||||
pub address_2: [u8; 6],
|
||||
/// byte 16..21: BSSID for 802.11p
|
||||
pub address_3: [u8; 6],
|
||||
/// byte 22..24: upper 12 bits: seq. no, lower 4 bits: fragment no
|
||||
pub sequence_control: u16,
|
||||
// Note: address 4 is not used in 802.11p
|
||||
// pub qos: Option<[u8; 2]>, // QoS frames are supported by radio library
|
||||
// Note: 802.11p does not use HT mode(s)
|
||||
}
|
||||
|
||||
impl DataFrameHeader {
|
||||
const BCAST_MAC: [u8; 6] = [0xFF; 6];
|
||||
|
||||
/// Create an 802.11p (non-QoS) data frame header with a source MAC address
|
||||
pub(crate) fn new_80211p(source_mac: &[u8; 6]) -> Self {
|
||||
Self {
|
||||
type_and_subtype: 0x08,
|
||||
address_1: Self::BCAST_MAC,
|
||||
address_2: *source_mac,
|
||||
address_3: Self::BCAST_MAC,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn to_binary(&self) -> [u8; 24] {
|
||||
let mut buf = [0u8; 24];
|
||||
|
||||
buf[0] = self.type_and_subtype;
|
||||
|
||||
buf[1] = self.fcf_flags;
|
||||
|
||||
Self::write_u16(&mut buf, 2, self.duration);
|
||||
Self::write_mac_addr(&mut buf, 4, &self.address_1);
|
||||
Self::write_mac_addr(&mut buf, 10, &self.address_2);
|
||||
Self::write_mac_addr(&mut buf, 16, &self.address_3);
|
||||
Self::write_u16(&mut buf, 22, self.sequence_control);
|
||||
|
||||
buf
|
||||
}
|
||||
|
||||
fn write_u16(buf: &mut [u8; 24], offset: usize, data: u16) {
|
||||
for (idx, byte) in data.to_be_bytes().iter().enumerate() {
|
||||
buf[idx + offset] = *byte;
|
||||
}
|
||||
}
|
||||
|
||||
fn write_mac_addr(buf: &mut [u8; 24], offset: usize, data: &[u8; 6]) {
|
||||
for (idx, byte) in data.iter().enumerate() {
|
||||
buf[idx + offset] = *byte;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// IEEE 802.11p LLC header
|
||||
#[derive(Debug, Default)]
|
||||
struct LlcHeader {
|
||||
/// byte 0: SNAP (0xaa) in 802.11p
|
||||
pub dsap: u8, // SNAP in
|
||||
/// byte 1: SNAP (0xaa) in 802.11p
|
||||
pub lsap: u8, // SNAP in
|
||||
/// byte 2: SNAP (0xaa) in 802.11p
|
||||
pub control_1: u8, // 0x03
|
||||
// Note: optional second control byte not used in 802.11p
|
||||
|
||||
// SNAP fields
|
||||
/// set to 0 since ethertype is used for protocol ID in 802.11p
|
||||
pub oui: [u8; 3],
|
||||
/// ethertype GeoNetworking (0x8947) in 802.11p
|
||||
pub protocol_id: u16,
|
||||
}
|
||||
|
||||
impl LlcHeader {
|
||||
pub(crate) fn new_80211p() -> Self {
|
||||
Self {
|
||||
dsap: 0xaa,
|
||||
lsap: 0xaa,
|
||||
control_1: 0x03,
|
||||
oui: [0; 3],
|
||||
protocol_id: 0x8947,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn to_binary(&self) -> [u8; 8] {
|
||||
let mut buf: [u8; 8] = [
|
||||
self.dsap,
|
||||
self.lsap,
|
||||
self.control_1,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
];
|
||||
|
||||
buf[3] = self.oui[0];
|
||||
buf[4] = self.oui[1];
|
||||
buf[5] = self.oui[2];
|
||||
|
||||
buf[6] = self.protocol_id.to_be_bytes()[0];
|
||||
buf[7] = self.protocol_id.to_be_bytes()[1];
|
||||
|
||||
buf
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn data_frame_test() {
|
||||
// build a typical IEEE 802.11p frame
|
||||
const OWN_MAC: [u8; 6] = [0x12, 0x34, 0x56, 0x78, 0xab, 0xcd];
|
||||
const REF_HEADER: [u8; 24] = [
|
||||
0x08, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x12, 0x34, 0x56, 0x78,
|
||||
0xab, 0xcd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
|
||||
];
|
||||
|
||||
let frame = DataFrameHeader::new_80211p(&OWN_MAC);
|
||||
|
||||
assert_eq!(0x08, frame.type_and_subtype); // data frame, subtype 0
|
||||
assert_eq!(0x0000, frame.fcf_flags);
|
||||
assert_eq!(0x0000, frame.duration);
|
||||
assert_eq!([0xFF; 6], frame.address_1);
|
||||
assert_eq!(OWN_MAC, frame.address_2);
|
||||
assert_eq!([0xFF; 6], frame.address_3);
|
||||
assert_eq!(0x0000, frame.sequence_control);
|
||||
|
||||
assert_eq!(REF_HEADER, frame.to_binary());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn llc_test() {
|
||||
// build a typical IEEE 802.11p LLC header
|
||||
const REF_HEADER: [u8; 8] = [0xaa, 0xaa, 0x03, 0x00, 0x00, 0x00, 0x89, 0x47];
|
||||
|
||||
let llc = LlcHeader::new_80211p();
|
||||
|
||||
assert_eq!(0xaa, llc.dsap);
|
||||
assert_eq!(0xaa, llc.lsap);
|
||||
assert_eq!(0x03, llc.control_1);
|
||||
assert_eq!([0x00; 3], llc.oui);
|
||||
assert_eq!(0x8947, llc.protocol_id);
|
||||
|
||||
assert_eq!(REF_HEADER, llc.to_binary());
|
||||
}
|
||||
}
|
||||
36
src/rx/mod.rs
Normal file
36
src/rx/mod.rs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
//! V2X Reception Utilities
|
||||
|
||||
/// Determines if the WiFi packet is a broadcast data frame
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns a human-readable string, if data contains unexpected values
|
||||
pub fn is_broadcast_frame(
|
||||
frame: &esp_radio::wifi::sniffer::PromiscuousPkt<'_>,
|
||||
) -> Result<bool, alloc::string::String> {
|
||||
// Ignore frames with errors and non-data frames
|
||||
if frame.rx_cntl.rx_state != 0 {
|
||||
return Err(alloc::format!(
|
||||
"Received frame has RX error: {}",
|
||||
frame.rx_cntl.rx_state
|
||||
));
|
||||
}
|
||||
if !crate::radio::PromiscuousPktType::Data.matches(frame.frame_type) {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// Ignore non-broadcast frames
|
||||
if frame.len < (4 + 6) {
|
||||
return Err(alloc::format!(
|
||||
"Received frame too small to be a valid broadcast frame: {} bytes",
|
||||
frame.len
|
||||
));
|
||||
}
|
||||
let dest_mac = &frame.data[4..10];
|
||||
if dest_mac != [0xff; 6] {
|
||||
Err(alloc::format!(
|
||||
"Received frame is not broadcast frame: Dest MAC {dest_mac:02x?}"
|
||||
))
|
||||
} else {
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
111
src/tx/gn.rs
Normal file
111
src/tx/gn.rs
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
//! Geonetworking utilities
|
||||
|
||||
use c_its_parser::gn as geonetworking;
|
||||
|
||||
#[allow(unused)]
|
||||
/// Creates a Single-Hop Broadcast (SHB) Extended Header from parts
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns a human-readable error description if values are out of range
|
||||
pub fn make_shb_eh(
|
||||
address: [u8; 6],
|
||||
station_type: geonetworking::en302636_4_1::StationType,
|
||||
own_position: &crate::PosVel,
|
||||
time: chrono::DateTime<chrono::Utc>,
|
||||
) -> Result<geonetworking::en302636_4_1::ExtendedHeader, alloc::string::String> {
|
||||
let source_position_vector = make_lpv(address, station_type, own_position, time)?;
|
||||
|
||||
Ok(geonetworking::en302636_4_1::ExtendedHeader::SHB(
|
||||
geonetworking::en302636_4_1::SingleHopBroadcast {
|
||||
source_position_vector,
|
||||
media_dependent_data: [0; 4],
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
/// Creates a Topo-Scoped Broadcast (TSB) Extended Header from parts
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns a human-readable error description if values are out of range
|
||||
pub fn make_tsb_eh(
|
||||
address: [u8; 6],
|
||||
station_type: geonetworking::en302636_4_1::StationType,
|
||||
own_position: &crate::PosVel,
|
||||
time: chrono::DateTime<chrono::Utc>,
|
||||
sequence_number: u16,
|
||||
) -> Result<geonetworking::en302636_4_1::ExtendedHeader, alloc::string::String> {
|
||||
let source_position_vector = make_lpv(address, station_type, own_position, time)?;
|
||||
|
||||
Ok(geonetworking::en302636_4_1::ExtendedHeader::TSB(
|
||||
geonetworking::en302636_4_1::TopologicallyScopedBroadcast::new(
|
||||
sequence_number,
|
||||
source_position_vector,
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
// #[allow(unused)]
|
||||
// /// Creates a Geo-Broadcast Extended Header
|
||||
// ///
|
||||
// /// # Panics
|
||||
// /// Can't panic in practice b/c angle is zero
|
||||
// #[must_use]
|
||||
// pub fn make_gbc_eh(
|
||||
// address: [u8; 6],
|
||||
// station_type: geonetworking::en302636_4_1::StationType,
|
||||
// own_position: &crate::PosVel,
|
||||
// time: chrono::DateTime<chrono::Utc>,
|
||||
// target_pos: geo_types::Point,
|
||||
// radius_m: u16,
|
||||
// sequence_number: u16,
|
||||
// ) -> geonetworking::en302636_4_1::ExtendedHeader {
|
||||
// let source_position_vector = make_lpv(address, station_type, own_position, time);
|
||||
// #[allow(clippy::cast_possible_truncation)]
|
||||
// let latitude_deg = target_pos.x() as f32;
|
||||
// #[allow(clippy::cast_possible_truncation)]
|
||||
// let longitude_deg = target_pos.y() as f32;
|
||||
|
||||
// geonetworking::en302636_4_1::ExtendedHeader::GBC(
|
||||
// geonetworking::en302636_4_1::GeoBroadcast::try_from_values(
|
||||
// sequence_number,
|
||||
// source_position_vector,
|
||||
// latitude_deg,
|
||||
// longitude_deg,
|
||||
// radius_m,
|
||||
// 0,
|
||||
// 0,
|
||||
// )
|
||||
// .expect("Failed to create circular GBC"),
|
||||
// )
|
||||
// }
|
||||
|
||||
/// Creates a Long Position Vector from parts
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns a human-readable error description if values are out of range
|
||||
pub fn make_lpv(
|
||||
address: [u8; 6],
|
||||
station_type: geonetworking::en302636_4_1::StationType,
|
||||
own_position: &crate::PosVel,
|
||||
time: chrono::DateTime<chrono::Utc>,
|
||||
) -> Result<geonetworking::en302636_4_1::LongPositionVector, alloc::string::String> {
|
||||
let timestamp_its = c_its_parser::time_utils::TimestampIts::from(time);
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
let latitude_deg = own_position.position.y() as f32;
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
let longitude_deg = own_position.position.x() as f32;
|
||||
let speed_mps = own_position.speed_mps.unwrap_or_default();
|
||||
let heading_deg = own_position.heading_deg.unwrap_or_default();
|
||||
|
||||
geonetworking::en302636_4_1::LongPositionVector::try_from_values(
|
||||
geonetworking::en302636_4_1::Address::new(false, station_type, address),
|
||||
timestamp_its.0,
|
||||
latitude_deg,
|
||||
longitude_deg,
|
||||
true,
|
||||
speed_mps,
|
||||
heading_deg,
|
||||
)
|
||||
.map_err(|err| alloc::format!("LPV invalid input: {err}"))
|
||||
}
|
||||
164
src/tx/mod.rs
Normal file
164
src/tx/mod.rs
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
//! V2X Transmission Utilities
|
||||
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use c_its_parser::gn as geonetworking;
|
||||
|
||||
pub mod gn;
|
||||
|
||||
/// Interface for ITS Payloads inside a Geonetworking header
|
||||
pub trait GnItsPayload {
|
||||
// common GN parameters
|
||||
const LIFETIME_MILLIS: u32 = 60_000; // itsGnDefaultPacketLifetime is 60 seconds according to ETSI EN 302 636-4-1 V1.4.1
|
||||
|
||||
// ---------------------------------------------
|
||||
// methods to build a GN packet with ITS payload
|
||||
// ---------------------------------------------
|
||||
|
||||
/// Builds the [`geonetworking::en302636_4_1::ExtendedHeader`] and additional information for the ITS message
|
||||
///
|
||||
/// Output:
|
||||
/// - Extended header
|
||||
/// - Area type (when using GAC or GBC)
|
||||
/// - hop limit
|
||||
///
|
||||
/// Note: Make sure to increase the sequence number on multi-hop packets.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns a human-readable error description if values are out of range
|
||||
fn make_eh(
|
||||
&self,
|
||||
address: [u8; 6],
|
||||
own_position: &crate::PosVel,
|
||||
time: chrono::DateTime<chrono::Utc>,
|
||||
seq_no: &mut u16,
|
||||
) -> Result<
|
||||
(
|
||||
geonetworking::en302636_4_1::ExtendedHeader,
|
||||
Option<geonetworking::en302636_4_1::AreaType>,
|
||||
u8,
|
||||
),
|
||||
alloc::string::String,
|
||||
>;
|
||||
|
||||
/// Creates the ITS payload (without BTP header)
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns a human-readable error description if values are out of range
|
||||
fn make_payload(
|
||||
&self,
|
||||
) -> Result<(Vec<u8>, c_its_parser::standards::extensions::ItsMessageId), alloc::string::String>;
|
||||
|
||||
// ---------------------------------------------
|
||||
// common methods (with implementation)
|
||||
// ---------------------------------------------
|
||||
|
||||
/// Encodes the GN packet
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns a human-readable error description if values are out of range
|
||||
fn encode_packet(
|
||||
&self,
|
||||
address: [u8; 6],
|
||||
own_position: &crate::PosVel,
|
||||
time: chrono::DateTime<chrono::Utc>,
|
||||
is_mobile: bool,
|
||||
seq_no: &mut u16,
|
||||
) -> Result<Vec<u8>, alloc::string::String> {
|
||||
use geonetworking::Encode as _;
|
||||
|
||||
// Build payload (including BTP header)
|
||||
let (mut its_payload, msg_type) = self.make_payload()?;
|
||||
let mut payload = make_btp_b(msg_type).encode()?;
|
||||
|
||||
payload.append(&mut its_payload);
|
||||
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
let payload_length = (payload.len()) as u16;
|
||||
|
||||
// get extended header (depending on message type and sometimes content as well)
|
||||
let (eh, area_type, maximum_hop_limit) =
|
||||
self.make_eh(address, own_position, time, seq_no)?;
|
||||
|
||||
// Build GN headers
|
||||
let traffic_class = geonetworking::en302636_4_1::TrafficClass::try_new(
|
||||
false,
|
||||
false,
|
||||
make_traffic_class(msg_type),
|
||||
)
|
||||
.map_err(|err| alloc::format!("Failed to create TrafficClass: {err:?}"))?;
|
||||
let common = geonetworking::en302636_4_1::CommonHeader::from_values(
|
||||
geonetworking::en302636_4_1::NextAfterCommon::BTPB,
|
||||
geonetworking::en302636_4_1::HeaderType::new_from(&eh, area_type)?,
|
||||
traffic_class,
|
||||
is_mobile,
|
||||
payload_length,
|
||||
maximum_hop_limit,
|
||||
);
|
||||
|
||||
let remaining_hop_limit = maximum_hop_limit;
|
||||
let basic = geonetworking::en302636_4_1::BasicHeader::try_new(
|
||||
1,
|
||||
geonetworking::en302636_4_1::NextAfterBasic::CommonHeader,
|
||||
geonetworking::en302636_4_1::Lifetime::from_milliseconds(Self::LIFETIME_MILLIS),
|
||||
remaining_hop_limit,
|
||||
)
|
||||
.map_err(|err| alloc::format!("Failed to create BasicHeader: {err}"))?;
|
||||
|
||||
// Assemble and encode packet
|
||||
let packet = geonetworking::Packet::Unsecured {
|
||||
basic,
|
||||
common,
|
||||
extended: Some(eh),
|
||||
payload: &payload,
|
||||
};
|
||||
|
||||
packet
|
||||
.encode_to_vec()
|
||||
.map_err(|err| alloc::format!("Failed to encode GN packet: {err:?}"))
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a BTP-B header based on the ITS message ID (message type)
|
||||
///
|
||||
/// Port numbers and destination port info according to ETSI TS 103 248
|
||||
fn make_btp_b(
|
||||
msg_type: c_its_parser::standards::extensions::ItsMessageId,
|
||||
) -> c_its_parser::transport::TransportHeader {
|
||||
let (destination_port, destination_port_info) = match msg_type {
|
||||
c_its_parser::standards::extensions::ItsMessageId::Denm => (2002, 0),
|
||||
c_its_parser::standards::extensions::ItsMessageId::Cam => (2001, 0),
|
||||
c_its_parser::standards::extensions::ItsMessageId::Mapem => (2003, 0),
|
||||
c_its_parser::standards::extensions::ItsMessageId::Spatem => (2004, 0),
|
||||
c_its_parser::standards::extensions::ItsMessageId::Saem => (2005, 0),
|
||||
c_its_parser::standards::extensions::ItsMessageId::Ivim => (2006, 0),
|
||||
c_its_parser::standards::extensions::ItsMessageId::Srem => (2007, 0),
|
||||
c_its_parser::standards::extensions::ItsMessageId::Ssem => (2008, 0),
|
||||
c_its_parser::standards::extensions::ItsMessageId::Cpm => (2009, 0),
|
||||
c_its_parser::standards::extensions::ItsMessageId::Poi => (2010, 0),
|
||||
c_its_parser::standards::extensions::ItsMessageId::EvRsr => (2012, 0),
|
||||
c_its_parser::standards::extensions::ItsMessageId::Rtcmem => (2013, 0),
|
||||
_ => (0, 0), // no information found for the subsequent types
|
||||
};
|
||||
|
||||
c_its_parser::transport::TransportHeader::BtpB(c_its_parser::transport::BasicTransportBHeader {
|
||||
destination_port,
|
||||
destination_port_info,
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a traffic class ID based on the ITS message ID (message type)
|
||||
fn make_traffic_class(msg_type: c_its_parser::standards::extensions::ItsMessageId) -> u8 {
|
||||
#[allow(clippy::match_same_arms)]
|
||||
match msg_type {
|
||||
c_its_parser::standards::extensions::ItsMessageId::Denm => 1,
|
||||
c_its_parser::standards::extensions::ItsMessageId::Cam => 2,
|
||||
c_its_parser::standards::extensions::ItsMessageId::Mapem => 3, // from C-Roads RS_RSP_063(1)
|
||||
c_its_parser::standards::extensions::ItsMessageId::Spatem => 3, // from C-Roads RS_RSP_063(1)
|
||||
c_its_parser::standards::extensions::ItsMessageId::Ivim => 3, // from C-Roads RS_RSP_063(1)
|
||||
c_its_parser::standards::extensions::ItsMessageId::Srem => 2, // from C-Roads RS_RSP_063(1)
|
||||
c_its_parser::standards::extensions::ItsMessageId::Ssem => 2, // from C-Roads RS_RSP_063(1)
|
||||
c_its_parser::standards::extensions::ItsMessageId::Rtcmem => 3, // CSP_MaxPrio=252 -> 3
|
||||
_ => 3, // fall-back to lowest priority
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue