domain/base/opt/
nsid.rs

1//! ENDS option to provide a Name Server Identifer.
2//!
3//! The option in this module – [`Nsid<Octs>`] – allows a resolver to query
4//! for and a server to provide an identifier for the particular server that
5//! answered the query. This can be helpful when debugging a scenario where
6//! multiple servers serve a common address.
7//!
8//! The option is defined in [RFC 5001](https://tools.ietf.org/html/rfc5001).
9
10use super::super::iana::OptionCode;
11use super::super::message_builder::OptBuilder;
12use super::super::wire::{Composer, ParseError};
13use super::{
14    BuildDataError, ComposeOptData, LongOptData, Opt, OptData, ParseOptData,
15};
16use core::cmp::Ordering;
17use core::{borrow, fmt, hash, mem, str};
18use octseq::builder::OctetsBuilder;
19use octseq::octets::{Octets, OctetsFrom};
20use octseq::parse::Parser;
21
22//------------ Nsid ---------------------------------------------------------/
23
24/// Option data for the Name Server Identifier (NSID) Option.
25///
26/// This option allows identifying a particular name server that has answered
27/// a query. If a client is interested in this information, it includes an
28/// empty NSID option in its query. If the server supports the option, it
29/// includes it in its response with byte string identifying the server.
30///
31/// The option and details about its use are defined in
32/// [RFC 5001](https://tools.ietf.org/html/rfc5001).
33#[derive(Clone, Copy)]
34#[repr(transparent)]
35pub struct Nsid<Octs: ?Sized> {
36    /// The octets of the identifier.
37    octets: Octs,
38}
39
40impl Nsid<()> {
41    /// The option code for this option.
42    pub(super) const CODE: OptionCode = OptionCode::NSID;
43}
44
45#[cfg(feature = "serde")]
46impl<Octs: octseq::serde::SerializeOctets> serde::Serialize for Nsid<Octs> {
47    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
48    where
49        S: serde::Serializer,
50    {
51        self.octets.serialize_octets(serializer)
52    }
53}
54
55impl<Octs> Nsid<Octs> {
56    /// Creates a value from the ocets of the name server identifier.
57    ///
58    /// The function returns an error if `octets` is longer than 65,535
59    /// octets.
60    pub fn from_octets(octets: Octs) -> Result<Self, LongOptData>
61    where
62        Octs: AsRef<[u8]>,
63    {
64        LongOptData::check_len(octets.as_ref().len())?;
65        Ok(unsafe { Self::from_octets_unchecked(octets) })
66    }
67
68    /// Creates a value from the name server identifier without checking.
69    ///
70    /// # Safety
71    ///
72    /// The caller has to make sure that `octets` is no longer than 65,535
73    /// octets.
74    pub unsafe fn from_octets_unchecked(octets: Octs) -> Self {
75        Nsid { octets }
76    }
77
78    /// Parses a value from its wire format.
79    pub fn parse<'a, Src: Octets<Range<'a> = Octs> + ?Sized>(
80        parser: &mut Parser<'a, Src>,
81    ) -> Result<Self, ParseError> {
82        let len = parser.remaining();
83        LongOptData::check_len(len)?;
84        Ok(unsafe { Self::from_octets_unchecked(parser.parse_octets(len)?) })
85    }
86}
87
88impl Nsid<[u8]> {
89    /// Creates a value for a slice of the name server identifer.
90    ///
91    /// The function returns an error if `slice` is longer than 65,535
92    /// octets.
93    pub fn from_slice(slice: &[u8]) -> Result<&Self, LongOptData> {
94        LongOptData::check_len(slice.len())?;
95        Ok(unsafe { Self::from_slice_unchecked(slice) })
96    }
97
98    /// Creates a value for a slice without checking.
99    ///
100    /// # Safety
101    ///
102    /// The caller has to make sure that `octets` is no longer than 65,535
103    /// octets.
104    #[must_use]
105    pub unsafe fn from_slice_unchecked(slice: &[u8]) -> &Self {
106        // SAFETY: Nsid has repr(transparent)
107        mem::transmute(slice)
108    }
109
110    /// Creates an empty NSID option value.
111    #[must_use]
112    pub fn empty() -> &'static Self {
113        unsafe { Self::from_slice_unchecked(b"") }
114    }
115}
116
117impl<Octs: ?Sized> Nsid<Octs> {
118    /// Returns a reference to the octets with the server identifier.
119    pub fn as_octets(&self) -> &Octs {
120        &self.octets
121    }
122
123    /// Converts the value into the octets with the server identifier.
124    pub fn into_octets(self) -> Octs
125    where
126        Octs: Sized,
127    {
128        self.octets
129    }
130
131    /// Returns a slice of the server identifier.
132    pub fn as_slice(&self) -> &[u8]
133    where
134        Octs: AsRef<[u8]>,
135    {
136        self.octets.as_ref()
137    }
138
139    /// Returns a value over an octets slice.
140    pub fn for_slice(&self) -> &Nsid<[u8]>
141    where
142        Octs: AsRef<[u8]>,
143    {
144        unsafe { Nsid::from_slice_unchecked(self.octets.as_ref()) }
145    }
146}
147
148//--- OctetsFrom
149
150impl<Octs, SrcOcts> OctetsFrom<Nsid<SrcOcts>> for Nsid<Octs>
151where
152    Octs: OctetsFrom<SrcOcts>,
153{
154    type Error = Octs::Error;
155
156    fn try_octets_from(src: Nsid<SrcOcts>) -> Result<Self, Self::Error> {
157        Octs::try_octets_from(src.octets)
158            .map(|octets| unsafe { Self::from_octets_unchecked(octets) })
159    }
160}
161
162//--- AsRef and Borrow
163
164impl<Octs: AsRef<[u8]> + ?Sized> AsRef<[u8]> for Nsid<Octs> {
165    fn as_ref(&self) -> &[u8] {
166        self.as_slice()
167    }
168}
169
170impl<Octs: AsRef<[u8]> + ?Sized> borrow::Borrow<[u8]> for Nsid<Octs> {
171    fn borrow(&self) -> &[u8] {
172        self.as_slice()
173    }
174}
175
176//--- OptData etc.
177
178impl<Octs: ?Sized> OptData for Nsid<Octs> {
179    fn code(&self) -> OptionCode {
180        OptionCode::NSID
181    }
182}
183
184impl<'a, Octs: Octets> ParseOptData<'a, Octs> for Nsid<Octs::Range<'a>> {
185    fn parse_option(
186        code: OptionCode,
187        parser: &mut Parser<'a, Octs>,
188    ) -> Result<Option<Self>, ParseError> {
189        if code == OptionCode::NSID {
190            Self::parse(parser).map(Some)
191        } else {
192            Ok(None)
193        }
194    }
195}
196
197impl<Octs: AsRef<[u8]> + ?Sized> ComposeOptData for Nsid<Octs> {
198    fn compose_len(&self) -> u16 {
199        self.octets
200            .as_ref()
201            .len()
202            .try_into()
203            .expect("long option data")
204    }
205
206    fn compose_option<Target: OctetsBuilder + ?Sized>(
207        &self,
208        target: &mut Target,
209    ) -> Result<(), Target::AppendError> {
210        target.append_slice(self.octets.as_ref())
211    }
212}
213
214//--- Display and Debug
215
216impl<Octs: AsRef<[u8]> + ?Sized> fmt::Display for Nsid<Octs> {
217    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
218        // RFC 5001 § 2.4:
219        // | User interfaces MUST read and write the contents of the NSID
220        // | option as a sequence of hexadecimal digits, two digits per
221        // | payload octet.
222        for v in self.octets.as_ref() {
223            write!(f, "{:X} ", *v)?;
224        }
225        if let Ok(s) = str::from_utf8(self.octets.as_ref()) {
226            write!(f, "({})", s)?;
227        }
228        Ok(())
229    }
230}
231
232impl<Octs: AsRef<[u8]> + ?Sized> fmt::Debug for Nsid<Octs> {
233    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234        write!(f, "Nsid({})", self)
235    }
236}
237
238//--- PartialEq and Eq
239
240impl<Octs, Other> PartialEq<Other> for Nsid<Octs>
241where
242    Octs: AsRef<[u8]> + ?Sized,
243    Other: AsRef<[u8]> + ?Sized,
244{
245    fn eq(&self, other: &Other) -> bool {
246        self.as_slice().eq(other.as_ref())
247    }
248}
249
250impl<Octs: AsRef<[u8]> + ?Sized> Eq for Nsid<Octs> {}
251
252//--- PartialOrd and Ord
253
254impl<Octs, Other> PartialOrd<Other> for Nsid<Octs>
255where
256    Octs: AsRef<[u8]> + ?Sized,
257    Other: AsRef<[u8]> + ?Sized,
258{
259    fn partial_cmp(&self, other: &Other) -> Option<Ordering> {
260        self.as_slice().partial_cmp(other.as_ref())
261    }
262}
263
264impl<Octs: AsRef<[u8]> + ?Sized> Ord for Nsid<Octs> {
265    fn cmp(&self, other: &Self) -> Ordering {
266        self.as_slice().cmp(other.as_slice())
267    }
268}
269
270//--- Hash
271
272impl<Octs: AsRef<[u8]> + ?Sized> hash::Hash for Nsid<Octs> {
273    fn hash<H: hash::Hasher>(&self, state: &mut H) {
274        self.as_slice().hash(state)
275    }
276}
277
278//--- Extended Opt and OptBuilder
279
280impl<Octs: Octets> Opt<Octs> {
281    /// Returns the first NSID option present.
282    ///
283    /// In a response, the NSID option contains an identifier of the name
284    /// server that answered the query. In a query, the option is empty and
285    /// signals a request for inclusion in a response.
286    pub fn nsid(&self) -> Option<Nsid<Octs::Range<'_>>> {
287        self.first()
288    }
289}
290
291impl<Target: Composer> OptBuilder<'_, Target> {
292    /// Appends an NSID option with the given server identifier.
293    ///
294    /// The NSID option contains an identifier for the name server that
295    /// processed a query.
296    ///
297    /// In a request, the option can be included to request the server to
298    /// include its server identifier. In this case, the data should be
299    /// empty. You can use [`client_nsid`][Self::client_nsid] to easily
300    /// append this version of the option.
301    pub fn nsid(
302        &mut self,
303        data: &(impl AsRef<[u8]> + ?Sized),
304    ) -> Result<(), BuildDataError> {
305        Ok(self.push(Nsid::from_slice(data.as_ref())?)?)
306    }
307
308    /// Appends the client version of an NSID option.
309    ///
310    /// If included by a client, the NSID option requests that the server
311    /// returns its name server identifier via the NSID option in a response.
312    /// In this case, the option must be empty. This method creates such an
313    /// empty NSID option.
314    pub fn client_nsid(&mut self) -> Result<(), Target::AppendError> {
315        self.push(Nsid::empty())
316    }
317}
318
319//============ Testing ======================================================
320
321#[cfg(test)]
322#[cfg(all(feature = "std", feature = "bytes"))]
323mod test {
324    use super::super::test::test_option_compose_parse;
325    use super::*;
326
327    #[test]
328    #[allow(clippy::redundant_closure)] // lifetimes ...
329    fn nsid_compose_parse() {
330        test_option_compose_parse(
331            &Nsid::from_octets("foo").unwrap(),
332            |parser| Nsid::parse(parser),
333        );
334    }
335}