#[cfg(any(feature = "native", test))]
use crate::AddressType;
use crate::MultiAddr;
use serde::{Deserialize, Serialize};
pub const MAX_TRANSPORT_ADDRESS_PAYLOAD: usize = 2 * 1024;
pub const MAX_TRANSPORT_ADDRESS_RECORDS: usize = 16;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KnownTransport {
Quic,
WebRtcDirect,
}
impl KnownTransport {
pub const QUIC_ID: u16 = 1;
pub const WEBRTC_DIRECT_ID: u16 = 2;
#[must_use]
pub const fn id(self) -> u16 {
match self {
Self::Quic => Self::QUIC_ID,
Self::WebRtcDirect => Self::WEBRTC_DIRECT_ID,
}
}
#[must_use]
pub const fn from_id(id: u16) -> Option<Self> {
match id {
Self::QUIC_ID => Some(Self::Quic),
Self::WEBRTC_DIRECT_ID => Some(Self::WebRtcDirect),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KnownReachability {
Relay,
Direct,
Unverified,
Lan,
}
impl KnownReachability {
pub const RELAY_ID: u16 = 1;
pub const DIRECT_ID: u16 = 2;
pub const UNVERIFIED_ID: u16 = 3;
pub const LAN_ID: u16 = 4;
#[must_use]
pub const fn id(self) -> u16 {
match self {
Self::Relay => Self::RELAY_ID,
Self::Direct => Self::DIRECT_ID,
Self::Unverified => Self::UNVERIFIED_ID,
Self::Lan => Self::LAN_ID,
}
}
#[must_use]
pub const fn from_id(id: u16) -> Option<Self> {
match id {
Self::RELAY_ID => Some(Self::Relay),
Self::DIRECT_ID => Some(Self::Direct),
Self::UNVERIFIED_ID => Some(Self::Unverified),
Self::LAN_ID => Some(Self::Lan),
_ => None,
}
}
#[must_use]
#[cfg(any(feature = "native", test))]
pub(crate) const fn from_legacy(value: AddressType) -> Self {
match value {
AddressType::Relay => Self::Relay,
AddressType::Direct => Self::Direct,
AddressType::Unverified => Self::Unverified,
AddressType::Lan => Self::Lan,
}
}
#[must_use]
#[cfg(any(feature = "native", test))]
pub(crate) const fn into_legacy(self) -> AddressType {
match self {
Self::Relay => AddressType::Relay,
Self::Direct => AddressType::Direct,
Self::Unverified => AddressType::Unverified,
Self::Lan => AddressType::Lan,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TransportAddressRecord {
pub transport: u16,
pub reachability: u16,
#[serde(deserialize_with = "bounded_address")]
pub address: Vec<u8>,
}
impl TransportAddressRecord {
pub fn from_multiaddr(
address: &MultiAddr,
reachability: KnownReachability,
) -> Result<Option<Self>, postcard::Error> {
let transport = if address.is_quic() {
KnownTransport::Quic
} else if address.is_webrtc_direct() {
KnownTransport::WebRtcDirect
} else {
return Ok(None);
};
let reachability = match transport {
KnownTransport::Quic => reachability,
KnownTransport::WebRtcDirect => KnownReachability::Unverified,
};
Ok(Some(Self {
transport: transport.id(),
reachability: reachability.id(),
address: postcard::to_stdvec(address)?,
}))
}
pub fn decode_known(&self) -> Result<Option<MultiAddr>, postcard::Error> {
let Some(transport) = KnownTransport::from_id(self.transport) else {
return Ok(None);
};
let address: MultiAddr = postcard::from_bytes(&self.address)?;
let matches_transport = match transport {
KnownTransport::Quic => address.is_quic(),
KnownTransport::WebRtcDirect => address.is_webrtc_direct(),
};
Ok(matches_transport.then_some(address))
}
#[must_use]
#[cfg(any(feature = "native", test))]
pub(crate) fn legacy_reachability(&self) -> Option<AddressType> {
KnownReachability::from_id(self.reachability).map(KnownReachability::into_legacy)
}
#[must_use]
#[cfg(any(feature = "native", test))]
pub(crate) fn is_within_wire_bounds(&self) -> bool {
!self.address.is_empty() && self.address.len() <= MAX_TRANSPORT_ADDRESS_PAYLOAD
}
}
fn bounded_address<'de, D: serde::Deserializer<'de>>(d: D) -> Result<Vec<u8>, D::Error> {
crate::signed_address::bounded_vec::<_, _, MAX_TRANSPORT_ADDRESS_PAYLOAD>(d)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{PeerId, WebRtcCertificateHash, WebRtcDirectAddr};
#[test]
fn known_names_have_stable_open_wire_ids() {
assert_eq!(KnownTransport::Quic.id(), 1);
assert_eq!(KnownTransport::WebRtcDirect.id(), 2);
assert_eq!(KnownReachability::Relay.id(), 1);
assert_eq!(KnownReachability::Direct.id(), 2);
assert_eq!(KnownReachability::Unverified.id(), 3);
assert_eq!(KnownReachability::Lan.id(), 4);
}
#[test]
fn unknown_identifiers_round_trip_without_enum_decode_failure() {
let record = TransportAddressRecord {
transport: 900,
reachability: 901,
address: vec![1, 2, 3, 4],
};
let bytes = postcard::to_stdvec(&record).unwrap();
let decoded: TransportAddressRecord = postcard::from_bytes(&bytes).unwrap();
assert_eq!(decoded, record);
assert_eq!(decoded.decode_known().unwrap(), None);
assert_eq!(decoded.legacy_reachability(), None);
}
#[test]
fn quic_record_round_trips() {
let address: MultiAddr = "/ip4/203.0.113.9/udp/12000/quic".parse().unwrap();
let record = TransportAddressRecord::from_multiaddr(&address, KnownReachability::Direct)
.unwrap()
.unwrap();
assert_eq!(record.transport, KnownTransport::Quic.id());
assert_eq!(record.legacy_reachability(), Some(AddressType::Direct));
assert_eq!(record.decode_known().unwrap(), Some(address));
}
#[test]
fn webrtc_direct_record_is_always_unverified() {
let peer_id = PeerId::from_bytes([0x22; 32]);
let address = MultiAddr::webrtc_direct(
WebRtcDirectAddr::new(
"203.0.113.9:42768".parse().unwrap(),
WebRtcCertificateHash::new([0x33; 32]),
)
.unwrap(),
)
.with_peer_id(peer_id);
let record = TransportAddressRecord::from_multiaddr(&address, KnownReachability::Direct)
.unwrap()
.unwrap();
assert_eq!(record.transport, KnownTransport::WebRtcDirect.id());
assert_eq!(record.legacy_reachability(), Some(AddressType::Unverified));
assert_eq!(record.decode_known().unwrap(), Some(address));
}
#[test]
fn declared_transport_must_match_the_encoded_multiaddress() {
let address: MultiAddr = "/ip4/203.0.113.9/udp/12000/quic".parse().unwrap();
let mut record =
TransportAddressRecord::from_multiaddr(&address, KnownReachability::Direct)
.unwrap()
.unwrap();
record.transport = KnownTransport::WebRtcDirect.id();
assert_eq!(record.decode_known().unwrap(), None);
}
}