Skip to main content

mdns_proto/wire/record/
srv.rs

1//! SRV record (server location, RFC 2782).
2
3use crate::{
4  error::{BufferTooShortDetail, ParseError},
5  wire::NameRef,
6};
7
8/// Parsed SRV record rdata: priority, weight, port, target.
9#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
10pub struct Srv<'a> {
11  priority: u16,
12  weight: u16,
13  port: u16,
14  target: NameRef<'a>,
15}
16
17impl<'a> Srv<'a> {
18  /// Parse an SRV record's rdata. `message` is the full message; `rdata_offset`
19  /// is the start of the rdata bytes within it; `rdata_len` is the declared
20  /// RDLENGTH.
21  ///
22  /// the 6-byte fixed header (priority+weight+port) AND the inline
23  /// portion of the target name MUST fit within the declared `rdata_len`.
24  /// A record advertising a short rdlength must not be allowed to consume
25  /// bytes past its declared boundary.
26  pub fn try_from_message(
27    message: &'a [u8],
28    rdata_offset: usize,
29    rdata_len: usize,
30  ) -> Result<Self, ParseError> {
31    if rdata_len < 6 {
32      return Err(ParseError::BufferTooShort(BufferTooShortDetail::new(
33        6,
34        rdata_offset,
35        rdata_len,
36      )));
37    }
38    let head = message
39      .get(rdata_offset..rdata_offset.saturating_add(6))
40      .and_then(|s| s.first_chunk::<6>())
41      .ok_or_else(|| {
42        ParseError::BufferTooShort(BufferTooShortDetail::new(
43          6,
44          rdata_offset,
45          message.len().saturating_sub(rdata_offset),
46        ))
47      })?;
48
49    let priority = u16::from_be_bytes([head[0], head[1]]);
50    let weight = u16::from_be_bytes([head[2], head[3]]);
51    let port = u16::from_be_bytes([head[4], head[5]]);
52
53    let target_offset = rdata_offset.saturating_add(6);
54    // SRV rdata is EXACTLY 6 bytes (priority + weight +
55    // port) + one domain name.  Require `6 + consumed == rdata_len`.
56    // Trailing bytes inside the declared rdlength would otherwise be
57    // silently dropped — a hostile known-answer could append garbage
58    // to a matching SRV and still suppress the legitimate outgoing
59    // record.
60    let (target, consumed) = NameRef::try_parse(message, target_offset)?;
61    if consumed.saturating_add(6) != rdata_len {
62      return Err(ParseError::BufferTooShort(BufferTooShortDetail::new(
63        consumed.saturating_add(6),
64        rdata_offset,
65        rdata_len,
66      )));
67    }
68
69    Ok(Self {
70      priority,
71      weight,
72      port,
73      target,
74    })
75  }
76
77  /// Returns the priority field.
78  #[inline(always)]
79  pub const fn priority(&self) -> u16 {
80    self.priority
81  }
82  /// Returns the weight field.
83  #[inline(always)]
84  pub const fn weight(&self) -> u16 {
85    self.weight
86  }
87  /// Returns the port number.
88  #[inline(always)]
89  pub const fn port(&self) -> u16 {
90    self.port
91  }
92  /// Returns the target host name.
93  #[inline(always)]
94  pub const fn target(&self) -> &NameRef<'a> {
95    &self.target
96  }
97}
98
99#[cfg(test)]
100#[allow(clippy::unwrap_used)]
101mod tests;