diff --git a/src/base/iana/mod.rs b/src/base/iana/mod.rs index d4bbba5f..10d6037b 100644 --- a/src/base/iana/mod.rs +++ b/src/base/iana/mod.rs @@ -34,6 +34,7 @@ pub use self::opt::OptionCode; pub use self::rcode::{OptRcode, Rcode, TsigRcode}; pub use self::rtype::Rtype; pub use self::secalg::SecAlg; +pub use self::svcb::SvcbParamKey; #[macro_use] mod macros; @@ -47,3 +48,4 @@ pub mod opt; pub mod rcode; pub mod rtype; pub mod secalg; +pub mod svcb; diff --git a/src/base/iana/rtype.rs b/src/base/iana/rtype.rs index 7f466344..88a676b8 100644 --- a/src/base/iana/rtype.rs +++ b/src/base/iana/rtype.rs @@ -299,6 +299,16 @@ int_enum! { /// See draft-wessels-dns-zone-digest. (Zonemd => 63, b"ZONEMD") + /// General Purpose Service Endpoints. + /// + /// See draft-ietf-dnsop-svcb-httpssvc + (Svcb => 64, b"SVCB") + + /// HTTPS Specific Service Endpoints. + /// + /// See draft-ietf-dnsop-svcb-httpssvc + (Https => 65, b"HTTPS") + /// SPF. /// /// RFC 7208. diff --git a/src/base/iana/svcb.rs b/src/base/iana/svcb.rs new file mode 100644 index 00000000..01092397 --- /dev/null +++ b/src/base/iana/svcb.rs @@ -0,0 +1,43 @@ +//! Service Binding (SVCB) Parameter Registry + +use core::fmt; + +//------------ Service Binding Parameters ----------- + +int_enum! { + => + SvcbParamKey, u16; + + (Mandatory => 0, b"Mandatory keys in this RR") + (Alpn => 1, b"Additional supported protocols") + (NoDefaultAlpn => 2, b"Additional supported protocols") + (Port => 3, b"Port for alternative endpoint") + (Ipv4Hint => 4, b"IPv4 address hints") + // https://datatracker.ietf.org/doc/draft-ietf-tls-esni/ + (Ech => 5, b"Encrypted ClientHello info") + (Ipv6Hint => 6, b"IPv6 address hints") + // https://datatracker.ietf.org/doc/draft-ietf-add-svcb-dns/ + (DohPath => 7, b"DNS over HTTPS path template") +} + +pub const SVCB_PARAM_KEY_PRIVATE_RANGE_BEGIN: u16 = 65280; +pub const SVCB_PARAM_KEY_PRIVATE_RANGE_END: u16 = 65534; +pub const SVCB_PARAM_KEY_INVALID: u16 = 65535; + +impl fmt::Display for SvcbParamKey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let s = match self { + Self::Mandatory => "mandatory", + Self::Alpn => "alpn", + Self::NoDefaultAlpn => "nodefaultalpn", + Self::Port => "port", + Self::Ipv4Hint => "ipv4hint", + Self::Ech => "ech", + Self::Ipv6Hint => "ipv6hint", + Self::DohPath => "dohpath", + Self::Int(n) => return write!(f, "key{}", n), + }; + + f.write_str(s) + } +} diff --git a/src/rdata/mod.rs b/src/rdata/mod.rs index b70c2357..dd08098e 100644 --- a/src/rdata/mod.rs +++ b/src/rdata/mod.rs @@ -35,6 +35,7 @@ pub mod rfc4034; pub mod rfc5155; pub mod rfc6672; pub mod rfc7344; +pub mod svcb; // The rdata_types! macro (defined in self::macros) re-exports the record data // types here and creates the ZoneRecordData and AllRecordData enums @@ -113,4 +114,10 @@ rdata_types! { Cds, } } + svcb::{ + pseudo { + Svcb, + Https, + } + } } diff --git a/src/rdata/svcb.rs b/src/rdata/svcb.rs new file mode 100644 index 00000000..7a2ebeec --- /dev/null +++ b/src/rdata/svcb.rs @@ -0,0 +1,1363 @@ +// Implementation of SVCB RR type +// https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-svcb-https-08#section-8 + +use crate::base::iana::{Rtype, SvcbParamKey}; +use crate::base::name::{ParsedDname, ToDname}; +use crate::base::octets::{ + Compose, Octets512, OctetsBuilder, OctetsFrom, OctetsRef, Parse, + ParseError, Parser, ShortBuf, +}; +use crate::base::rdata::RtypeRecordData; +use core::{fmt, hash}; +use param::{AllParams, SvcbParam}; + +// Types in SVCB type group are based on the same format. +macro_rules! svcb_types { + ($($name:ident,)+) => { + $( +/// Struct has priority and target decoded, but not parameters. +/// Provides a [`iter`](Svcb::iter) method to iterate through each parameter. +#[derive(Clone)] +pub struct $name { + priority: u16, + target: N, + params: O, + sorter: Sorter, +} + +impl $name { + /// Create a new SVCB(or its siblings) with given arguments. + /// + /// # Examples + /// + /// To parse a SVCB record + /// ```ignore + /// let rdata = SVCB::new(1, dname, octets); + /// for param in rdata.iter() { + /// ... + /// } + /// ``` + /// To build a SCVB record + /// ```ignore + /// let rdata = SCVB::new(1, dname, octets_builder); + /// rdata.push(param1)?; + /// rdata.push(param2)?; + /// rdata.freeze(); + /// ``` + pub fn new(priority: u16, target: N, params: O) -> Self { + Self { + priority, + target, + params, + sorter: Default::default(), + } + } + + /// Get the priority. + pub fn priority(&self) -> u16 { + self.priority + } + + /// Get the target. Note the target won't be translated to owner + /// automatically in service mode if it equals to root. + pub fn target(&self) -> &N { + &self.target + } +} + +impl OctetsFrom<$name> for $name +where + OO: OctetsFrom, + NN: OctetsFrom, +{ + fn octets_from(source: $name) -> Result { + Ok($name::new( + source.priority, + NN::octets_from(source.target)?, + OO::octets_from(source.params)?, + )) + } +} + +impl $name { + /// Freeze the internal OctetsBuilder. + pub fn freeze(self) -> $name { + $name { + priority: self.priority, + target: self.target, + params: self.params.freeze(), + sorter: self.sorter, + } + } + + /// Push a parameter into the builder. + pub fn push>( + &mut self, + param: AllParams, + ) -> Result<(), ShortBuf> { + let key = param.key().into(); + let off = self.params.len(); + param.compose(&mut self.params)?; + let len = self.params.len() - off; + self.sorter.insert(key, off as u16, len as u16) + } +} + +//--- Parse, ParseAll, Compose and Compress + +impl Parse for $name> { + fn parse(parser: &mut Parser) -> Result { + let priority = u16::parse(parser)?; + let target = ParsedDname::parse(parser)?; + let len = parser.remaining(); + let params = parser.parse_octets(len)?; + Ok(Self::new(priority, target, params)) + } + + fn skip(parser: &mut Parser) -> Result<(), ParseError> { + u16::skip(parser)?; + ParsedDname::skip(parser)?; + parser.advance_to_end(); + Ok(()) + } +} + +impl, N: Compose> Compose for $name { + fn compose( + &self, + target: &mut T, + ) -> Result<(), ShortBuf> { + target.append_all(|buf| { + self.priority.compose(buf)?; + self.target.compose(buf)?; + + let view = self.sorter.buf.as_slice(); + let mut bytes = [0u8; 2]; + for chunk in view.chunks_exact(Sorter::CHUNK_SIZE) { + bytes[0] = chunk[2]; + bytes[1] = chunk[3]; + let off = u16::from_ne_bytes(bytes).into(); + bytes[0] = chunk[4]; + bytes[1] = chunk[5]; + let len: usize = u16::from_ne_bytes(bytes).into(); + let slice = &self.params.as_ref()[off..off + len]; + buf.append_slice(slice)?; + } + + Ok(()) + }) + } +} + +impl, N: Compose> $name { + /// Compose without checking for the order of parameters. + pub fn compose_unchecked( + &self, + target: &mut T, + ) -> Result<(), ShortBuf> { + target.append_all(|buf| { + self.priority.compose(buf)?; + self.target.compose(buf)?; + buf.append_slice(self.params.as_ref()) + }) + } +} + +//--- RtypeRecordData + +impl RtypeRecordData for $name { + const RTYPE: Rtype = Rtype::$name; +} + +impl, N> $name { + pub fn iter(&self) -> ParamIter<&[u8]> { + let parser = Parser::from_ref(self.params.as_ref()); + ParamIter { parser } + } +} + +//--- Display and Debug +impl fmt::Display for $name +where + O: AsRef<[u8]>, + N: fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} {}", self.priority, self.target)?; + for param in self.iter() { + write!(f, " {}", param.map_err(|_| fmt::Error)?)?; + } + Ok(()) + } +} + +impl fmt::Debug for $name +where + O: AsRef<[u8]>, + N: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} {:?}", self.priority, self.target)?; + for param in self.iter() { + write!(f, " {}", param.map_err(|_| fmt::Error)?)?; + } + Ok(()) + } +} + +//--- PartialEq and Eq + +impl PartialEq<$name> for $name +where + O: AsRef<[u8]>, + OO: AsRef<[u8]>, + N: ToDname, + NN: ToDname, +{ + fn eq(&self, other: &$name) -> bool { + self.priority == other.priority + && self.target.name_eq(&other.target) + && self.params.as_ref() == other.params.as_ref() + } +} + +impl, N: ToDname> Eq for $name {} + +//--- Hash + +impl, N: hash::Hash> hash::Hash for $name { + fn hash(&self, state: &mut H) { + self.priority.hash(state); + self.target.hash(state); + self.params.as_ref().hash(state); + } +} + )+ + } +} + +svcb_types!(Svcb, Https,); + +// This is a helper type to sort parameters without allocation. It +// uses internal buffer to keep parameter key sorted. With the +// additional offset and length recorded when the parameter was +// pushed, it reorder them during composing. +// +// FIXME: Using a fixed length buffer is not ideal, the size should be +// big enough. +#[derive(Clone, Default)] +struct Sorter { + n: usize, + buf: Octets512, +} + +impl Sorter { + // key + off + len + const CHUNK_SIZE: usize = 6; + fn insert( + &mut self, + key: u16, + off: u16, + len: u16, + ) -> Result<(), ShortBuf> { + let end = self.buf.len(); + + // allocate space by appending an empty chunck + self.buf.append_slice(&[0u8; Self::CHUNK_SIZE])?; + + let view = &self.buf.as_slice()[..end]; + let target = + view.chunks_exact(Self::CHUNK_SIZE) + .enumerate() + .find(|(_, c)| { + let mut key_buf = [0u8; 2]; + key_buf[0] = c[0]; + key_buf[1] = c[1]; + key <= u16::from_ne_bytes(key_buf) + }); + + let buf = match target { + None => &mut self.buf.as_slice_mut()[end..end + Self::CHUNK_SIZE], + Some((i, _)) => { + let view = &mut self.buf.as_slice_mut() + [i * Self::CHUNK_SIZE..end + Self::CHUNK_SIZE]; + view.rotate_right(Self::CHUNK_SIZE); + &mut view[..Self::CHUNK_SIZE] + } + }; + let bytes = key.to_ne_bytes(); + buf[0] = bytes[0]; + buf[1] = bytes[1]; + let bytes = off.to_ne_bytes(); + buf[2] = bytes[0]; + buf[3] = bytes[1]; + let bytes = len.to_ne_bytes(); + buf[4] = bytes[0]; + buf[5] = bytes[1]; + self.n += 1; + Ok(()) + } +} + +/// A iterator to parse each parameter. +pub struct ParamIter { + parser: Parser, +} + +impl Iterator for ParamIter +where + Ref: OctetsRef, +{ + type Item = Result, ParseError>; + + fn next(&mut self) -> Option { + if self.parser.remaining() == 0 { + return None; + } + Some(AllParams::parse(&mut self.parser)) + } +} + +pub mod param { + use super::*; + use crate::base::net::{Ipv4Addr, Ipv6Addr}; + use core::convert::TryInto; + use core::fmt::{self, Write}; + + macro_rules! param_enum { + ($($name:ident($type:ty),)+) => { + /// A enum to hold all the parameters. + #[derive(Debug, Clone, PartialEq)] + pub enum AllParams { + $($name($type)),+ + } + + impl SvcbParam for AllParams { + fn key(&self) -> SvcbParamKey { + match self { + $(Self::$name(v) => v.key()),+ + } + } + } + + impl Parse for AllParams { + fn parse(parser: &mut Parser) -> Result { + let key = parser.parse_u16()?.into(); + let len = parser.parse_u16()?.into(); + parser.parse_block(len, |parser| AllParams::parse_value(parser, key)) + } + + fn skip(parser: &mut Parser) -> Result<(), ParseError> { + u16::skip(parser)?; + let len = parser.parse_u16()?; + parser.advance(len.into()) + } + } + + impl> Compose for AllParams { + fn compose( + &self, + target: &mut T, + ) -> Result<(), ShortBuf> { + target.append_all(|buf| { + let key: u16 = self.key().into(); + key.compose(buf)?; + buf.u16_len_prefixed(|buf| match self { + $(Self::$name(v) => v.compose(buf)),+ + }) + }) + } + } + + $(impl From<$type> for AllParams { + fn from(p: $type) -> Self { + AllParams::$name(p) + } + })+ + + impl fmt::Display for AllParams { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + $(Self::$name(v) => v.fmt(f)?),+ + } + Ok(()) + } + } + }; + } + + param_enum!( + Mandatory(Mandatory), + Alpn(Alpn), + NoDefaultAlpn(NoDefaultAlpn), + Port(Port), + Ech(Ech), + Ipv4Hint(Ipv4Hint), + Ipv6Hint(Ipv6Hint), + DohPath(DohPath), + Unknown(Unknown), + ); + + /// Basic trait for SVCB parameters. + pub trait SvcbParam { + fn key(&self) -> SvcbParamKey; + } + + impl AllParams { + fn parse_value( + parser: &mut Parser, + key: SvcbParamKey, + ) -> Result, ParseError> { + let val = match key { + SvcbParamKey::Mandatory => Mandatory::parse(parser)?.into(), + SvcbParamKey::Alpn => Alpn::parse(parser)?.into(), + SvcbParamKey::NoDefaultAlpn => { + NoDefaultAlpn::parse(parser)?.into() + } + SvcbParamKey::Port => Port::parse(parser)?.into(), + SvcbParamKey::Ipv4Hint => Ipv4Hint::parse(parser)?.into(), + SvcbParamKey::Ech => Ech::parse(parser)?.into(), + SvcbParamKey::Ipv6Hint => Ipv6Hint::parse(parser)?.into(), + _ => Unknown::new( + key, + parser.parse_octets(parser.remaining())?, + ) + .into(), + }; + Ok(val) + } + } + + // for types wraps an octets + macro_rules! octets_wrapper { + ($name:ident) => { + /// A SVCB parameter. + #[derive(Debug, Clone, PartialEq)] + pub struct $name(Octets); + impl Parse for $name { + fn parse( + parser: &mut Parser, + ) -> Result { + //let len = u16::parse(parser)?; + //let data = parser.parse_octets(len.into())?; + let data = parser.parse_octets(parser.remaining())?; + Ok(Self(data)) + } + + fn skip(parser: &mut Parser) -> Result<(), ParseError> { + //u16::skip(parser)?; + parser.advance_to_end(); + Ok(()) + } + } + + impl> Compose for $name { + fn compose( + &self, + target: &mut T, + ) -> Result<(), ShortBuf> { + // target.append_all(|buf| { + // let len = self.0.as_ref().len() as u16; + // len.compose(buf)?; + // }) + target.append_slice(self.0.as_ref()) + } + } + + impl SvcbParam for $name { + fn key(&self) -> SvcbParamKey { + SvcbParamKey::$name + } + } + impl $name { + /// Create a new parameter from octets. + pub fn new(o: Octets) -> Self { + Self(o) + } + } + impl $name { + /// Freeze the internal OctetsBuilder. + pub fn freeze(self) -> $name { + $name(self.0.freeze()) + } + } + + impl $name { + pub fn for_ref(&self) -> $name<&Octets> { + $name(&self.0) + } + } + + impl> $name { + pub fn for_slice(&self) -> $name<&[u8]> { + $name(self.0.as_ref()) + } + } + + impl> $name { + pub fn as_slice(&self) -> &[u8] { + self.0.as_ref() + } + } + + impl, T> AsRef for $name { + fn as_ref(&self) -> &T { + self.0.as_ref() + } + } + + impl OctetsFrom<$name> for $name + where + OO: OctetsFrom, + { + fn octets_from(source: $name) -> Result { + Ok($name::new(OO::octets_from(source.0)?)) + } + } + }; + ($name:ident, $iter:ident) => { + octets_wrapper!($name); + + impl $name { + /// Iterate over the internal items. + pub fn iter(&self) -> $iter { + let parser = Parser::from_ref(self.0); + $iter { parser } + } + } + + /// An iterator type to parse the internal items. + pub struct $iter { + parser: Parser, + } + }; + } + + octets_wrapper!(Mandatory, MandatoryIter); + + impl Mandatory { + pub fn push(&mut self, key: SvcbParamKey) -> Result<(), ShortBuf> { + u16::from(key).compose(&mut self.0) + } + } + + impl Iterator for MandatoryIter + where + Ref: OctetsRef, + { + type Item = Result; + + fn next(&mut self) -> Option { + if self.parser.remaining() == 0 { + return None; + } + + Some(self.parser.parse_u16().map(|v| v.into())) + } + } + + impl fmt::Display for Mandatory { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + for (i, v) in self.iter().enumerate() { + let v = v.map_err(|_| fmt::Error)?; + if i == 0 { + write!(f, "mandatory={}", v)?; + } else { + write!(f, ",{}", v)?; + } + } + Ok(()) + } + } + + octets_wrapper!(Alpn, AlpnIter); + + impl Alpn { + pub fn push>( + &mut self, + name: O, + ) -> Result<(), ShortBuf> { + self.0.append_all(|buf| { + let name = name.as_ref(); + let len: u8 = name.len().try_into().map_err(|_| ShortBuf)?; + len.compose(buf)?; + buf.append_slice(name) + }) + } + } + + impl Iterator for AlpnIter + where + Ref: OctetsRef, + { + type Item = Result; + + fn next(&mut self) -> Option { + if self.parser.remaining() == 0 { + return None; + } + + Some( + self.parser + .parse_u8() + .and_then(|len| self.parser.parse_octets(len.into())), + ) + } + } + + impl fmt::Display for Alpn { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + for (i, v) in self.iter().enumerate() { + let v = v.map_err(|_| fmt::Error)?; + if i == 0 { + f.write_str("alpn=")?; + } else { + f.write_char(',')?; + } + for ch in v.as_ref() { + f.write_char(*ch as char)?; + } + } + Ok(()) + } + } + + #[derive(Debug, Clone, PartialEq)] + pub struct NoDefaultAlpn; + + impl Parse for NoDefaultAlpn { + fn parse(_parser: &mut Parser) -> Result { + Ok(Self) + } + + fn skip(_parser: &mut Parser) -> Result<(), ParseError> { + Ok(()) + } + } + + impl Compose for NoDefaultAlpn { + fn compose( + &self, + _target: &mut T, + ) -> Result<(), ShortBuf> { + Ok(()) + } + } + + impl SvcbParam for NoDefaultAlpn { + fn key(&self) -> SvcbParamKey { + SvcbParamKey::NoDefaultAlpn + } + } + + impl fmt::Display for NoDefaultAlpn { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("nodefaultalpn") + } + } + + #[derive(Debug, Clone, PartialEq)] + pub struct Port(u16); + impl Port { + pub fn new(port: u16) -> Self { + Self(port) + } + } + impl Parse for Port { + fn parse(parser: &mut Parser) -> Result { + let port = u16::parse(parser)?; + Ok(Self(port)) + } + + fn skip(parser: &mut Parser) -> Result<(), ParseError> { + u16::skip(parser)?; + Ok(()) + } + } + + impl Compose for Port { + fn compose( + &self, + target: &mut T, + ) -> Result<(), ShortBuf> { + self.0.compose(target) + } + } + + impl SvcbParam for Port { + fn key(&self) -> SvcbParamKey { + SvcbParamKey::Port + } + } + + impl fmt::Display for Port { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "port={}", self.0) + } + } + + // ECHConfigList: https://datatracker.ietf.org/doc/draft-ietf-tls-esni/ + octets_wrapper!(Ech); + + impl fmt::Display for Ech { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("ech") + } + } + + octets_wrapper!(Ipv4Hint, Ipv4HintIter); + + impl Ipv4Hint { + pub fn push(&mut self, addr: Ipv4Addr) -> Result<(), ShortBuf> { + let octets = addr.octets(); + self.0.append_slice(octets.as_ref()) + } + } + + impl Iterator for Ipv4HintIter + where + Ref: OctetsRef, + { + type Item = Result; + + fn next(&mut self) -> Option { + if self.parser.remaining() == 0 { + return None; + } + + let mut buf = [0u8; 4]; + //self.parser.check_len(buf.len())?; + if let Err(e) = self.parser.parse_buf(&mut buf) { + return Some(Err(e)); + } + Some(Ok(buf.into())) + } + } + + impl fmt::Display for Ipv4Hint { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + for (i, v) in self.iter().enumerate() { + let v = v.map_err(|_| fmt::Error)?; + if i == 0 { + write!(f, "ipv4hint={}", v)?; + } else { + write!(f, ",{}", v)?; + } + } + Ok(()) + } + } + + octets_wrapper!(Ipv6Hint, Ipv6HintIter); + + impl Ipv6Hint { + pub fn push(&mut self, addr: Ipv6Addr) -> Result<(), ShortBuf> { + let octets = addr.octets(); + self.0.append_slice(octets.as_ref()) + } + } + + impl Iterator for Ipv6HintIter + where + Ref: OctetsRef, + { + type Item = Result; + + fn next(&mut self) -> Option { + if self.parser.remaining() == 0 { + return None; + } + + let mut buf = [0u8; 16]; + //self.parser.check_len(buf.len())?; + if let Err(e) = self.parser.parse_buf(&mut buf) { + return Some(Err(e)); + } + Some(Ok(buf.into())) + } + } + + impl fmt::Display for Ipv6Hint { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + for (i, v) in self.iter().enumerate() { + let v = v.map_err(|_| fmt::Error)?; + if i == 0 { + write!(f, "ipv6hint={}", v)?; + } else { + write!(f, ",{}", v)?; + } + } + Ok(()) + } + } + + // contains an URL template: https://datatracker.ietf.org/doc/html/rfc6570 + octets_wrapper!(DohPath); + + impl fmt::Display for DohPath { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("dohpath=")?; + for ch in self.0.as_ref() { + f.write_char(*ch as char)?; + } + Ok(()) + } + } + + #[derive(Debug, Clone, PartialEq)] + pub struct Unknown { + key: SvcbParamKey, + val: Octets, + } + + impl SvcbParam for Unknown { + fn key(&self) -> SvcbParamKey { + self.key + } + } + + impl Unknown { + pub fn new(key: SvcbParamKey, val: Octets) -> Self { + Self { key, val } + } + + pub fn value(&self) -> &Octets { + &self.val + } + } + + impl> Compose for Unknown { + fn compose( + &self, + target: &mut T, + ) -> Result<(), ShortBuf> { + target.append_slice(self.val.as_ref()) + } + } + + impl fmt::Display for Unknown { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.key()) + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::base::{octets::Octets512, Dname}; + + // Test parser and composer with test vectors from appendix D + #[test] + fn test_vectors_alias() { + let rdata = + b"\x00\x00\ + \x03\x66\x6f\x6f\x07\x65\x78\x61\x6d\x70\x6c\x65\x03\x63\x6f\x6d\x00"; + + // parse test + let mut parser = Parser::from_ref(rdata.as_ref()); + let svcb = Svcb::parse(&mut parser).unwrap(); + assert_eq!(0, svcb.priority); + assert_eq!( + "foo.example.com".parse::>().unwrap(), + svcb.target + ); + assert_eq!(0, svcb.params.len()); + + // compose test + let svcb_builder = + Svcb::new(svcb.priority, svcb.target, Octets512::new()); + + let mut buf = Octets512::new(); + svcb_builder.freeze().compose(&mut buf).unwrap(); + assert_eq!(rdata.as_ref(), buf.as_ref()); + } + + #[test] + fn test_vectors_port_only() { + let rdata = + b"\x00\x10\ + \x03\x66\x6f\x6f\x07\x65\x78\x61\x6d\x70\x6c\x65\x03\x63\x6f\x6d\x00\ + \x00\x03\ + \x00\x02\ + \x00\x35"; + + // parse test + let mut parser = Parser::from_ref(rdata.as_ref()); + let svcb = Svcb::parse(&mut parser).unwrap(); + assert_eq!(16, svcb.priority); + assert_eq!( + "foo.example.com".parse::>().unwrap(), + svcb.target + ); + + let mut param_iter = svcb.iter(); + let param = param_iter.next().unwrap().unwrap(); + assert_eq!(AllParams::from(param::Port::new(53)), param); + assert_eq!(None, param_iter.next()); + + // compose test + let mut svcb_builder = + Svcb::new(svcb.priority, svcb.target, Octets512::new()); + svcb_builder + .push::<&[u8]>(param::Port::new(53).into()) + .unwrap(); + let mut buf = Octets512::new(); + svcb_builder.freeze().compose(&mut buf).unwrap(); + assert_eq!(rdata.as_ref(), buf.as_ref()); + } + + #[test] + fn test_vectors_unknown_param() { + let rdata = + b"\x00\x01\ + \x03\x66\x6f\x6f\x07\x65\x78\x61\x6d\x70\x6c\x65\x03\x63\x6f\x6d\x00\ + \x02\x9b\ + \x00\x05\ + \x68\x65\x6c\x6c\x6f"; + + // parse test + let mut parser = Parser::from_ref(rdata.as_ref()); + let svcb = Svcb::parse(&mut parser).unwrap(); + assert_eq!(1, svcb.priority); + assert_eq!( + "foo.example.com".parse::>().unwrap(), + svcb.target + ); + + let mut param_iter = svcb.iter(); + let r = param_iter.next(); + match r { + Some(Ok(AllParams::Unknown(param))) => { + assert_eq!(0x029b, param.key()); + assert_eq!(b"\x68\x65\x6c\x6c\x6f".as_ref(), *param.value(),); + } + _ => panic!("{:?}", r), + } + assert_eq!(None, param_iter.next()); + + // compose test + let mut svcb_builder = + Svcb::new(svcb.priority, svcb.target, Octets512::new()); + svcb_builder + .push(param::Unknown::new(0x029b.into(), b"hello").into()) + .unwrap(); + let mut buf = Octets512::new(); + svcb_builder.freeze().compose(&mut buf).unwrap(); + assert_eq!(rdata.as_ref(), buf.as_ref()); + } + + #[test] + fn test_vectors_unknown_param_quote() { + let rdata = + b"\x00\x01\ + \x03\x66\x6f\x6f\x07\x65\x78\x61\x6d\x70\x6c\x65\x03\x63\x6f\x6d\x00\ + \x02\x9b\ + \x00\x09\ + \x68\x65\x6c\x6c\x6f\xd2\x71\x6f\x6f"; + + // parse test + let mut parser = Parser::from_ref(rdata.as_ref()); + let svcb = Svcb::parse(&mut parser).unwrap(); + assert_eq!(1, svcb.priority); + assert_eq!( + "foo.example.com".parse::>().unwrap(), + svcb.target + ); + + let mut param_iter = svcb.iter(); + let r = param_iter.next(); + match r { + Some(Ok(AllParams::Unknown(param))) => { + assert_eq!(0x029b, param.key()); + assert_eq!( + b"\x68\x65\x6c\x6c\x6f\xd2\x71\x6f\x6f".as_ref(), + *param.value(), + ); + } + _ => panic!("{:?}", r), + } + assert_eq!(None, param_iter.next()); + + // compose test + let mut svcb_builder = + Svcb::new(svcb.priority, svcb.target, Octets512::new()); + svcb_builder + .push( + param::Unknown::new( + 0x029b.into(), + b"\x68\x65\x6c\x6c\x6f\xd2\x71\x6f\x6f", + ) + .into(), + ) + .unwrap(); + let mut buf = Octets512::new(); + svcb_builder.freeze().compose(&mut buf).unwrap(); + assert_eq!(rdata.as_ref(), buf.as_ref()); + } + + #[test] + fn test_vectors_ipv6hint() { + let rdata = + b"\x00\x01\ + \x03\x66\x6f\x6f\x07\x65\x78\x61\x6d\x70\x6c\x65\x03\x63\x6f\x6d\x00\ + \x00\x06\ + \x00\x20\ + \x20\x01\x0d\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\ + \x20\x01\x0d\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x53\x00\x01"; + + // parse test + let mut parser = Parser::from_ref(rdata.as_ref()); + let svcb = Svcb::parse(&mut parser).unwrap(); + assert_eq!(1, svcb.priority); + assert_eq!( + "foo.example.com".parse::>().unwrap(), + svcb.target + ); + + let mut param_iter = svcb.iter(); + let r = param_iter.next(); + match r { + Some(Ok(AllParams::Ipv6Hint(param))) => { + let mut iter = param.iter(); + assert_eq!( + "2001:db8::1", + format!("{}", iter.next().unwrap().unwrap()), + ); + assert_eq!( + "2001:db8::53:1", + format!("{}", iter.next().unwrap().unwrap()), + ); + assert_eq!(None, iter.next()); + } + _ => panic!("{:?}", r), + } + assert_eq!(None, param_iter.next()); + + // compose test + let mut svcb_builder = + Svcb::new(svcb.priority, svcb.target, Octets512::new()); + let mut ipv6_hint_builder = param::Ipv6Hint::new(Octets512::new()); + ipv6_hint_builder + .push( + [ + 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + ] + .into(), + ) + .unwrap(); + ipv6_hint_builder + .push( + [ + 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x53, 0x00, 0x01, + ] + .into(), + ) + .unwrap(); + svcb_builder + .push(ipv6_hint_builder.freeze().into()) + .unwrap(); + let mut buf = Octets512::new(); + svcb_builder.freeze().compose(&mut buf).unwrap(); + assert_eq!(rdata.as_ref(), buf.as_ref()); + } + + #[test] + fn test_vectors_ipv6hint_v4mapped() { + let rdata = + b"\x00\x01\ + \x07\x65\x78\x61\x6d\x70\x6c\x65\x03\x63\x6f\x6d\x00\ + \x00\x06\ + \x00\x10\ + \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xc6\x33\x64\x64"; + + // parse test + let mut parser = Parser::from_ref(rdata.as_ref()); + let svcb = Svcb::parse(&mut parser).unwrap(); + assert_eq!(1, svcb.priority); + assert_eq!( + "example.com".parse::>().unwrap(), + svcb.target + ); + + let mut param_iter = svcb.iter(); + let r = param_iter.next(); + match r { + Some(Ok(AllParams::Ipv6Hint(param))) => { + let mut iter = param.iter(); + assert_eq!( + "::ffff:198.51.100.100", + format!("{}", iter.next().unwrap().unwrap()), + ); + assert_eq!(None, iter.next()); + } + _ => panic!("{:?}", r), + } + assert_eq!(None, param_iter.next()); + + // compose test + let mut svcb_builder = + Svcb::new(svcb.priority, svcb.target, Octets512::new()); + let mut ipv6_hint_builder = param::Ipv6Hint::new(Octets512::new()); + ipv6_hint_builder + .push( + [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xff, 0xff, 198, 51, 100, 100, + ] + .into(), + ) + .unwrap(); + svcb_builder + .push(ipv6_hint_builder.freeze().into()) + .unwrap(); + let mut buf = Octets512::new(); + svcb_builder.freeze().compose(&mut buf).unwrap(); + assert_eq!(rdata.as_ref(), buf.as_ref()); + } + + #[test] + fn test_vectors_key_sorting() { + let rdata = + b"\x00\x10\ + \x03\x66\x6f\x6f\x07\x65\x78\x61\x6d\x70\x6c\x65\x03\x6f\x72\x67\x00\ + \x00\x00\ + \x00\x04\ + \x00\x01\ + \x00\x04\ + \x00\x01\ + \x00\x09\ + \x02\ + \x68\x32\ + \x05\ + \x68\x33\x2d\x31\x39\ + \x00\x04\ + \x00\x04\ + \xc0\x00\x02\x01"; + + // parse test + let mut parser = Parser::from_ref(rdata.as_ref()); + let svcb = Svcb::parse(&mut parser).unwrap(); + assert_eq!(16, svcb.priority); + assert_eq!( + "foo.example.org".parse::>().unwrap(), + svcb.target + ); + + let mut param_iter = svcb.iter(); + let r = param_iter.next(); + match r { + Some(Ok(AllParams::Mandatory(keys))) => { + let mut iter = keys.iter(); + assert_eq!(Some(Ok(SvcbParamKey::Alpn)), iter.next()); + assert_eq!(Some(Ok(SvcbParamKey::Ipv4Hint)), iter.next()); + assert_eq!(None, iter.next()); + } + _ => panic!("{:?}", r), + } + + let r = param_iter.next(); + match r { + Some(Ok(AllParams::Alpn(names))) => { + let mut iter = names.iter(); + assert_eq!(Some(Ok("h2".as_bytes())), iter.next()); + assert_eq!(Some(Ok("h3-19".as_bytes())), iter.next()); + assert_eq!(None, iter.next()); + } + _ => panic!("{:?}", r), + } + + let r = param_iter.next(); + match r { + Some(Ok(AllParams::Ipv4Hint(hints))) => { + let mut iter = hints.iter(); + assert_eq!( + "192.0.2.1", + format!("{}", iter.next().unwrap().unwrap()), + ); + assert_eq!(None, iter.next()); + } + _ => panic!("{:?}", r), + } + assert_eq!(None, param_iter.next()); + + // compose test + let mut mandatory_builder = param::Mandatory::new(Octets512::new()); + mandatory_builder.push(SvcbParamKey::Alpn).unwrap(); + mandatory_builder.push(SvcbParamKey::Ipv4Hint).unwrap(); + let mandatory = mandatory_builder.freeze(); + + let mut alpn_builder = param::Alpn::new(Octets512::new()); + alpn_builder.push("h2").unwrap(); + alpn_builder.push("h3-19").unwrap(); + let alpn = alpn_builder.freeze(); + + let mut ipv4_hint_builder = param::Ipv4Hint::new(Octets512::new()); + ipv4_hint_builder.push([192, 0, 2, 1].into()).unwrap(); + let ipv4_hint = ipv4_hint_builder.freeze(); + + let mut svcb_builder = + Svcb::new(svcb.priority, svcb.target, Octets512::new()); + svcb_builder.push(mandatory.into()).unwrap(); + svcb_builder.push(alpn.into()).unwrap(); + svcb_builder.push(ipv4_hint.into()).unwrap(); + + let mut buf = Octets512::new(); + svcb_builder.freeze().compose(&mut buf).unwrap(); + assert_eq!(rdata.as_ref(), buf.as_ref()); + } + + #[test] + fn test_vectors_alpn_escape() { + let rdata = + b"\x00\x10\ + \x03\x66\x6f\x6f\x07\x65\x78\x61\x6d\x70\x6c\x65\x03\x6f\x72\x67\x00\ + \x00\x01\ + \x00\x0c\ + \x08\ + \x66\x5c\x6f\x6f\x2c\x62\x61\x72\ + \x02\ + \x68\x32"; + + // parse test + let mut parser = Parser::from_ref(rdata.as_ref()); + let svcb = Svcb::parse(&mut parser).unwrap(); + assert_eq!(16, svcb.priority); + assert_eq!( + "foo.example.org".parse::>().unwrap(), + svcb.target + ); + + let mut param_iter = svcb.iter(); + let r = param_iter.next(); + match r { + Some(Ok(AllParams::Alpn(names))) => { + let mut iter = names.iter(); + assert_eq!(Some(Ok(br#"f\oo,bar"#.as_ref())), iter.next()); + assert_eq!(Some(Ok("h2".as_bytes())), iter.next()); + assert_eq!(None, iter.next()); + } + _ => panic!("{:?}", r), + } + assert_eq!(None, param_iter.next()); + + // compose test + let mut svcb_builder = + Svcb::new(svcb.priority, svcb.target, Octets512::new()); + + let mut alpn_builder = param::Alpn::new(Octets512::new()); + alpn_builder.push(br#"f\oo,bar"#).unwrap(); + alpn_builder.push("h2").unwrap(); + let alpn = alpn_builder.freeze(); + + svcb_builder.push(alpn.into()).unwrap(); + let mut buf = Octets512::new(); + svcb_builder.freeze().compose(&mut buf).unwrap(); + assert_eq!(rdata.as_ref(), buf.as_ref()); + } + + #[test] + fn test_representation() { + use crate::base::iana::svcb::SVCB_PARAM_KEY_PRIVATE_RANGE_BEGIN; + + let mut mandatory_builder = param::Mandatory::new(Octets512::new()); + mandatory_builder.push(SvcbParamKey::Alpn).unwrap(); + mandatory_builder.push(SvcbParamKey::Ipv4Hint).unwrap(); + mandatory_builder + .push(SVCB_PARAM_KEY_PRIVATE_RANGE_BEGIN.into()) + .unwrap(); + let mandatory = mandatory_builder.freeze(); + + assert_eq!( + "mandatory=alpn,ipv4hint,key65280", + format!("{}", mandatory.for_slice()) + ); + + let mut alpn_builder = param::Alpn::new(Octets512::new()); + alpn_builder.push("h2").unwrap(); + alpn_builder.push("h3-19").unwrap(); + assert_eq!( + "alpn=h2,h3-19", + format!("{}", alpn_builder.freeze().for_slice()) + ); + + assert_eq!("nodefaultalpn", format!("{}", param::NoDefaultAlpn)); + + assert_eq!( + "ech", + format!( + "{}", + param::Ech::new(Octets512::new()).freeze().for_slice() + ) + ); + + let mut ipv4_hint_builder = param::Ipv4Hint::new(Octets512::new()); + ipv4_hint_builder.push([192, 0, 2, 1].into()).unwrap(); + ipv4_hint_builder.push([192, 0, 2, 2].into()).unwrap(); + let ipv4_hint = ipv4_hint_builder.freeze(); + assert_eq!( + "ipv4hint=192.0.2.1,192.0.2.2", + format!("{}", ipv4_hint.for_slice()) + ); + } + + #[test] + fn test_param_order() { + let mut mandatory_builder = param::Mandatory::new(Octets512::new()); + mandatory_builder.push(SvcbParamKey::Alpn).unwrap(); + mandatory_builder.push(SvcbParamKey::Ipv4Hint).unwrap(); + let mandatory = mandatory_builder.freeze(); + + let mut alpn_builder = param::Alpn::new(Octets512::new()); + alpn_builder.push("h2").unwrap(); + alpn_builder.push("h3-19").unwrap(); + let alpn = alpn_builder.freeze(); + + let mut ipv4_hint_builder = param::Ipv4Hint::new(Octets512::new()); + ipv4_hint_builder.push([192, 0, 2, 1].into()).unwrap(); + let ipv4_hint = ipv4_hint_builder.freeze(); + + let target: Dname = "example.com".parse().unwrap(); + let mut svcb_builder = Svcb::new(1, target, Octets512::new()); + + // params are pushed out of order + svcb_builder.push(ipv4_hint.into()).unwrap(); + svcb_builder.push(alpn.into()).unwrap(); + svcb_builder.push(mandatory.into()).unwrap(); + let svcb = svcb_builder.freeze(); + + // unchecked compose + let mut buf = Octets512::new(); + svcb.compose_unchecked(&mut buf).unwrap(); + let mut parser = Parser::from_ref(buf.as_ref()); + let parsed_svcb = Svcb::parse(&mut parser).unwrap(); + let mut iter = parsed_svcb.iter(); + assert_eq!( + SvcbParamKey::Ipv4Hint, + iter.next().unwrap().unwrap().key() + ); + assert_eq!(SvcbParamKey::Alpn, iter.next().unwrap().unwrap().key()); + assert_eq!( + SvcbParamKey::Mandatory, + iter.next().unwrap().unwrap().key() + ); + + // checked compose + let mut buf = Octets512::new(); + svcb.compose(&mut buf).unwrap(); + let mut parser = Parser::from_ref(buf.as_ref()); + let parsed_svcb = Svcb::parse(&mut parser).unwrap(); + let mut iter = parsed_svcb.iter(); + assert_eq!( + SvcbParamKey::Mandatory, + iter.next().unwrap().unwrap().key() + ); + assert_eq!(SvcbParamKey::Alpn, iter.next().unwrap().unwrap().key()); + assert_eq!( + SvcbParamKey::Ipv4Hint, + iter.next().unwrap().unwrap().key() + ); + } +}