use std::fmt;
use std::net::SocketAddr;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TransportType {
Udp,
Ble,
LoRa,
Serial,
Ax25,
I2p,
Yggdrasil,
}
impl fmt::Display for TransportType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Udp => write!(f, "UDP"),
Self::Ble => write!(f, "BLE"),
Self::LoRa => write!(f, "LoRa"),
Self::Serial => write!(f, "Serial"),
Self::Ax25 => write!(f, "AX.25"),
Self::I2p => write!(f, "I2P"),
Self::Yggdrasil => write!(f, "Yggdrasil"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct LoRaParams {
pub spreading_factor: u8,
pub bandwidth_khz: u16,
pub coding_rate: u8,
}
impl Default for LoRaParams {
fn default() -> Self {
Self {
spreading_factor: 12, bandwidth_khz: 125, coding_rate: 5, }
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub enum TransportAddr {
Udp(SocketAddr),
Ble {
device_id: [u8; 6],
service_uuid: Option<[u8; 16]>,
},
LoRa {
device_addr: [u8; 4],
params: LoRaParams,
},
Serial {
port: String,
},
Ax25 {
callsign: String,
ssid: u8,
},
I2p {
destination: Box<[u8; 387]>,
},
Yggdrasil {
address: [u8; 16],
},
Broadcast {
transport_type: TransportType,
},
}
impl TransportAddr {
pub fn transport_type(&self) -> TransportType {
match self {
Self::Udp(_) => TransportType::Udp,
Self::Ble { .. } => TransportType::Ble,
Self::LoRa { .. } => TransportType::LoRa,
Self::Serial { .. } => TransportType::Serial,
Self::Ax25 { .. } => TransportType::Ax25,
Self::I2p { .. } => TransportType::I2p,
Self::Yggdrasil { .. } => TransportType::Yggdrasil,
Self::Broadcast { transport_type } => *transport_type,
}
}
pub fn ble(device_id: [u8; 6], service_uuid: Option<[u8; 16]>) -> Self {
Self::Ble {
device_id,
service_uuid,
}
}
pub fn lora(device_addr: [u8; 4]) -> Self {
Self::LoRa {
device_addr,
params: LoRaParams::default(),
}
}
pub fn lora_with_params(device_addr: [u8; 4], params: LoRaParams) -> Self {
Self::LoRa {
device_addr,
params,
}
}
pub fn serial(port: impl Into<String>) -> Self {
Self::Serial { port: port.into() }
}
pub fn ax25(callsign: impl Into<String>, ssid: u8) -> Self {
Self::Ax25 {
callsign: callsign.into(),
ssid: ssid.min(15), }
}
pub fn yggdrasil(address: [u8; 16]) -> Self {
Self::Yggdrasil { address }
}
pub fn broadcast(transport_type: TransportType) -> Self {
Self::Broadcast { transport_type }
}
pub fn is_broadcast(&self) -> bool {
matches!(self, Self::Broadcast { .. })
}
pub fn as_socket_addr(&self) -> Option<SocketAddr> {
match self {
Self::Udp(addr) => Some(*addr),
_ => None,
}
}
pub fn to_synthetic_socket_addr(&self) -> SocketAddr {
use std::net::{IpAddr, Ipv6Addr};
match self {
Self::Udp(addr) => *addr,
Self::Ble { device_id, .. } => {
let addr = Ipv6Addr::new(
0x2001,
0x0db8,
0x0001, ((device_id[0] as u16) << 8) | (device_id[1] as u16),
((device_id[2] as u16) << 8) | (device_id[3] as u16),
((device_id[4] as u16) << 8) | (device_id[5] as u16),
0,
0,
);
SocketAddr::new(IpAddr::V6(addr), 0)
}
Self::LoRa { device_addr, .. } => {
let addr = Ipv6Addr::new(
0x2001,
0x0db8,
0x0002, ((device_addr[0] as u16) << 8) | (device_addr[1] as u16),
((device_addr[2] as u16) << 8) | (device_addr[3] as u16),
0,
0,
0,
);
SocketAddr::new(IpAddr::V6(addr), 0)
}
Self::Serial { port } => {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
port.hash(&mut hasher);
let hash = hasher.finish();
let addr = Ipv6Addr::new(
0x2001,
0x0db8,
0x0003, (hash >> 48) as u16,
(hash >> 32) as u16,
(hash >> 16) as u16,
hash as u16,
0,
);
SocketAddr::new(IpAddr::V6(addr), 0)
}
Self::Ax25 { callsign, ssid } => {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
callsign.hash(&mut hasher);
ssid.hash(&mut hasher);
let hash = hasher.finish();
let addr = Ipv6Addr::new(
0x2001,
0x0db8,
0x0004, (hash >> 48) as u16,
(hash >> 32) as u16,
(hash >> 16) as u16,
hash as u16,
0,
);
SocketAddr::new(IpAddr::V6(addr), 0)
}
Self::I2p { destination } => {
let addr = Ipv6Addr::new(
0x2001,
0x0db8,
0x0005, ((destination[0] as u16) << 8) | (destination[1] as u16),
((destination[2] as u16) << 8) | (destination[3] as u16),
((destination[4] as u16) << 8) | (destination[5] as u16),
((destination[6] as u16) << 8) | (destination[7] as u16),
0,
);
SocketAddr::new(IpAddr::V6(addr), 0)
}
Self::Yggdrasil { address } => {
let addr = Ipv6Addr::new(
0x2001,
0x0db8,
0x0006, ((address[0] as u16) << 8) | (address[1] as u16),
((address[2] as u16) << 8) | (address[3] as u16),
((address[4] as u16) << 8) | (address[5] as u16),
((address[6] as u16) << 8) | (address[7] as u16),
0,
);
SocketAddr::new(IpAddr::V6(addr), 0)
}
Self::Broadcast { transport_type } => {
let type_code = match transport_type {
TransportType::Udp => 0,
TransportType::Ble => 1,
TransportType::LoRa => 2,
TransportType::Serial => 3,
TransportType::Ax25 => 4,
TransportType::I2p => 5,
TransportType::Yggdrasil => 6,
};
let addr = Ipv6Addr::new(
0x2001, 0x0db8, type_code, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
);
SocketAddr::new(IpAddr::V6(addr), 0)
}
}
}
}
impl fmt::Debug for TransportAddr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Udp(addr) => write!(f, "Udp({addr})"),
Self::Ble {
device_id,
service_uuid,
} => {
write!(
f,
"Ble({:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
device_id[0],
device_id[1],
device_id[2],
device_id[3],
device_id[4],
device_id[5]
)?;
if service_uuid.is_some() {
write!(f, ", custom_service")?;
}
write!(f, ")")
}
Self::LoRa {
device_addr,
params,
} => {
write!(
f,
"LoRa(0x{:02X}{:02X}{:02X}{:02X}, SF{}, {}kHz)",
device_addr[0],
device_addr[1],
device_addr[2],
device_addr[3],
params.spreading_factor,
params.bandwidth_khz
)
}
Self::Serial { port } => write!(f, "Serial({port})"),
Self::Ax25 { callsign, ssid } => write!(f, "Ax25({callsign}-{ssid})"),
Self::I2p { .. } => write!(f, "I2p([destination])"),
Self::Yggdrasil { address } => {
write!(f, "Yggdrasil({:02x}{:02x}:...)", address[0], address[1])
}
Self::Broadcast { transport_type } => write!(f, "Broadcast({transport_type})"),
}
}
}
impl fmt::Display for TransportAddr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Udp(addr) => write!(f, "udp://{addr}"),
Self::Ble { device_id, .. } => {
write!(
f,
"ble://{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
device_id[0],
device_id[1],
device_id[2],
device_id[3],
device_id[4],
device_id[5]
)
}
Self::LoRa { device_addr, .. } => {
write!(
f,
"lora://0x{:02X}{:02X}{:02X}{:02X}",
device_addr[0], device_addr[1], device_addr[2], device_addr[3]
)
}
Self::Serial { port } => write!(f, "serial://{port}"),
Self::Ax25 { callsign, ssid } => write!(f, "ax25://{callsign}-{ssid}"),
Self::I2p { .. } => write!(f, "i2p://[destination]"),
Self::Yggdrasil { address } => {
write!(
f,
"yggdrasil://{:02x}{:02x}:{:02x}{:02x}:{:02x}{:02x}:{:02x}{:02x}:{:02x}{:02x}:{:02x}{:02x}:{:02x}{:02x}:{:02x}{:02x}",
address[0],
address[1],
address[2],
address[3],
address[4],
address[5],
address[6],
address[7],
address[8],
address[9],
address[10],
address[11],
address[12],
address[13],
address[14],
address[15]
)
}
Self::Broadcast { transport_type } => write!(f, "broadcast://{transport_type}"),
}
}
}
impl From<SocketAddr> for TransportAddr {
fn from(addr: SocketAddr) -> Self {
Self::Udp(addr)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_udp_addr() {
let addr: SocketAddr = "192.168.1.1:9000".parse().unwrap();
let transport_addr = TransportAddr::Udp(addr);
assert_eq!(transport_addr.transport_type(), TransportType::Udp);
assert_eq!(transport_addr.as_socket_addr(), Some(addr));
assert!(!transport_addr.is_broadcast());
}
#[test]
fn test_ble_addr() {
let device_id = [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC];
let addr = TransportAddr::ble(device_id, None);
assert_eq!(addr.transport_type(), TransportType::Ble);
assert!(addr.as_socket_addr().is_none());
let debug_str = format!("{addr:?}");
assert!(debug_str.contains("12:34:56:78:9A:BC"));
}
#[test]
fn test_lora_addr() {
let device_addr = [0xDE, 0xAD, 0xBE, 0xEF];
let addr = TransportAddr::lora(device_addr);
assert_eq!(addr.transport_type(), TransportType::LoRa);
if let TransportAddr::LoRa { params, .. } = &addr {
assert_eq!(params.spreading_factor, 12);
assert_eq!(params.bandwidth_khz, 125);
}
}
#[test]
fn test_lora_custom_params() {
let device_addr = [0xDE, 0xAD, 0xBE, 0xEF];
let params = LoRaParams {
spreading_factor: 7,
bandwidth_khz: 500,
coding_rate: 8,
};
let addr = TransportAddr::lora_with_params(device_addr, params.clone());
if let TransportAddr::LoRa { params: p, .. } = &addr {
assert_eq!(p.spreading_factor, 7);
assert_eq!(p.bandwidth_khz, 500);
assert_eq!(p.coding_rate, 8);
}
}
#[test]
fn test_serial_addr() {
let addr = TransportAddr::serial("/dev/ttyUSB0");
assert_eq!(addr.transport_type(), TransportType::Serial);
let display = format!("{addr}");
assert_eq!(display, "serial:///dev/ttyUSB0");
}
#[test]
fn test_ax25_addr() {
let addr = TransportAddr::ax25("N0CALL", 5);
assert_eq!(addr.transport_type(), TransportType::Ax25);
if let TransportAddr::Ax25 { callsign, ssid } = &addr {
assert_eq!(callsign, "N0CALL");
assert_eq!(*ssid, 5);
}
}
#[test]
fn test_ax25_ssid_clamp() {
let addr = TransportAddr::ax25("N0CALL", 20);
if let TransportAddr::Ax25 { ssid, .. } = &addr {
assert_eq!(*ssid, 15); }
}
#[test]
fn test_broadcast_addr() {
let addr = TransportAddr::broadcast(TransportType::Ble);
assert!(addr.is_broadcast());
assert_eq!(addr.transport_type(), TransportType::Ble);
}
#[test]
fn test_from_socket_addr() {
let socket_addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();
let transport_addr: TransportAddr = socket_addr.into();
assert_eq!(transport_addr, TransportAddr::Udp(socket_addr));
}
#[test]
fn test_from_socket_addr_ipv6() {
let socket_addr: SocketAddr = "[::1]:9000".parse().unwrap();
let transport_addr = TransportAddr::from(socket_addr);
assert_eq!(transport_addr.transport_type(), TransportType::Udp);
assert_eq!(transport_addr.as_socket_addr(), Some(socket_addr));
match transport_addr.as_socket_addr().unwrap() {
SocketAddr::V6(_) => {} SocketAddr::V4(_) => panic!("Expected IPv6 address, got IPv4"),
}
}
#[test]
fn test_socket_addr_conversion_pattern() {
let socket_addr: SocketAddr = "192.168.1.100:5000".parse().unwrap();
let transport_addr = TransportAddr::from(socket_addr);
assert_eq!(transport_addr.transport_type(), TransportType::Udp);
assert_eq!(transport_addr.as_socket_addr(), Some(socket_addr));
let transport_addr2: TransportAddr = socket_addr.into();
assert_eq!(transport_addr, transport_addr2);
let ble = TransportAddr::ble([0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF], None);
assert_eq!(ble.as_socket_addr(), None);
let serial = TransportAddr::serial("/dev/ttyUSB0");
assert_eq!(serial.as_socket_addr(), None);
}
#[test]
fn test_display_formats() {
let udp_addr = TransportAddr::Udp("192.168.1.1:9000".parse().unwrap());
assert_eq!(format!("{udp_addr}"), "udp://192.168.1.1:9000");
let ble_addr = TransportAddr::ble([0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF], None);
assert_eq!(format!("{ble_addr}"), "ble://AA:BB:CC:DD:EE:FF");
let serial_addr = TransportAddr::serial("COM3");
assert_eq!(format!("{serial_addr}"), "serial://COM3");
}
#[test]
fn test_transport_type_display() {
assert_eq!(format!("{}", TransportType::Udp), "UDP");
assert_eq!(format!("{}", TransportType::Ble), "BLE");
assert_eq!(format!("{}", TransportType::LoRa), "LoRa");
assert_eq!(format!("{}", TransportType::Serial), "Serial");
assert_eq!(format!("{}", TransportType::Ax25), "AX.25");
assert_eq!(format!("{}", TransportType::I2p), "I2P");
assert_eq!(format!("{}", TransportType::Yggdrasil), "Yggdrasil");
}
}