use crate::BoundedVec;
use crate::netmap::{CapacityError, Error, MAX_DERP_HOSTNAME, MAX_DERP_NODES, MAX_DERP_REGIONS};
pub const DEFAULT_DERP_PORT: u16 = 443;
pub const DEFAULT_STUN_PORT: u16 = 3478;
#[cfg(not(feature = "alloc"))]
const MAX_DERP_CERT_NAMES: usize = 4;
#[cfg(feature = "alloc")]
const MAX_DERP_CERT_NAMES: usize = NO_CERT_NAME as usize;
#[cfg(not(feature = "alloc"))]
type CertIndex = u8;
#[cfg(feature = "alloc")]
type CertIndex = u32;
const INSECURE_BIT: CertIndex = 1 << (CertIndex::BITS - 1);
const NO_CERT_NAME: CertIndex = !INSECURE_BIT;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DerpCertName(CertIndex);
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DerpNode {
pub host_name: heapless::String<MAX_DERP_HOSTNAME>,
pub cert_name: Option<DerpCertName>,
pub port: u16,
pub stun_port: u16,
}
impl DerpNode {
pub fn insecure_for_tests(&self) -> bool {
self.cert_name.is_some_and(DerpCertName::is_insecure)
}
pub(crate) fn mark_insecure_for_tests(&mut self) {
let index = self
.cert_name
.map_or(NO_CERT_NAME, |name| name.0 & NO_CERT_NAME);
self.cert_name = Some(DerpCertName(index | INSECURE_BIT));
}
}
impl DerpCertName {
const fn is_insecure(self) -> bool {
self.0 & INSECURE_BIT != 0
}
const fn index(self) -> Option<usize> {
let index = self.0 & NO_CERT_NAME;
if index == NO_CERT_NAME {
None
} else {
Some(index as usize)
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DerpRegion {
pub id: u16,
pub code: heapless::String<16>,
pub no_measure_no_home: bool,
pub nodes: BoundedVec<DerpNode, MAX_DERP_NODES>,
}
impl DerpRegion {
pub fn can_be_home(&self) -> bool {
!self.no_measure_no_home && self.nodes.iter().any(|node| node.port != 0)
}
}
#[derive(Debug, Default, Clone)]
pub struct DerpMap {
regions: BoundedVec<DerpRegion, MAX_DERP_REGIONS>,
cert_names: BoundedVec<heapless::String<MAX_DERP_HOSTNAME>, MAX_DERP_CERT_NAMES>,
truncated: bool,
}
impl DerpMap {
pub const fn new() -> Self {
Self {
regions: BoundedVec::new(),
cert_names: BoundedVec::new(),
truncated: false,
}
}
pub fn truncated(&self) -> bool {
self.truncated
}
pub(crate) fn mark_truncated(&mut self) {
self.truncated = true;
}
pub fn len(&self) -> usize {
self.regions.len()
}
pub fn is_empty(&self) -> bool {
self.regions.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = &DerpRegion> {
self.regions.iter()
}
pub(crate) fn copy_from(&mut self, source: &Self) {
self.regions.clear();
self.cert_names.clear();
self.truncated = source.truncated;
let _ = self.cert_names.try_reserve_total(source.cert_names.len());
for name in source.cert_names.iter() {
if self.cert_names.push(name.clone()).is_err() {
self.truncated = true;
break;
}
}
let names = self.cert_names.len();
let _ = self.regions.try_reserve_total(source.regions.len());
for region in source.iter() {
let mut copy = DerpRegion {
id: region.id,
code: region.code.clone(),
no_measure_no_home: region.no_measure_no_home,
nodes: BoundedVec::new(),
};
let _ = copy.nodes.try_reserve_total(region.nodes.len());
for node in region.nodes.iter() {
let named = node
.cert_name
.and_then(DerpCertName::index)
.is_none_or(|index| index < names);
if !named || copy.nodes.push(node.clone()).is_err() {
self.truncated = true;
}
}
self.insert_bounded(copy);
}
}
pub fn region(&self, id: u16) -> Option<&DerpRegion> {
self.regions.iter().find(|r| r.id == id)
}
pub fn cert_name<'a>(&'a self, node: &'a DerpNode) -> &'a str {
node.cert_name
.and_then(DerpCertName::index)
.and_then(|index| self.cert_names.get(index))
.map_or(node.host_name.as_str(), |name| name.as_str())
}
pub(crate) fn intern_cert_name(
&mut self,
name: &str,
pending: &mut [DerpNode],
) -> Result<DerpCertName, Error> {
#[cfg(not(feature = "alloc"))]
let existing = self
.cert_names
.iter()
.position(|existing| existing.as_str() == name);
#[cfg(feature = "alloc")]
let existing = self
.cert_names
.last()
.filter(|existing| existing.as_str() == name)
.map(|_| self.cert_names.len() - 1);
if let Some(index) = existing {
return Ok(DerpCertName(index as CertIndex));
}
let name = heapless::String::try_from(name)
.map_err(|_| Error::Full(super::CapacityError::String))?;
#[cfg(not(feature = "alloc"))]
if self.cert_names.is_full() {
self.compact_cert_names(pending);
}
#[cfg(feature = "alloc")]
let _ = pending;
self.cert_names
.push(name)
.map_err(|_| Error::Full(super::CapacityError::String))?;
Ok(DerpCertName((self.cert_names.len() - 1) as CertIndex))
}
#[cfg(not(feature = "alloc"))]
fn compact_cert_names(&mut self, pending: &mut [DerpNode]) {
let Self {
regions,
cert_names,
..
} = self;
let mut kept = BoundedVec::<_, MAX_DERP_CERT_NAMES>::new();
let mut remap = [None::<u8>; MAX_DERP_CERT_NAMES];
let nodes = regions
.iter_mut()
.flat_map(|region| region.nodes.iter_mut())
.chain(pending.iter_mut());
for node in nodes {
let Some(reference) = node.cert_name else {
continue;
};
let Some(index) = reference.index() else {
continue;
};
let Some(name) = cert_names.get(index) else {
continue;
};
let new = *remap[index].get_or_insert_with(|| {
let _ = kept.push(name.clone());
(kept.len() - 1) as u8
});
node.cert_name = Some(DerpCertName(new | (reference.0 & INSECURE_BIT)));
}
*cert_names = kept;
}
pub(crate) fn region_mut(&mut self, id: u16) -> Option<&mut DerpRegion> {
self.regions.iter_mut().find(|r| r.id == id)
}
pub fn insert(&mut self, region: DerpRegion) -> Result<(), Error> {
if let Some(existing) = self.regions.iter_mut().find(|r| r.id == region.id) {
*existing = region;
return Ok(());
}
self.regions
.push(region)
.map_err(|_| Error::Full(CapacityError::DerpRegions))
}
pub(crate) fn insert_bounded(&mut self, region: DerpRegion) {
if let Some(existing) = self.regions.iter_mut().find(|r| r.id == region.id) {
*existing = region;
return;
}
let Err(region) = self.regions.push(region) else {
return;
};
self.truncated = true;
if let Some(highest) = self
.regions
.iter_mut()
.max_by_key(|existing| existing.id)
.filter(|existing| existing.id > region.id)
{
*highest = region;
}
}
pub fn clear(&mut self) {
self.regions.clear();
self.cert_names.clear();
self.truncated = false;
}
}
#[cfg(test)]
mod tests {
use super::*;
fn region(id: u16) -> DerpRegion {
DerpRegion {
id,
code: heapless::String::try_from("lab").unwrap(),
no_measure_no_home: false,
nodes: BoundedVec::new(),
}
}
#[test]
fn a_region_replaces_rather_than_duplicates() {
let mut map = DerpMap::new();
map.insert(region(999)).unwrap();
map.insert(region(999)).unwrap();
assert_eq!(map.len(), 1);
assert!(map.region(999).is_some());
assert!(map.region(1).is_none());
}
#[cfg(not(feature = "alloc"))]
#[test]
fn a_map_larger_than_the_table_reports_rather_than_truncating() {
let mut map = DerpMap::new();
for id in 0..MAX_DERP_REGIONS {
map.insert(region(id as u16)).unwrap();
}
assert_eq!(
map.insert(region(999)),
Err(Error::Full(CapacityError::DerpRegions))
);
}
#[cfg(feature = "alloc")]
#[test]
fn a_heap_map_holds_every_region() {
let mut map = DerpMap::new();
for id in 0..1000 {
map.insert_bounded(region(id));
}
assert_eq!(map.len(), 1000);
assert!(!map.truncated());
let mut copy = DerpMap::new();
copy.copy_from(&map);
assert_eq!(copy.len(), 1000);
assert!(!copy.truncated());
}
}