extern crate alloc;
use alloc::string::ToString;
use arbitrary_int::{i15, traits::Integer, u10, u4, u6};
#[cfg(feature = "json")]
use serde::{Deserialize, Serialize};
const LATLON_TO_INT_FACTOR: f32 = 1e7; const ANGLE_TO_INT_FACTOR: f32 = 10f32;
#[derive(Debug, PartialEq, Eq)]
pub enum Error {
ValueOutOfBounds(OutOfBoundsError),
ValueOutOfRange(alloc::string::String),
}
impl core::fmt::Display for Error {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Error::ValueOutOfBounds(out_of_bounds_error) => write!(f, "{out_of_bounds_error}"),
Error::ValueOutOfRange(value_name) => {
write!(f, "Value out of range: '{value_name}' is not a sane value")
}
}
}
}
impl core::error::Error for Error {}
#[derive(Debug, PartialEq, Eq)]
pub struct OutOfBoundsError {
pub value_name: alloc::string::String,
pub bounds_name: alloc::string::String,
}
impl core::fmt::Display for OutOfBoundsError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"Value out of bounds: given '{}' does not fit inside an {}",
self.value_name, self.bounds_name
)
}
}
impl OutOfBoundsError {
fn new(value_name: &str, bounds_name: &str) -> Self {
Self {
value_name: value_name.to_string(),
bounds_name: bounds_name.to_string(),
}
}
}
fn make_latitude(deg: f32) -> Result<i32, Error> {
#[allow(clippy::cast_possible_truncation)]
let raw = (deg * LATLON_TO_INT_FACTOR) as i32;
if (-900_000_000..=900_000_000).contains(&raw) {
Ok(raw)
} else {
Err(Error::ValueOutOfRange("latitude".to_string()))
}
}
fn make_longitude(deg: f32) -> Result<i32, Error> {
#[allow(clippy::cast_possible_truncation)]
let raw = (deg * LATLON_TO_INT_FACTOR) as i32;
if (-1_800_000_000..=1_800_000_000).contains(&raw) {
Ok(raw)
} else {
Err(Error::ValueOutOfRange("longitude".to_string()))
}
}
fn make_heading(deg: f32) -> Result<u16, Error> {
if deg < 0. {
return Err(Error::ValueOutOfRange("heading".to_string()));
}
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let raw = (deg * ANGLE_TO_INT_FACTOR) as u16;
if raw > 3600 {
Err(Error::ValueOutOfRange("heading".to_string()))
} else {
Ok(raw)
}
}
#[derive(Debug, Clone, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct Address {
pub manually_configured: bool, pub station_type: StationType, pub reserved: u10, pub address: [u8; 6], }
impl Address {
#[must_use]
pub fn new(manually_configured: bool, station_type: StationType, address: [u8; 6]) -> Self {
Self {
manually_configured,
station_type,
reserved: u10::from_u16(0),
address,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub enum StationType {
#[default]
Unknown = 0,
Pedestrian = 1,
Cyclist = 2,
Moped = 3,
Motorcycle = 4,
PassengerCar = 5,
Bus = 6,
LightTruck = 7,
HeavyTruck = 8,
Trailer = 9,
SpecialVehicle = 10,
Tram = 11,
RoadSideUnit = 15,
}
impl TryFrom<u8> for StationType {
type Error = alloc::string::String;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Unknown),
1 => Ok(Self::Pedestrian),
2 => Ok(Self::Cyclist),
3 => Ok(Self::Moped),
4 => Ok(Self::Motorcycle),
5 => Ok(Self::PassengerCar),
6 => Ok(Self::Bus),
7 => Ok(Self::LightTruck),
8 => Ok(Self::HeavyTruck),
9 => Ok(Self::Trailer),
10 => Ok(Self::SpecialVehicle),
11 => Ok(Self::Tram),
15 => Ok(Self::RoadSideUnit),
i => Err(alloc::format!(
"No corresponding station type for value {i}!"
)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct Timestamp(pub u32);
impl Timestamp {
#[must_use]
pub fn as_unix_timestamp(&self) -> u64 {
u64::from(self.0) + 1_072_915_200_000
}
#[must_use]
pub fn from_its_timestamp(timestamp_its: u64) -> Self {
#[allow(clippy::cast_possible_truncation)]
let ts_mod = (timestamp_its % 4_294_967_296) as u32;
Self(ts_mod)
}
}
#[derive(Debug, Clone, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct LongPositionVector {
pub gn_address: Address, pub timestamp: Timestamp, pub latitude: i32, pub longitude: i32, pub position_accuracy: bool, pub speed: i15, pub heading: u16, }
impl LongPositionVector {
const MPS_TO_INT_FACTOR: f32 = 100f32; const SPEED_MIN: i16 = -16_384;
const SPEED_MAX: i16 = 16_383;
pub fn try_from_values(
gn_address: Address,
timestamp_its: u64,
latitude_deg: f32,
longitude_deg: f32,
position_accuracy: bool,
speed_mps: f32,
heading_deg: f32,
) -> Result<Self, Error> {
let latitude = make_latitude(latitude_deg)?;
let longitude = make_longitude(longitude_deg)?;
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let speed = (speed_mps * Self::MPS_TO_INT_FACTOR) as i16;
let heading = make_heading(heading_deg)?;
let timestamp = Timestamp::from_its_timestamp(timestamp_its);
Self::try_new(
gn_address,
timestamp,
latitude,
longitude,
position_accuracy,
speed,
heading,
)
}
pub fn try_new(
gn_address: Address,
timestamp: Timestamp,
latitude: i32,
longitude: i32,
position_accuracy: bool,
speed: i16,
heading: u16,
) -> Result<Self, Error> {
let speed = i15::try_new(speed)
.map_err(|_| Error::ValueOutOfBounds(OutOfBoundsError::new("speed", "i15")))?;
Ok(Self {
gn_address,
timestamp,
latitude,
longitude,
position_accuracy,
speed,
heading,
})
}
#[must_use]
pub fn get_position_deg(&self) -> (f32, f32) {
#[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
let lat = (self.latitude as f32) / LATLON_TO_INT_FACTOR;
#[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
let lon = (self.longitude as f32) / LATLON_TO_INT_FACTOR;
(lat, lon)
}
#[must_use]
pub fn get_speed_mps(&self) -> f32 {
f32::from(self.speed.as_i16()) / Self::MPS_TO_INT_FACTOR
}
#[must_use]
pub fn get_heading_deg(&self) -> f32 {
f32::from(self.heading) / ANGLE_TO_INT_FACTOR
}
#[must_use]
pub fn clamp_speed_mps(speed_mps: f32) -> f32 {
let min_mps = f32::from(Self::SPEED_MIN) / Self::MPS_TO_INT_FACTOR;
let max_mps = f32::from(Self::SPEED_MAX) / Self::MPS_TO_INT_FACTOR;
speed_mps.clamp(min_mps, max_mps)
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct ShortPositionVector {
pub gn_address: Address, pub timestamp: Timestamp, pub latitude: i32, pub longitude: i32, }
impl ShortPositionVector {
pub fn try_from_values(
gn_address: Address,
timestamp_its: u64,
latitude_deg: f32,
longitude_deg: f32,
) -> Result<Self, Error> {
let latitude = make_latitude(latitude_deg)?;
let longitude = make_longitude(longitude_deg)?;
let timestamp = Timestamp::from_its_timestamp(timestamp_its);
Ok(Self {
gn_address,
timestamp,
latitude,
longitude,
})
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct BasicHeader {
pub version: u4, pub next_header: NextAfterBasic, pub reserved: u8, pub lifetime: Lifetime, pub remaining_hop_limit: u8, }
impl BasicHeader {
pub fn try_new(
version: u8,
next_header: NextAfterBasic,
lifetime: Lifetime,
remaining_hop_limit: u8,
) -> Result<Self, Error> {
let version = u4::try_new(version)
.map_err(|_| Error::ValueOutOfBounds(OutOfBoundsError::new("version", "u4")))?;
Ok(Self {
version,
next_header,
reserved: 0,
lifetime,
remaining_hop_limit,
})
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub enum NextAfterBasic {
Any = 0,
CommonHeader = 1,
SecuredPacket = 2,
}
impl TryFrom<u8> for NextAfterBasic {
type Error = alloc::string::String;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Any),
1 => Ok(Self::CommonHeader),
2 => Ok(Self::SecuredPacket),
i => Err(alloc::format!(
"No corresponding header type for value {i}!"
)),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct Lifetime(pub u8);
impl Lifetime {
#[must_use]
pub fn from_raw(value: u8) -> Self {
Self(value)
}
#[must_use]
pub fn from_milliseconds(millis: u32) -> Self {
const ITS_GN_MAX_PACKET_LIFETIME: u32 = 600_000;
let (multiplier, base) = if millis < 3000 {
let multiplier = millis / 50;
(multiplier, 0) } else if millis < 60_000 {
let multiplier = millis / 1000;
(multiplier, 1) } else if millis < 600_000 {
let multiplier = millis / 10_000;
(multiplier, 2) } else if millis < ITS_GN_MAX_PACKET_LIFETIME {
let multiplier = millis / 100_000;
(multiplier, 3) } else {
(ITS_GN_MAX_PACKET_LIFETIME / 100_000, 3) };
#[allow(clippy::cast_possible_truncation)]
let lifetime_data = (multiplier << 2) as u8 | (base & 0x03);
Self(lifetime_data)
}
#[must_use]
pub fn base(&self) -> u8 {
self.0 & 0b0000_0011
}
#[must_use]
pub fn multiplier(&self) -> u8 {
self.0 >> 2
}
#[must_use]
pub fn as_milliseconds(&self) -> u32 {
match self.base() {
0 => 50 * u32::from(self.multiplier()),
1 => 1000 * u32::from(self.multiplier()),
2 => 10000 * u32::from(self.multiplier()),
3 => 100_000 * u32::from(self.multiplier()),
_ => unreachable!(),
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct CommonHeader {
pub next_header: NextAfterCommon, pub reserved_1: u4, pub header_type_and_subtype: HeaderType, pub traffic_class: TrafficClass, pub flags: [bool; 8], pub payload_length: u16, pub maximum_hop_limit: u8, pub reserved_2: u8, }
impl CommonHeader {
#[must_use]
pub fn new(
next_header: NextAfterCommon,
header_type_and_subtype: HeaderType,
traffic_class: TrafficClass,
flags: [bool; 8],
payload_length: u16,
maximum_hop_limit: u8,
) -> Self {
Self {
next_header,
reserved_1: u4::from_u8(0),
header_type_and_subtype,
traffic_class,
flags,
payload_length,
maximum_hop_limit,
reserved_2: 0,
}
}
#[must_use]
pub fn from_values(
next_header: NextAfterCommon,
header_type_and_subtype: HeaderType,
traffic_class: TrafficClass,
is_mobile: bool,
payload_length: u16,
maximum_hop_limit: u8,
) -> Self {
let flags = [is_mobile, false, false, false, false, false, false, false];
Self {
next_header,
reserved_1: u4::from_u8(0),
header_type_and_subtype,
traffic_class,
flags,
payload_length,
maximum_hop_limit,
reserved_2: 0,
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct TrafficClass {
pub store_carry_forward: bool,
pub channel_offload: bool,
pub traffic_class_id: u6, }
impl TrafficClass {
pub fn try_new(
store_carry_forward: bool,
channel_offload: bool,
traffic_class_id: u8,
) -> Result<Self, Error> {
let traffic_class_id = u6::try_new(traffic_class_id).map_err(|_| {
Error::ValueOutOfBounds(OutOfBoundsError::new("traffic_class_id", "u6"))
})?;
Ok(Self {
store_carry_forward,
channel_offload,
traffic_class_id,
})
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub enum NextAfterCommon {
Any = 0,
BTPA = 1,
BTPB = 2,
IPv6 = 3,
}
impl TryFrom<u8> for NextAfterCommon {
type Error = alloc::string::String;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Any),
1 => Ok(Self::BTPA),
2 => Ok(Self::BTPB),
3 => Ok(Self::IPv6),
i => Err(alloc::format!(
"No corresponding header type for value {i}!"
)),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub enum HeaderType {
Any,
Beacon,
GeoUnicast,
GeoAnycast(AreaType),
GeoBroadcast(AreaType),
TopologicallyScopedBroadcast(BroadcastType),
LocationService(LocationServiceType),
}
impl HeaderType {
#[allow(unused)]
pub fn new_from(
value: &ExtendedHeader,
area_type: Option<AreaType>,
) -> Result<Self, alloc::string::String> {
Ok(match &value {
ExtendedHeader::Beacon(_) => Self::Beacon,
ExtendedHeader::GAC(gac) => {
let atype = if gac.distance_b == 0 && gac.angle == 0 {
AreaType::Circular
} else if let Some(area) = area_type {
area
} else {
return Err("GAC area type can't be inferred and isn't provided".into());
};
Self::GeoAnycast(atype)
}
ExtendedHeader::GBC(gac) => {
let atype = if gac.distance_b == 0 && gac.angle == 0 {
AreaType::Circular
} else if let Some(area) = area_type {
area
} else {
return Err("GBC area type can't be inferred and isn't provided".into());
};
Self::GeoBroadcast(atype)
}
ExtendedHeader::GUC(_) => Self::GeoUnicast,
ExtendedHeader::TSB(_) => Self::TopologicallyScopedBroadcast(BroadcastType::MultiHop),
ExtendedHeader::SHB(_) => Self::TopologicallyScopedBroadcast(BroadcastType::SingleHop),
ExtendedHeader::LSRequest(_) => Self::LocationService(LocationServiceType::Request),
ExtendedHeader::LSReply(_) => Self::LocationService(LocationServiceType::Reply),
})
}
}
impl TryFrom<u8> for HeaderType {
type Error = alloc::string::String;
fn try_from(value: u8) -> Result<Self, Self::Error> {
let ty = (value >> 4) & 0x0F;
let subtype = value & 0x0F;
let error = alloc::format!(
"No corresponding header type for value {ty} and subtype value {subtype}!"
);
match ty {
0 => Ok(Self::Any),
1 => Ok(Self::Beacon),
2 => Ok(Self::GeoUnicast),
3 => match subtype {
0 => Ok(Self::GeoAnycast(AreaType::Circular)),
1 => Ok(Self::GeoAnycast(AreaType::Rectangular)),
2 => Ok(Self::GeoAnycast(AreaType::Ellipsoidal)),
_ => Err(error),
},
4 => match subtype {
0 => Ok(Self::GeoBroadcast(AreaType::Circular)),
1 => Ok(Self::GeoBroadcast(AreaType::Rectangular)),
2 => Ok(Self::GeoBroadcast(AreaType::Ellipsoidal)),
_ => Err(error),
},
5 => match subtype {
0 => Ok(Self::TopologicallyScopedBroadcast(BroadcastType::SingleHop)),
1 => Ok(Self::TopologicallyScopedBroadcast(BroadcastType::MultiHop)),
_ => Err(error),
},
6 => match subtype {
0 => Ok(Self::LocationService(LocationServiceType::Request)),
1 => Ok(Self::LocationService(LocationServiceType::Reply)),
_ => Err(error),
},
_ => Err(error),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub enum AreaType {
Circular,
Rectangular,
Ellipsoidal,
}
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub enum BroadcastType {
SingleHop,
MultiHop,
}
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub enum LocationServiceType {
Request,
Reply,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub enum ExtendedHeader {
GUC(GeoUnicast),
TSB(TopologicallyScopedBroadcast),
SHB(SingleHopBroadcast),
GBC(GeoBroadcast),
GAC(GeoAnycast),
Beacon(Beacon),
LSRequest(LSRequest),
LSReply(LSReply),
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct GeoUnicast {
pub sequence_number: u16, pub reserved: u16, pub source_position_vector: LongPositionVector, pub destination_position_vector: ShortPositionVector, }
impl GeoUnicast {
#[must_use]
pub fn new(
sequence_number: u16,
source_position_vector: LongPositionVector,
destination_position_vector: ShortPositionVector,
) -> Self {
Self {
sequence_number,
reserved: 0,
source_position_vector,
destination_position_vector,
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct TopologicallyScopedBroadcast {
pub sequence_number: u16, pub reserved: u16, pub source_position_vector: LongPositionVector, }
impl TopologicallyScopedBroadcast {
#[must_use]
pub fn new(sequence_number: u16, source_position_vector: LongPositionVector) -> Self {
Self {
sequence_number,
reserved: 0,
source_position_vector,
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct SingleHopBroadcast {
pub source_position_vector: LongPositionVector, pub media_dependent_data: [u8; 4], }
pub type GeoBroadcast = GeoAnycast;
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct GeoAnycast {
pub sequence_number: u16, pub reserved_1: u16, pub source_position_vector: LongPositionVector, pub geo_area_position_latitude: i32, pub geo_area_position_longitude: i32, pub distance_a: u16, pub distance_b: u16, pub angle: u16, pub reserved_2: u16, }
impl GeoAnycast {
pub fn try_from_values(
sequence_number: u16,
source_position_vector: LongPositionVector,
latitude_deg: f32,
longitude_deg: f32,
distance_a: u16,
distance_b: u16,
angle: u16,
) -> Result<Self, Error> {
let latitude = make_latitude(latitude_deg)?;
let longitude = make_longitude(longitude_deg)?;
if angle > 360 {
return Err(Error::ValueOutOfRange("angle".to_string()));
}
Ok(Self::new(
sequence_number,
source_position_vector,
latitude,
longitude,
distance_a,
distance_b,
angle,
))
}
#[must_use]
pub fn new(
sequence_number: u16,
source_position_vector: LongPositionVector,
geo_area_position_latitude: i32,
geo_area_position_longitude: i32,
distance_a: u16,
distance_b: u16,
angle: u16,
) -> Self {
Self {
sequence_number,
reserved_1: 0,
source_position_vector,
geo_area_position_latitude,
geo_area_position_longitude,
distance_a,
distance_b,
angle,
reserved_2: 0,
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct Beacon {
pub source_position_vector: LongPositionVector, }
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct LSRequest {
pub sequence_number: u16, pub reserved: u16, pub source_position_vector: LongPositionVector, pub request_gn_address: Address, }
impl LSRequest {
#[must_use]
pub fn new(
sequence_number: u16,
source_position_vector: LongPositionVector,
request_gn_address: Address,
) -> Self {
Self {
sequence_number,
reserved: 0,
source_position_vector,
request_gn_address,
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct LSReply {
pub sequence_number: u16, pub reserved: u16, pub source_position_vector: LongPositionVector, pub destination_position_vector: ShortPositionVector, }
impl LSReply {
#[must_use]
pub fn new(
sequence_number: u16,
source_position_vector: LongPositionVector,
destination_position_vector: ShortPositionVector,
) -> Self {
Self {
sequence_number,
reserved: 0,
source_position_vector,
destination_position_vector,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn convert_latitude() {
assert_eq!(Ok(0), make_latitude(0.));
assert_eq!(Ok(900_000_000), make_latitude(90.));
assert_eq!(Ok(-900_000_000), make_latitude(-90.));
assert!(make_latitude(90.1).is_err());
assert!(make_latitude(-90.1).is_err());
}
#[test]
fn convert_longitude() {
assert_eq!(Ok(0), make_longitude(0.));
assert_eq!(Ok(1_800_000_000), make_longitude(180.));
assert_eq!(Ok(-1_800_000_000), make_longitude(-180.));
assert!(make_longitude(180.1).is_err());
assert!(make_longitude(-180.1).is_err());
}
#[test]
fn convert_heading() {
assert_eq!(Ok(0), make_heading(0.));
assert_eq!(Ok(3600), make_heading(360.));
assert!(make_heading(360.1).is_err());
assert!(make_heading(-1.).is_err());
}
#[test]
fn gets_lifetime_base() {
assert_eq!(Lifetime(127).base(), 3);
assert_eq!(Lifetime(126).base(), 2);
assert_eq!(Lifetime(125).base(), 1);
}
#[test]
fn gets_lifetime_multiplier() {
assert_eq!(Lifetime(5).multiplier(), 1);
assert_eq!(Lifetime(9).multiplier(), 2);
assert_eq!(Lifetime(125).multiplier(), 31);
assert_eq!(Lifetime(255).multiplier(), 63);
}
}