//! Record data types.
//!
//! ## Containers for record data
//!
//! When you need data for a particular record type, you can use the matching
//! concrete type for it. Otherwise, the record data can be held in one of
//! the following types:
//!
//! - [`RecordData`] is useful for short-term usage, e.g. when manipulating a
//!   DNS message or parsing into a custom representation. It can be parsed
//!   from the wire format very efficiently.
//!
#![cfg_attr(feature = "alloc", doc = " - [`BoxedRecordData`] ")]
#![cfg_attr(not(feature = "alloc"), doc = " - `BoxedRecordData` ")]
//!   is useful for long-term storage. For long-term storage of a whole DNS
//!   zone, it's more advisable to use the "zone tree" types provided by this
//!   crate.
//!
//! - [`UnparsedRecordData`] is a niche type, useful for low-level
//!   manipulation of the DNS wire format. Beware that it can contain
//!   unresolved name compression pointers.
//!
//! - [`UnknownRecordData`] can be used to represent data types that aren't
//!   supported yet. It functions similarly to [`UnparsedRecordData`], but
//!   it can't be used for many "basic" record data types, like [`Soa`] and
//!   [`Mx`]. These types come with many special cases that
//!   [`UnknownRecordData`] doesn't try to account for.
//!
//! [`UnparsedRecordData`]: crate::new::base::UnparsedRecordData
//!
//! ## Supported data types
//!
//! The following record data types are supported. They are enumerated by
//! [`RecordData`], which can store any one of them at a time.
//!
//! Core types:
//! - [`Soa`]
//! - [`Ns`]
//!
//! Basic data types:
//! - [`A`]
//! - [`Aaaa`]
//! - [`Mx`]
//! - [`Txt`]
//! - [`Rp`]
//! - [`Srv`]
//!
//! Indirection types:
//! - [`CName`]
//! - [`Ptr`]
//! - [`DName`]
//!
//! Security related types:
//! - [`DNSKey`]
//! - [`Rrsig`]
//! - [`Nsec`]
//! - [`Nsec3`]
//! - [`Nsec3Param`]
//! - [`Ds`]
//! - [`ZoneMD`]
//!
//! Miscellaneous types:
//! - [`HInfo`]
//!
//! "Pseudo-RR" types:
//! - [`Opt`]

#![deny(missing_docs)]
#![deny(clippy::missing_docs_in_private_items)]

use core::cmp::Ordering;

#[cfg(feature = "alloc")]
use core::{
    fmt,
    hash::{Hash, Hasher},
};

#[cfg(feature = "alloc")]
use alloc::boxed::Box;

#[cfg(feature = "alloc")]
use alloc::vec;

use crate::{
    new::base::{
        CanonicalRecordData, ParseRecordData, ParseRecordDataBytes, RType,
        build::{BuildInMessage, NameCompressor},
        name::CanonicalName,
        parse::{ParseMessageBytes, SplitMessageBytes},
        wire::{
            AsBytes, BuildBytes, ParseBytes, ParseError, SplitBytes,
            TruncationError,
        },
    },
    utils::dst::UnsizedCopy,
};

#[cfg(feature = "alloc")]
use crate::new::base::name::{Name, NameBuf};

//----------- Concrete record data types -------------------------------------

mod basic;
pub use basic::{A, CName, HInfo, Mx, Ns, Ptr, Soa, Txt};

mod dname;
pub use dname::DName;

mod ipv6;
pub use ipv6::Aaaa;

mod edns;
pub use edns::{EdnsOptionsIter, Opt};

mod rp;
pub use rp::Rp;

mod srv;
pub use srv::Srv;

mod dnssec;
pub use dnssec::{
    DNSKey, DNSKeyFlags, DigestType, Ds, Nsec, Nsec3, Nsec3Flags,
    Nsec3HashAlgorithm, Nsec3Param, Rrsig, SecAlg, TypeBitmaps,
};

mod zonemd;
pub use zonemd::{ZoneMD, ZoneMDHashAlg, ZoneMDScheme};

use super::base::wire::ParseBytesZC;

//----------- RecordData -----------------------------------------------------

