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