#[macro_use]
mod macros;
pub mod algsig;
pub mod chain;
pub mod cookie;
pub mod expire;
pub mod exterr;
pub mod keepalive;
pub mod keytag;
pub mod nsid;
pub mod padding;
pub mod subnet;
opt_types! {
algsig::{Dau<Octs>, Dhu<Octs>, N3u<Octs>};
chain::{Chain<Name>};
cookie::{Cookie};
expire::{Expire};
exterr::{ExtendedError<Octs>};
keepalive::{TcpKeepalive};
keytag::{KeyTag<Octs>};
nsid::{Nsid<Octs>};
padding::{Padding<Octs>};
subnet::{ClientSubnet};
}
use super::cmp::CanonicalOrd;
use super::header::Header;
use super::iana::{Class, OptRcode, OptionCode, Rtype};
use super::name::{Name, ToName};
use super::rdata::{ComposeRecordData, ParseRecordData, RecordData};
use super::record::{Record, Ttl};
use super::wire::{Compose, Composer, FormError, ParseError};
use super::zonefile_fmt::{self, Formatter, ZonefileFmt};
use crate::utils::base16;
use core::cmp::Ordering;
use core::marker::PhantomData;
use core::{fmt, hash, mem};
use octseq::builder::{EmptyBuilder, OctetsBuilder, ShortBuf};
use octseq::octets::{Octets, OctetsFrom};
use octseq::parse::Parser;
#[derive(Clone)]
#[repr(transparent)]
pub struct Opt<Octs: ?Sized> {
octets: Octs,
}
#[cfg(feature = "serde")]
impl<O: AsRef<[u8]>> serde::Serialize for Opt<O> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeSeq;
let mut list = serializer.serialize_seq(None)?;
for rec in self.for_slice_ref().iter::<AllOptData<_, _>>() {
let Ok(rec) = rec else {
continue;
};
list.serialize_element(&rec)?;
}
list.end()
}
}
impl Opt<()> {
pub(crate) const RTYPE: Rtype = Rtype::OPT;
}
impl<Octs: EmptyBuilder> Opt<Octs> {
pub fn empty() -> Self {
Self {
octets: Octs::empty(),
}
}
}
impl<Octs: AsRef<[u8]>> Opt<Octs> {
pub fn from_octets(octets: Octs) -> Result<Self, ParseError> {
Opt::check_slice(octets.as_ref())?;
Ok(unsafe { Self::from_octets_unchecked(octets) })
}
unsafe fn from_octets_unchecked(octets: Octs) -> Self {
Self { octets }
}
pub fn parse<'a, Src: Octets<Range<'a> = Octs> + ?Sized>(
parser: &mut Parser<'a, Src>,
) -> Result<Self, ParseError> {
let len = parser.remaining();
Self::from_octets(parser.parse_octets(len)?)
}
}
impl Opt<[u8]> {
pub fn from_slice(slice: &[u8]) -> Result<&Self, ParseError> {
Self::check_slice(slice)?;
Ok(unsafe { Self::from_slice_unchecked(slice) })
}
unsafe fn from_slice_unchecked(slice: &[u8]) -> &Self {
mem::transmute(slice)
}
fn check_slice(slice: &[u8]) -> Result<(), ParseError> {
if slice.len() > usize::from(u16::MAX) {
return Err(FormError::new("long record data").into());
}
let mut parser = Parser::from_ref(slice);
while parser.remaining() > 0 {
parser.advance(2)?;
let len = parser.parse_u16_be()?;
parser.advance(len as usize)?;
}
Ok(())
}
}
impl<Octs: AsRef<[u8]> + ?Sized> Opt<Octs> {
pub fn for_slice_ref(&self) -> Opt<&[u8]> {
unsafe { Opt::from_octets_unchecked(self.octets.as_ref()) }
}
}
impl<Octs: AsRef<[u8]> + ?Sized> Opt<Octs> {
pub fn len(&self) -> usize {
self.octets.as_ref().len()
}
pub fn is_empty(&self) -> bool {
self.octets.as_ref().is_empty()
}
pub fn iter<'s, Data>(&'s self) -> OptIter<'s, Octs, Data>
where
Octs: Octets,
Data: ParseOptData<'s, Octs>,
{
OptIter::new(&self.octets)
}
pub fn first<'s, Data>(&'s self) -> Option<Data>
where
Octs: Octets,
Data: ParseOptData<'s, Octs>,
{
self.iter::<Data>().next()?.ok()
}
}
impl<Octs: Composer> Opt<Octs> {
pub fn push<Opt: ComposeOptData + ?Sized>(
&mut self,
option: &Opt,
) -> Result<(), BuildDataError> {
self.push_raw_option(option.code(), option.compose_len(), |target| {
option.compose_option(target)
})
}
pub fn push_raw_option<F>(
&mut self,
code: OptionCode,
option_len: u16,
op: F,
) -> Result<(), BuildDataError>
where
F: FnOnce(&mut Octs) -> Result<(), Octs::AppendError>,
{
LongOptData::check_len(
self.octets
.as_ref()
.len()
.saturating_add(usize::from(option_len)),
)?;
code.compose(&mut self.octets)?;
option_len.compose(&mut self.octets)?;
op(&mut self.octets)?;
Ok(())
}
}
impl<Octs, SrcOcts> OctetsFrom<Opt<SrcOcts>> for Opt<Octs>
where
Octs: OctetsFrom<SrcOcts>,
{
type Error = Octs::Error;
fn try_octets_from(source: Opt<SrcOcts>) -> Result<Self, Self::Error> {
Octs::try_octets_from(source.octets).map(|octets| Opt { octets })
}
}
impl<Octs, Other> PartialEq<Opt<Other>> for Opt<Octs>
where
Octs: AsRef<[u8]> + ?Sized,
Other: AsRef<[u8]> + ?Sized,
{
fn eq(&self, other: &Opt<Other>) -> bool {
self.octets.as_ref().eq(other.octets.as_ref())
}
}
impl<Octs: AsRef<[u8]> + ?Sized> Eq for Opt<Octs> {}
impl<Octs, Other> PartialOrd<Opt<Other>> for Opt<Octs>
where
Octs: AsRef<[u8]> + ?Sized,
Other: AsRef<[u8]> + ?Sized,
{
fn partial_cmp(&self, other: &Opt<Other>) -> Option<Ordering> {
self.octets.as_ref().partial_cmp(other.octets.as_ref())
}
}
impl<Octs: AsRef<[u8]> + ?Sized> Ord for Opt<Octs> {
fn cmp(&self, other: &Self) -> Ordering {
self.octets.as_ref().cmp(other.octets.as_ref())
}
}
impl<Octs, Other> CanonicalOrd<Opt<Other>> for Opt<Octs>
where
Octs: AsRef<[u8]> + ?Sized,
Other: AsRef<[u8]> + ?Sized,
{
fn canonical_cmp(&self, other: &Opt<Other>) -> Ordering {
self.octets.as_ref().cmp(other.octets.as_ref())
}
}
impl<Octs: AsRef<[u8]> + ?Sized> hash::Hash for Opt<Octs> {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.octets.as_ref().hash(state)
}
}
impl<Octs: ?Sized> RecordData for Opt<Octs> {
fn rtype(&self) -> Rtype {
Rtype::OPT
}
}
impl<'a, Octs> ParseRecordData<'a, Octs> for Opt<Octs::Range<'a>>
where
Octs: Octets + ?Sized,
{
fn parse_rdata(
rtype: Rtype,
parser: &mut Parser<'a, Octs>,
) -> Result<Option<Self>, ParseError> {
if rtype == Rtype::OPT {
Self::parse(parser).map(Some)
} else {
Ok(None)
}
}
}
impl<Octs: AsRef<[u8]> + ?Sized> ComposeRecordData for Opt<Octs> {
fn rdlen(&self, _compress: bool) -> Option<u16> {
Some(u16::try_from(self.octets.as_ref().len()).expect("long OPT"))
}
fn compose_rdata<Target: Composer + ?Sized>(
&self,
target: &mut Target,
) -> Result<(), Target::AppendError> {
target.append_slice(self.octets.as_ref())
}
fn compose_canonical_rdata<Target: Composer + ?Sized>(
&self,
target: &mut Target,
) -> Result<(), Target::AppendError> {
self.compose_rdata(target)
}
}
impl<Octs: AsRef<[u8]> + ?Sized> fmt::Display for Opt<Octs> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("OPT ...")
}
}
impl<Octs: AsRef<[u8]> + ?Sized> fmt::Debug for Opt<Octs> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Opt(")?;
fmt::Display::fmt(self, f)?;
f.write_str(")")
}
}
impl<Octs: AsRef<[u8]> + ?Sized> ZonefileFmt for Opt<Octs> {
fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result {
p.write_token("OPT ...")
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[repr(transparent)]
pub struct OptHeader {
inner: [u8; 9],
}
impl OptHeader {
#[must_use]
pub fn for_record_slice(slice: &[u8]) -> &OptHeader {
assert!(slice.len() >= mem::size_of::<Self>());
unsafe { &*(slice.as_ptr() as *const OptHeader) }
}
pub fn for_record_slice_mut(slice: &mut [u8]) -> &mut OptHeader {
assert!(slice.len() >= mem::size_of::<Self>());
unsafe { &mut *(slice.as_mut_ptr() as *mut OptHeader) }
}
#[must_use]
pub fn udp_payload_size(&self) -> u16 {
u16::from_be_bytes(self.inner[3..5].try_into().unwrap())
}
pub fn set_udp_payload_size(&mut self, value: u16) {
self.inner[3..5].copy_from_slice(&value.to_be_bytes())
}
#[must_use]
pub fn rcode(&self, header: Header) -> OptRcode {
OptRcode::from_parts(header.rcode(), self.inner[5])
}
pub fn set_rcode(&mut self, rcode: OptRcode) {
self.inner[5] = rcode.ext()
}
#[must_use]
pub fn version(&self) -> u8 {
self.inner[6]
}
pub fn set_version(&mut self, version: u8) {
self.inner[6] = version
}
#[must_use]
pub fn dnssec_ok(&self) -> bool {
self.inner[7] & 0x80 != 0
}
pub fn set_dnssec_ok(&mut self, value: bool) {
if value {
self.inner[7] |= 0x80
} else {
self.inner[7] &= 0x7F
}
}
pub fn compose<Target: OctetsBuilder + ?Sized>(
self,
target: &mut Target,
) -> Result<(), Target::AppendError> {
target.append_slice(&self.inner)
}
}
impl Default for OptHeader {
fn default() -> Self {
OptHeader {
inner: [0, 0, 41, 0, 0, 0, 0, 0, 0],
}
}
}
#[derive(Clone)]
pub struct OptRecord<Octs> {
udp_payload_size: u16,
ext_rcode: u8,
version: u8,
flags: u16,
data: Opt<Octs>,
}
impl<Octs> OptRecord<Octs> {
pub fn from_record<N: ToName>(record: Record<N, Opt<Octs>>) -> Self {
OptRecord {
udp_payload_size: record.class().to_int(),
ext_rcode: (record.ttl().as_secs() >> 24) as u8,
version: (record.ttl().as_secs() >> 16) as u8,
flags: record.ttl().as_secs() as u16,
data: record.into_data(),
}
}
pub fn as_record(&self) -> Record<&'static Name<[u8]>, Opt<&[u8]>>
where
Octs: AsRef<[u8]>,
{
Record::new(
Name::root_slice(),
Class::from_int(self.udp_payload_size),
Ttl::from_secs(
(u32::from(self.ext_rcode) << 24)
| (u32::from(self.version) << 16)
| u32::from(self.flags),
),
self.data.for_slice_ref(),
)
}
pub fn udp_payload_size(&self) -> u16 {
self.udp_payload_size
}
pub fn set_udp_payload_size(&mut self, value: u16) {
self.udp_payload_size = value
}
pub fn rcode(&self, header: Header) -> OptRcode {
OptRcode::from_parts(header.rcode(), self.ext_rcode)
}
pub fn version(&self) -> u8 {
self.version
}
pub fn dnssec_ok(&self) -> bool {
self.flags & 0x8000 != 0
}
pub fn set_dnssec_ok(&mut self, value: bool) {
if value {
self.flags |= 0x8000;
} else {
self.flags &= !0x8000;
}
}
pub fn opt(&self) -> &Opt<Octs> {
&self.data
}
}
impl<Octs: Composer> OptRecord<Octs> {
pub fn push<Opt: ComposeOptData + ?Sized>(
&mut self,
option: &Opt,
) -> Result<(), BuildDataError> {
self.data.push(option)
}
pub fn push_raw_option<F>(
&mut self,
code: OptionCode,
option_len: u16,
op: F,
) -> Result<(), BuildDataError>
where
F: FnOnce(&mut Octs) -> Result<(), Octs::AppendError>,
{
self.data.push_raw_option(code, option_len, op)
}
}
impl<Octs: EmptyBuilder> Default for OptRecord<Octs> {
fn default() -> Self {
Self {
udp_payload_size: 0,
ext_rcode: 0,
version: 0,
flags: 0,
data: Opt::empty(),
}
}
}
impl<Octs, N: ToName> From<Record<N, Opt<Octs>>> for OptRecord<Octs> {
fn from(record: Record<N, Opt<Octs>>) -> Self {
Self::from_record(record)
}
}
impl<Octs, SrcOcts> OctetsFrom<OptRecord<SrcOcts>> for OptRecord<Octs>
where
Octs: OctetsFrom<SrcOcts>,
{
type Error = Octs::Error;
fn try_octets_from(
source: OptRecord<SrcOcts>,
) -> Result<Self, Self::Error> {
Ok(OptRecord {
udp_payload_size: source.udp_payload_size,
ext_rcode: source.ext_rcode,
version: source.version,
flags: source.flags,
data: Opt::try_octets_from(source.data)?,
})
}
}
impl<Octs> AsRef<Opt<Octs>> for OptRecord<Octs> {
fn as_ref(&self) -> &Opt<Octs> {
&self.data
}
}
impl<Octs: AsRef<[u8]>> fmt::Debug for OptRecord<Octs> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("OptRecord")
.field("udp_payload_size", &self.udp_payload_size)
.field("ext_rcord", &self.ext_rcode)
.field("version", &self.version)
.field("flags", &self.flags)
.field("data", &self.data)
.finish()
}
}
#[derive(Clone, Copy, Debug)]
pub struct OptionHeader {
code: u16,
len: u16,
}
#[allow(clippy::len_without_is_empty)]
impl OptionHeader {
#[must_use]
pub fn new(code: u16, len: u16) -> Self {
OptionHeader { code, len }
}
#[must_use]
pub fn code(self) -> u16 {
self.code
}
#[must_use]
pub fn len(self) -> u16 {
self.len
}
pub fn parse<Octs: AsRef<[u8]> + ?Sized>(
parser: &mut Parser<'_, Octs>,
) -> Result<Self, ParseError> {
Ok(OptionHeader::new(
parser.parse_u16_be()?,
parser.parse_u16_be()?,
))
}
}
#[derive(Clone, Debug)]
pub struct OptIter<'a, Octs: ?Sized, D> {
parser: Parser<'a, Octs>,
marker: PhantomData<D>,
}
impl<'a, Octs, D> OptIter<'a, Octs, D>
where
Octs: Octets + ?Sized,
D: ParseOptData<'a, Octs>,
{
fn new(octets: &'a Octs) -> Self {
OptIter {
parser: Parser::from_ref(octets),
marker: PhantomData,
}
}
fn next_step(&mut self) -> Result<Option<D>, ParseError> {
let code = self.parser.parse_u16_be()?.into();
let len = self.parser.parse_u16_be()? as usize;
let mut parser = self.parser.parse_parser(len)?;
let res = D::parse_option(code, &mut parser)?;
if res.is_some() && parser.remaining() > 0 {
return Err(ParseError::Form(FormError::new(
"trailing data in option",
)));
}
Ok(res)
}
}
impl<'a, Octs, Data> Iterator for OptIter<'a, Octs, Data>
where
Octs: Octets + ?Sized,
Data: ParseOptData<'a, Octs>,
{
type Item = Result<Data, ParseError>;
fn next(&mut self) -> Option<Self::Item> {
while self.parser.remaining() > 0 {
match self.next_step() {
Ok(Some(res)) => return Some(Ok(res)),
Ok(None) => {}
Err(err) => {
self.parser.advance_to_end();
return Some(Err(err));
}
}
}
None
}
}
pub trait OptData {
fn code(&self) -> OptionCode;
}
pub trait ParseOptData<'a, Octs: ?Sized>: OptData + Sized {
fn parse_option(
code: OptionCode,
parser: &mut Parser<'a, Octs>,
) -> Result<Option<Self>, ParseError>;
}
pub trait ComposeOptData: OptData {
fn compose_len(&self) -> u16;
fn compose_option<Target: OctetsBuilder + ?Sized>(
&self,
target: &mut Target,
) -> Result<(), Target::AppendError>;
}
#[derive(Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct UnknownOptData<Octs> {
code: OptionCode,
#[cfg_attr(
feature = "serde",
serde(
serialize_with = "crate::utils::base16::serde::serialize",
bound(
serialize = "Octs: AsRef<[u8]> + octseq::serde::SerializeOctets",
)
)
)]
data: Octs,
}
impl<Octs> UnknownOptData<Octs> {
pub fn new(code: OptionCode, data: Octs) -> Result<Self, LongOptData>
where
Octs: AsRef<[u8]>,
{
LongOptData::check_len(data.as_ref().len())?;
Ok(unsafe { Self::new_unchecked(code, data) })
}
pub unsafe fn new_unchecked(code: OptionCode, data: Octs) -> Self {
Self { code, data }
}
pub fn code(&self) -> OptionCode {
self.code
}
pub fn data(&self) -> &Octs {
&self.data
}
pub fn as_slice(&self) -> &[u8]
where
Octs: AsRef<[u8]>,
{
self.data.as_ref()
}
pub fn as_slice_mut(&mut self) -> &mut [u8]
where
Octs: AsMut<[u8]>,
{
self.data.as_mut()
}
}
impl<Octs, SrcOcts> OctetsFrom<UnknownOptData<SrcOcts>>
for UnknownOptData<Octs>
where
Octs: OctetsFrom<SrcOcts>,
{
type Error = Octs::Error;
fn try_octets_from(
src: UnknownOptData<SrcOcts>,
) -> Result<Self, Self::Error> {
Ok(unsafe {
Self::new_unchecked(src.code, Octs::try_octets_from(src.data)?)
})
}
}
impl<Octs> AsRef<Octs> for UnknownOptData<Octs> {
fn as_ref(&self) -> &Octs {
self.data()
}
}
impl<Octs: AsRef<[u8]>> AsRef<[u8]> for UnknownOptData<Octs> {
fn as_ref(&self) -> &[u8] {
self.as_slice()
}
}
impl<Octs: AsMut<[u8]>> AsMut<[u8]> for UnknownOptData<Octs> {
fn as_mut(&mut self) -> &mut [u8] {
self.as_slice_mut()
}
}
impl<Octs: AsRef<[u8]>> OptData for UnknownOptData<Octs> {
fn code(&self) -> OptionCode {
self.code
}
}
impl<'a, Octs> ParseOptData<'a, Octs> for UnknownOptData<Octs::Range<'a>>
where
Octs: Octets + ?Sized,
{
fn parse_option(
code: OptionCode,
parser: &mut Parser<'a, Octs>,
) -> Result<Option<Self>, ParseError> {
Self::new(code, parser.parse_octets(parser.remaining())?)
.map(Some)
.map_err(Into::into)
}
}
impl<Octs: AsRef<[u8]>> ComposeOptData for UnknownOptData<Octs> {
fn compose_len(&self) -> u16 {
self.data
.as_ref()
.len()
.try_into()
.expect("long option data")
}
fn compose_option<Target: OctetsBuilder + ?Sized>(
&self,
target: &mut Target,
) -> Result<(), Target::AppendError> {
target.append_slice(self.data.as_ref())
}
}
impl<Octs: AsRef<[u8]>> fmt::Display for UnknownOptData<Octs> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
base16::display(self.data.as_ref(), f)
}
}
impl<Octs: AsRef<[u8]>> fmt::Debug for UnknownOptData<Octs> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("UnknownOptData")
.field("code", &self.code)
.field("data", &format_args!("{}", self))
.finish()
}
}
#[derive(Clone, Copy, Debug)]
pub struct LongOptData(());
impl LongOptData {
#[must_use]
pub fn as_str(self) -> &'static str {
"option data too long"
}
pub fn check_len(len: usize) -> Result<(), Self> {
if len > usize::from(u16::MAX) {
Err(Self(()))
} else {
Ok(())
}
}
}
impl From<LongOptData> for ParseError {
fn from(src: LongOptData) -> Self {
ParseError::form_error(src.as_str())
}
}
impl fmt::Display for LongOptData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[cfg(feature = "std")]
impl std::error::Error for LongOptData {}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum BuildDataError {
LongOptData,
ShortBuf,
}
impl BuildDataError {
pub fn unlimited_buf(self) -> LongOptData {
match self {
Self::LongOptData => LongOptData(()),
Self::ShortBuf => panic!("ShortBuf on unlimited buffer"),
}
}
}
impl From<LongOptData> for BuildDataError {
fn from(_: LongOptData) -> Self {
Self::LongOptData
}
}
impl<T: Into<ShortBuf>> From<T> for BuildDataError {
fn from(_: T) -> Self {
Self::ShortBuf
}
}
impl fmt::Display for BuildDataError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::LongOptData => f.write_str("long option data"),
Self::ShortBuf => ShortBuf.fmt(f),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for BuildDataError {}
#[cfg(test)]
#[cfg(all(feature = "std", feature = "bytes"))]
pub(super) mod test {
use super::*;
use crate::base::rdata::test::{test_compose_parse, test_rdlen};
use crate::base::record::ParsedRecord;
use crate::base::{opt, MessageBuilder};
use bytes::{Bytes, BytesMut};
use core::fmt::Debug;
use octseq::builder::infallible;
use std::vec::Vec;
#[test]
#[allow(clippy::redundant_closure)] fn opt_compose_parse_scan() {
let rdata = Opt::from_octets("fo\x00\x03foo").unwrap();
test_rdlen(&rdata);
test_compose_parse(&rdata, |parser| Opt::parse(parser));
}
#[test]
fn opt_record_header() {
let mut header = OptHeader::default();
header.set_udp_payload_size(0x1234);
header.set_rcode(OptRcode::BADVERS);
header.set_version(0xbd);
header.set_dnssec_ok(true);
let mut buf = Vec::with_capacity(11);
infallible(header.compose(&mut buf));
infallible(0u16.compose(&mut buf));
let mut buf = Parser::from_ref(buf.as_slice());
let record = ParsedRecord::parse(&mut buf)
.unwrap()
.into_record::<Opt<_>>()
.unwrap()
.unwrap();
let record = OptRecord::from_record(record);
assert_eq!(record.udp_payload_size(), 0x1234);
assert_eq!(record.ext_rcode, OptRcode::BADVERS.ext());
assert_eq!(record.version(), 0xbd);
assert!(record.dnssec_ok());
}
#[test]
fn opt_iter() {
use self::opt::cookie::{ClientCookie, Cookie};
let nsid = opt::Nsid::from_octets(&b"example"[..]).unwrap();
let cookie = Cookie::new(
ClientCookie::from_octets(1234u64.to_be_bytes()),
None,
);
let msg = {
let mut mb = MessageBuilder::new_vec().additional();
mb.opt(|mb| {
mb.push(&nsid)?;
mb.push(&cookie)?;
Ok(())
})
.unwrap();
mb.into_message()
};
let opt = msg.opt().unwrap();
assert_eq!(Some(Ok(nsid)), opt.opt().iter::<opt::Nsid<_>>().next());
assert_eq!(Some(Ok(cookie)), opt.opt().iter::<opt::Cookie>().next());
}
pub fn test_option_compose_parse<In, F, Out>(data: &In, parse: F)
where
In: ComposeOptData + PartialEq<Out> + Debug,
F: FnOnce(&mut Parser<'_, Bytes>) -> Result<Out, ParseError>,
Out: Debug,
{
let mut buf = BytesMut::new();
infallible(data.compose_option(&mut buf));
let buf = buf.freeze();
assert_eq!(buf.len(), usize::from(data.compose_len()));
let mut parser = Parser::from_ref(&buf);
let parsed = (parse)(&mut parser).unwrap();
assert_eq!(parser.remaining(), 0);
assert_eq!(*data, parsed);
}
}