/// A helper macro to handle boilerplate in defining [`RecordData`].
macro_rules! define_record_data {
    {
        $(#[$attr:meta])*
        $vis:vis enum $name:ident<$a:lifetime, $N:ident> {
            $(
                $(#[$v_attr:meta])*
                $v_name:ident ($v_type:ty) = $v_disc:ident
            ),*;

            $(#[$u_attr:meta])*
            Unknown(RType, $u_type:ty)
        }
    } => {
        // The primary type definition.
        $(#[$attr])*
        $vis enum $name<$a, $N> {
            $($(#[$v_attr])* $v_name ($v_type),)*
            $(#[$u_attr])* Unknown(RType, $u_type),
        }

        //--- Inspection

        impl<$N> $name<'_, $N> {
            /// The record data type.
            pub const fn rtype(&self) -> RType {
                match *self {
                    $(Self::$v_name(_) => RType::$v_disc,)*
                    Self::Unknown(rtype, _) => rtype,
                }
            }
        }

        //--- Conversion from concrete types

        $(impl<$a, $N> From<$v_type> for $name<$a, $N> {
            fn from(value: $v_type) -> Self {
                Self::$v_name(value)
            }
        })*

        //--- Canonical operations

        impl<$N: CanonicalName> CanonicalRecordData for $name<'_, $N> {
            fn build_canonical_bytes<'b>(
                &self,
                bytes: &'b mut [u8],
            ) -> Result<&'b mut [u8], TruncationError> {
                match self {
                    $(Self::$v_name(r) => r.build_canonical_bytes(bytes),)*
                    Self::Unknown(_, rd) => rd.build_canonical_bytes(bytes),
                }
            }

            fn cmp_canonical(&self, other: &Self) -> Ordering {
                if self.rtype() != other.rtype() {
                    return self.rtype().cmp(&other.rtype());
                }

                match (self, other) {
                    $((Self::$v_name(l), Self::$v_name(r))
                        => l.cmp_canonical(r),)*
                    (Self::Unknown(_, l), Self::Unknown(_, r))
                        => l.cmp_canonical(r),
                    _ => unreachable!("'self' and 'other' had the same rtype but were different enum variants"),
                }
            }
        }

        //--- Parsing record data

        impl<$a, $N: SplitBytes<$a>> ParseRecordDataBytes<$a> for $name<$a, $N> {
            fn parse_record_data_bytes(
                bytes: &$a [u8],
                rtype: RType,
            ) -> Result<Self, ParseError> {
                Ok(match rtype {
                    $(RType::$v_disc => Self::$v_name(ParseBytes::parse_bytes(bytes)?),)*
                    _ => Self::Unknown(rtype, ParseBytes::parse_bytes(bytes)?),
                })
            }
        }

        //--- Building record data

        impl<$N: BuildInMessage> BuildInMessage for $name<'_, $N> {
            fn build_in_message(
                &self,
                contents: &mut [u8],
                start: usize,
                name: &mut NameCompressor,
            ) -> Result<usize, TruncationError> {
                match *self {
                    $(Self::$v_name(ref r) => r.build_in_message(contents, start, name),)*
                    Self::Unknown(_, r) => r.build_in_message(contents, start, name),
                }
            }
        }

        impl<$N: BuildBytes> BuildBytes for $name<'_, $N> {
            fn build_bytes<'b>(
                &self,
                bytes: &'b mut [u8],
            ) -> Result<&'b mut [u8], TruncationError> {
                match self {
                    $(Self::$v_name(r) => r.build_bytes(bytes),)*
                    Self::Unknown(_, r) => r.build_bytes(bytes),
                }
            }

            fn built_bytes_size(&self) -> usize {
                match self {
                    $(Self::$v_name(r) => r.built_bytes_size(),)*
                    Self::Unknown(_, r) => r.built_bytes_size(),
                }
            }
        }
    };
}

