extern crate alloc;
use crate::{bits, Bits};
use alloc::string::ToString;
#[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)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct Address {
pub manually_configured: bool,
pub station_type: StationType,
pub reserved: Bits<10>,
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: bits![0;10],
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,
}
#[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)]
#[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: i16,
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> {
if !(Self::SPEED_MIN..=Self::SPEED_MAX).contains(&speed) {
return 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) / 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: u8,
pub next_header: NextAfterBasic,
pub reserved: Bits<8>,
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> {
if version > 15 {
return Err(Error::ValueOutOfBounds(OutOfBoundsError::new(
"version", "u4",
)));
}
Ok(Self {
version,
next_header,
reserved: bits![0; 8],
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,
}
#[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: Bits<4>,
pub header_type_and_subtype: HeaderType,
pub traffic_class: TrafficClass,
pub flags: Bits<8>,
pub payload_length: u16,
pub maximum_hop_limit: u8,
pub reserved_2: Bits<8>,
}
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 {
let flags = Bits(flags.iter().collect::<_>());
Self {
next_header,
reserved_1: bits![0; 4],
header_type_and_subtype,
traffic_class,
flags,
payload_length,
maximum_hop_limit,
reserved_2: bits![0; 8],
}
}
#[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 mobile_flag = u8::from(is_mobile);
let flags = bits![mobile_flag, 0, 0, 0, 0, 0, 0, 0];
Self {
next_header,
reserved_1: bits![0; 4],
header_type_and_subtype,
traffic_class,
flags,
payload_length,
maximum_hop_limit,
reserved_2: bits![0; 8],
}
}
}
#[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: u8,
}
impl TrafficClass {
pub fn try_new(
store_carry_forward: bool,
channel_offload: bool,
traffic_class_id: u8,
) -> Result<Self, Error> {
if traffic_class_id > 63 {
return 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,
}
#[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),
}
#[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: Bits<16>,
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: bits![0; 16],
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: Bits<16>,
pub source_position_vector: LongPositionVector,
}
impl TopologicallyScopedBroadcast {
#[must_use]
pub fn new(sequence_number: u16, source_position_vector: LongPositionVector) -> Self {
Self {
sequence_number,
reserved: bits![0; 16],
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: Bits<16>,
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: Bits<16>,
}
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: bits![0; 16],
source_position_vector,
geo_area_position_latitude,
geo_area_position_longitude,
distance_a,
distance_b,
angle,
reserved_2: bits![0; 16],
}
}
}
#[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: Bits<16>,
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: bits![0; 16],
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: Bits<16>,
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: bits![0; 16],
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());
}
}