define_record_data! {
    /// DNS record data.
    #[derive(Clone, Debug, PartialEq, Eq, Hash)]
    #[non_exhaustive]
    pub enum RecordData<'a, N> {
        /// The IPv4 address of a host responsible for this domain.
        A(A) = A,

        /// The authoritative name server for this domain.
        Ns(Ns<N>) = NS,

        /// The canonical name for this domain.
        CName(CName<N>) = CNAME,

        /// The start of a zone of authority.
        Soa(Soa<N>) = SOA,

        /// A pointer to another domain name.
        Ptr(Ptr<N>) = PTR,

        /// Information about the host computer.
        HInfo(HInfo<'a>) = HINFO,

        /// A host that can exchange mail for this domain.
        Mx(Mx<N>) = MX,

        /// Free-form text strings about this domain.
        Txt(&'a Txt) = TXT,

        /// Identification of the person/party responsible for this domain.
        Rp(Rp<N>) = RP,

        /// The locations of services associated with this domain.
        Srv(&'a Srv) = SRV,

        /// The IPv6 address of a host responsible for this domain.
        Aaaa(Aaaa) = AAAA,

        /// Redirection for the descendants of this domain.
        DName(&'a DName) = DNAME,

        /// Extended DNS options.
        Opt(&'a Opt) = OPT,

        /// The signing key of a delegated zone.
        Ds(&'a Ds) = DS,

        /// A cryptographic signature on a DNS record set.
        Rrsig(Rrsig<'a>) = RRSIG,

        /// An indication of the non-existence of a set of DNS records (version 1).
        Nsec(Nsec<'a>) = NSEC,

        /// A cryptographic key for DNS security.
        DNSKey(&'a DNSKey) = DNSKEY,

        /// An indication of the non-existence of a set of DNS records (version 3).
        Nsec3(Nsec3<'a>) = NSEC3,

        /// Parameters for computing [`Nsec3`] records.
        Nsec3Param(&'a Nsec3Param) = NSEC3PARAM,

        /// A message digest of the enclosing zone.
        ZoneMD(&'a ZoneMD) = ZONEMD;

        /// Data for an unknown DNS record type.
        Unknown(RType, &'a UnknownRecordData)
    }
}

//--- Interaction

impl<'a, N> RecordData<'a, N> {
    /// Map the domain names within to another type.
    pub fn map_names<R, F: FnMut(N) -> R>(self, f: F) -> RecordData<'a, R> {
        match self {
            Self::A(r) => RecordData::A(r),
            Self::Ns(r) => RecordData::Ns(r.map_name(f)),
            Self::CName(r) => RecordData::CName(r.map_name(f)),
            Self::Soa(r) => RecordData::Soa(r.map_names(f)),
            Self::Ptr(r) => RecordData::Ptr(r.map_name(f)),
            Self::HInfo(r) => RecordData::HInfo(r),
            Self::Mx(r) => RecordData::Mx(r.map_name(f)),
            Self::Txt(r) => RecordData::Txt(r),
            Self::Rp(r) => RecordData::Rp(r.map_names(f)),
            Self::Aaaa(r) => RecordData::Aaaa(r),
            Self::Srv(r) => RecordData::Srv(r),
            Self::DName(r) => RecordData::DName(r),
            Self::Opt(r) => RecordData::Opt(r),
            Self::Ds(r) => RecordData::Ds(r),
            Self::Rrsig(r) => RecordData::Rrsig(r),
            Self::Nsec(r) => RecordData::Nsec(r),
            Self::DNSKey(r) => RecordData::DNSKey(r),
            Self::Nsec3(r) => RecordData::Nsec3(r),
            Self::Nsec3Param(r) => RecordData::Nsec3Param(r),
            Self::ZoneMD(r) => RecordData::ZoneMD(r),
            Self::Unknown(rt, rd) => RecordData::Unknown(rt, rd),
        }
    }

    /// Map references to the domain names within to another type.
    pub fn map_names_by_ref<'r, R, F: FnMut(&'r N) -> R>(
        &'r self,
        f: F,
    ) -> RecordData<'r, R> {
        match self {
            Self::A(r) => RecordData::A(*r),
            Self::Ns(r) => RecordData::Ns(r.map_name_by_ref(f)),
            Self::CName(r) => RecordData::CName(r.map_name_by_ref(f)),
            Self::Soa(r) => RecordData::Soa(r.map_names_by_ref(f)),
            Self::Ptr(r) => RecordData::Ptr(r.map_name_by_ref(f)),
            Self::HInfo(r) => RecordData::HInfo(*r),
            Self::Mx(r) => RecordData::Mx(r.map_name_by_ref(f)),
            Self::Txt(r) => RecordData::Txt(r),
            Self::Rp(r) => RecordData::Rp(r.map_names_by_ref(f)),
            Self::Aaaa(r) => RecordData::Aaaa(*r),
            Self::Srv(r) => RecordData::Srv(r),
            Self::DName(r) => RecordData::DName(r),
            Self::Opt(r) => RecordData::Opt(r),
            Self::Ds(r) => RecordData::Ds(r),
            Self::Rrsig(r) => RecordData::Rrsig(r.clone()),
            Self::Nsec(r) => RecordData::Nsec(r.clone()),
            Self::DNSKey(r) => RecordData::DNSKey(r),
            Self::Nsec3(r) => RecordData::Nsec3(r.clone()),
            Self::Nsec3Param(r) => RecordData::Nsec3Param(r),
            Self::ZoneMD(r) => RecordData::ZoneMD(r),
            Self::Unknown(rt, rd) => RecordData::Unknown(*rt, rd),
        }
    }

    /// Copy referenced data into the given [`Bump`](bumpalo::Bump) allocator.
    #[cfg(feature = "bumpalo")]
    pub fn clone_to_bump<'r>(
        &self,
        bump: &'r bumpalo::Bump,
    ) -> RecordData<'r, N>
    where
        N: Clone,
    {
        use crate::utils::dst::copy_to_bump;

        match self {
            Self::A(r) => RecordData::A(*r),
            Self::Ns(r) => RecordData::Ns(r.clone()),
            Self::CName(r) => RecordData::CName(r.clone()),
            Self::Soa(r) => RecordData::Soa(r.clone()),
            Self::Ptr(r) => RecordData::Ptr(r.clone()),
            Self::HInfo(r) => RecordData::HInfo(r.clone_to_bump(bump)),
            Self::Mx(r) => RecordData::Mx(r.clone()),
            Self::Txt(r) => RecordData::Txt(copy_to_bump(*r, bump)),
            Self::Rp(r) => RecordData::Rp(r.clone()),
            Self::Aaaa(r) => RecordData::Aaaa(*r),
            Self::Srv(r) => RecordData::Srv(copy_to_bump(*r, bump)),
            Self::DName(r) => RecordData::DName(copy_to_bump(*r, bump)),
            Self::Opt(r) => RecordData::Opt(copy_to_bump(*r, bump)),
            Self::Ds(r) => RecordData::Ds(copy_to_bump(*r, bump)),
            Self::Rrsig(r) => RecordData::Rrsig(r.clone_to_bump(bump)),
            Self::Nsec(r) => RecordData::Nsec(r.clone_to_bump(bump)),
            Self::DNSKey(r) => RecordData::DNSKey(copy_to_bump(*r, bump)),
            Self::Nsec3(r) => RecordData::Nsec3(r.clone_to_bump(bump)),
            Self::Nsec3Param(r) => {
                RecordData::Nsec3Param(copy_to_bump(*r, bump))
            }
            Self::ZoneMD(r) => RecordData::ZoneMD(copy_to_bump(*r, bump)),
            Self::Unknown(rt, rd) => {
                RecordData::Unknown(*rt, rd.clone_to_bump(bump))
            }
        }
    }
}

//--- Parsing record data

impl<'a, N: SplitMessageBytes<'a>> ParseRecordData<'a> for RecordData<'a, N> {
    fn parse_record_data(
        contents: &'a [u8],
        start: usize,
        rtype: RType,
    ) -> Result<Self, ParseError> {
        match rtype {
            RType::A => A::parse_bytes(&contents[start..]).map(Self::A),
            RType::NS => {
                Ns::parse_message_bytes(contents, start).map(Self::Ns)
            }
            RType::CNAME => {
                CName::parse_message_bytes(contents, start).map(Self::CName)
            }
            RType::SOA => {
                Soa::parse_message_bytes(contents, start).map(Self::Soa)
            }
            RType::PTR => {
                Ptr::parse_message_bytes(contents, start).map(Self::Ptr)
            }
            RType::HINFO => {
                HInfo::parse_bytes(&contents[start..]).map(Self::HInfo)
            }
            RType::MX => {
                Mx::parse_message_bytes(contents, start).map(Self::Mx)
            }
            RType::TXT => {
                <&Txt>::parse_bytes(&contents[start..]).map(Self::Txt)
            }
            RType::RP => {
                Rp::parse_message_bytes(contents, start).map(Self::Rp)
            }
            RType::AAAA => {
                Aaaa::parse_bytes(&contents[start..]).map(Self::Aaaa)
            }
            RType::SRV => {
                <&Srv>::parse_bytes(&contents[start..]).map(Self::Srv)
            }
            RType::DNAME => {
                <&DName>::parse_bytes(&contents[start..]).map(Self::DName)
            }
            RType::OPT => {
                <&Opt>::parse_bytes(&contents[start..]).map(Self::Opt)
            }
            RType::DS => <&Ds>::parse_bytes(&contents[start..]).map(Self::Ds),
            RType::RRSIG => {
                Rrsig::parse_bytes(&contents[start..]).map(Self::Rrsig)
            }
            RType::NSEC => {
                Nsec::parse_bytes(&contents[start..]).map(Self::Nsec)
            }
            RType::DNSKEY => {
                <&DNSKey>::parse_bytes(&contents[start..]).map(Self::DNSKey)
            }
            RType::NSEC3 => {
                Nsec3::parse_bytes(&contents[start..]).map(Self::Nsec3)
            }
            RType::NSEC3PARAM => {
                <&Nsec3Param>::parse_bytes(&contents[start..])
                    .map(Self::Nsec3Param)
            }
            RType::ZONEMD => {
                <&ZoneMD>::parse_bytes(&contents[start..]).map(Self::ZoneMD)
            }
            _ => <&UnknownRecordData>::parse_bytes(&contents[start..])
                .map(|data| Self::Unknown(rtype, data)),
        }
    }
}

//----------- BoxedRecordData ------------------------------------------------

/// A heap-allocated container for [`RecordData`].
///
/// This is an efficient heap-allocated container for DNS record data. While
/// it does not directly provide much functionality, it has getters to access
/// the [`RecordData`] within.
///
/// ## Performance
///
/// On 64-bit machines, [`BoxedRecordData`] has a size of 16 bytes. This is
/// significantly better than [`RecordData`], which is usually 64 bytes in
/// size. Since [`BoxedRecordData`] is intended for long-term storage and
/// use, it trades off ergonomics for lower memory usage.
#[cfg(feature = "alloc")]
pub struct BoxedRecordData {
    /// A pointer to the record data.
    ///
    /// This is the raw pointer backing a `Box<[u8]>` (its size is stored in
    /// the `size` field). It is owned by this type.
    data: *mut u8,

    /// The record data type.
    ///
    /// The stored bytes represent a valid instance of this record data type,
    /// at least for all known record data types.
    rtype: RType,

    /// The size of the record data.
    size: u16,
}

//--- Inspection

#[cfg(feature = "alloc")]
impl BoxedRecordData {
    /// The record data type.
    pub const fn rtype(&self) -> RType {
        self.rtype
    }

    /// The wire format of the record data.
    pub const fn bytes(&self) -> &[u8] {
        // SAFETY:
        //
        // As documented on 'BoxedRecordData', 'data' and 'size' form the
        // pointer and length of a 'Box<[u8]>'. This pointer is identical to
        // the pointer returned by 'Box::deref()', so we use it directly.
        //
        // The lifetime of the returned slice is within the lifetime of 'self'
        // which is a shared borrow of the 'BoxedRecordData'. As such, the
        // underlying 'Box<[u8]>' outlives the returned slice.
        unsafe { core::slice::from_raw_parts(self.data, self.size as usize) }
    }

    /// Access the [`RecordData`] within.
    pub fn get(&self) -> RecordData<'_, &'_ Name> {
        let (rtype, bytes) = (self.rtype, self.bytes());
        // SAFETY: As documented on 'BoxedRecordData', the referenced bytes
        // are known to be a valid instance of the record data type (for all
        // known record data types). As such, this function will succeed.
        unsafe {
            RecordData::parse_record_data_bytes(bytes, rtype)
                .unwrap_unchecked()
        }
    }
}

//--- Formatting

#[cfg(feature = "alloc")]
impl fmt::Debug for BoxedRecordData {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        // This should concatenate to form 'BoxedRecordData'.
        f.write_str("Boxed")?;
        self.get().fmt(f)
    }
}

//--- Equality

#[cfg(feature = "alloc")]
impl PartialEq for BoxedRecordData {
    fn eq(&self, other: &Self) -> bool {
        self.get().eq(&other.get())
    }
}

#[cfg(feature = "alloc")]
impl Eq for BoxedRecordData {}

//--- Hashing

#[cfg(feature = "alloc")]
impl Hash for BoxedRecordData {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.get().hash(state)
    }
}

//--- Clone

#[cfg(feature = "alloc")]
impl Clone for BoxedRecordData {
    fn clone(&self) -> Self {
        let bytes: Box<[u8]> = self.bytes().into();
        let data = Box::into_raw(bytes).cast::<u8>();
        let (rtype, size) = (self.rtype, self.size);
        Self { data, rtype, size }
    }
}

//--- Drop

#[cfg(feature = "alloc")]
impl Drop for BoxedRecordData {
    fn drop(&mut self) {
        // Reconstruct the 'Box' and drop it.
        let slice = core::ptr::slice_from_raw_parts_mut(
            self.data,
            self.size as usize,
        );

        // SAFETY: As documented on 'BoxedRecordData', 'data' and 'size' form
        // the pointer and length of a 'Box<[u8]>'. Reconstructing the 'Box'
        // moves out of 'self', but this is sound because 'self' is dropped.
        let _ = unsafe { Box::from_raw(slice) };
    }
}

//--- Send and Sync

// SAFETY: 'BoxedRecordData' is equivalent to '(RType, Box<[u8]>)' with a
// custom representation. It cannot cause data races.
#[cfg(feature = "alloc")]
unsafe impl Send for BoxedRecordData {}

// SAFETY: 'BoxedRecordData' is equivalent to '(RType, Box<[u8]>)' with a
// custom representation. It cannot cause data races.
#[cfg(feature = "alloc")]
unsafe impl Sync for BoxedRecordData {}

//--- Conversion from 'RecordData'

#[cfg(feature = "alloc")]
impl<N: BuildBytes> From<RecordData<'_, N>> for BoxedRecordData {
    /// Build a [`RecordData`] into a heap allocation.
    ///
    /// # Panics
    ///
    /// Panics if the [`RecordData`] does not fit in a 64KiB buffer, or if the
    /// serialized bytes cannot be parsed back into `RecordData<'_, &Name>`.
    fn from(value: RecordData<'_, N>) -> Self {
        let mut buffer = vec![0u8; value.built_bytes_size()];
        let rest = value
            .build_bytes(&mut buffer)
            .expect("Exactly the right size was allocated");
        debug_assert!(
            rest.is_empty(),
            "Exactly the right size was allocated"
        );
        let buffer: Box<[u8]> = buffer.into_boxed_slice();

        // Verify that the built bytes can be parsed correctly.
        #[cfg(debug_assertions)]
        {
            let _rdata: RecordData<'_, &Name> =
                RecordData::parse_record_data_bytes(&buffer, value.rtype())
                    .expect(
                        "A serialized 'RecordData' could not be parsed back",
                    );
        }

        // Construct the internal representation.
        debug_assert!(
            buffer.len() <= u16::MAX as usize,
            "Record data must fit under 64KiB"
        );
        let size = buffer.len() as u16;
        let data = Box::into_raw(buffer).cast::<u8>();
        let rtype = value.rtype();
        Self { data, rtype, size }
    }
}

// TODO: Convert from 'Box<A>', 'Box<Txt>', etc.

//--- Canonical operations

#[cfg(feature = "alloc")]
impl CanonicalRecordData for BoxedRecordData {
    fn build_canonical_bytes<'b>(
        &self,
        bytes: &'b mut [u8],
    ) -> Result<&'b mut [u8], TruncationError> {
        if self.rtype.uses_lowercase_canonical_form() {
            // Forward to the semantically correct operation.
            self.get().build_canonical_bytes(bytes)
        } else {
            // The canonical format is the same as the wire format.
            self.bytes().build_bytes(bytes)
        }
    }

    fn cmp_canonical(&self, other: &Self) -> Ordering {
        // Compare record data types.
        if self.rtype != other.rtype {
            return self.rtype.cmp(&other.rtype);
        }

        if self.rtype.uses_lowercase_canonical_form() {
            // Forward to the semantically correct operation.
            self.get().cmp_canonical(&other.get())
        } else {
            // Compare raw byte sequences.
            self.bytes().cmp(other.bytes())
        }
    }
}

//--- Parsing record data

#[cfg(feature = "alloc")]
impl ParseRecordData<'_> for BoxedRecordData {
    fn parse_record_data(
        contents: &'_ [u8],
        start: usize,
        rtype: RType,
    ) -> Result<Self, ParseError> {
        RecordData::<'_, NameBuf>::parse_record_data(contents, start, rtype)
            .map(BoxedRecordData::from)
    }
}

#[cfg(feature = "alloc")]
impl ParseRecordDataBytes<'_> for BoxedRecordData {
    fn parse_record_data_bytes(
        bytes: &'_ [u8],
        rtype: RType,
    ) -> Result<Self, ParseError> {
        // Ensure the bytes form valid 'RecordData'.
        let _rdata: RecordData<'_, &Name> =
            RecordData::parse_record_data_bytes(bytes, rtype)?;

        // Ensure the data size is valid.
        let size = u16::try_from(bytes.len()).map_err(|_| ParseError)?;

        // Construct the 'BoxedRecordData' manually.
        let bytes: Box<[u8]> = bytes.into();
        let data = Box::into_raw(bytes).cast::<u8>();
        Ok(Self { data, rtype, size })
    }
}

//--- Building record data

// TODO: 'impl BuildInMessage for BoxedRecordData' will require implementing
// 'impl BuildInMessage for Name', which is difficult because it is hard on
// name compression.

#[cfg(feature = "alloc")]
impl BuildBytes for BoxedRecordData {
    fn build_bytes<'b>(
        &self,
        bytes: &'b mut [u8],
    ) -> Result<&'b mut [u8], TruncationError> {
        self.bytes().build_bytes(bytes)
    }

    fn built_bytes_size(&self) -> usize {
        self.bytes().len()
    }
}

//----------- UnknownRecordData ----------------------------------------------

/// Data for an unknown DNS record type.
///
/// This is a fallback type, used for record types not known to the current
/// implementation. It must not be used for well-known record types, because
/// some of them have special rules that this type does not follow.
#[derive(
    Debug, PartialEq, Eq, Hash, AsBytes, BuildBytes, ParseBytesZC, UnsizedCopy,
)]
#[repr(transparent)]
pub struct UnknownRecordData {
    /// The unparsed option data.
    pub octets: [u8],
}

//--- Interaction

impl UnknownRecordData {
    /// Copy referenced data into the given [`Bump`](bumpalo::Bump) allocator.
    #[cfg(feature = "bumpalo")]
    #[allow(clippy::mut_from_ref)] // using a memory allocator
    pub fn clone_to_bump<'r>(&self, bump: &'r bumpalo::Bump) -> &'r mut Self {
        use crate::new::base::wire::{AsBytes, ParseBytesZC};

        let bytes = bump.alloc_slice_copy(self.as_bytes());
        // SAFETY: 'ParseBytesZC' and 'AsBytes' are inverses.
        unsafe { Self::parse_bytes_in(bytes).unwrap_unchecked() }
    }
}

//--- Canonical operations

impl CanonicalRecordData for UnknownRecordData {
    fn cmp_canonical(&self, other: &Self) -> Ordering {
        // Since this is not a well-known record data type, embedded domain
        // names do not need to be lowercased.
        self.octets.cmp(&other.octets)
    }
}

//--- Building in DNS messages

impl BuildInMessage for UnknownRecordData {
    fn build_in_message(
        &self,
        contents: &mut [u8],
        start: usize,
        _compressor: &mut NameCompressor,
    ) -> Result<usize, TruncationError> {
        let end = start + self.octets.len();
        contents
            .get_mut(start..end)
            .ok_or(TruncationError)?
            .copy_from_slice(&self.octets);
        Ok(end)
    }
}

//--- Cloning

#[cfg(feature = "alloc")]
impl Clone for alloc::boxed::Box<UnknownRecordData> {
    fn clone(&self) -> Self {
        (*self).unsized_copy_into()
    }
}
