1use core::fmt;
4use core::net::Ipv4Addr;
5
6use super::{ComposeRdata, ParseRdata, ParseRdataText};
7use crate::wire::{Composer, OutBuf, WireReader};
8use crate::zone::{Scanner, Token};
9use crate::{Class, Error, Result, Rtype};
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
15pub struct Wks<'a> {
16 pub address: Ipv4Addr,
18 pub protocol: u8,
20 pub bitmap: &'a [u8],
22}
23
24impl<'a> Wks<'a> {
25 pub fn ports(&self) -> impl Iterator<Item = u16> + 'a {
27 let bitmap = self.bitmap;
28 bitmap.iter().enumerate().flat_map(|(i, &byte)| {
29 (0..8u16).filter_map(move |bit| {
30 let port = u16::try_from(i * 8).ok()?.checked_add(bit)?;
31 (byte & (0x80 >> bit) != 0).then_some(port)
32 })
33 })
34 }
35}
36
37impl<'a> ParseRdata<'a> for Wks<'a> {
38 const RTYPE: Rtype = Rtype::WKS;
39 const CLASS: Option<Class> = Some(Class::IN);
40
41 fn parse_rdata(rdata: &mut WireReader<'a>) -> Result<Self> {
42 Ok(Wks {
43 address: Ipv4Addr::from(rdata.read_array::<4>()?),
44 protocol: rdata.read_u8()?,
45 bitmap: rdata.read_rest(),
46 })
47 }
48}
49
50impl ComposeRdata for Wks<'_> {
51 fn rtype(&self) -> Rtype {
52 Rtype::WKS
53 }
54
55 fn compose_rdata<C: Composer + ?Sized>(&self, c: &mut C) -> Result<()> {
56 c.put_bytes(&self.address.octets())?;
57 c.put_u8(self.protocol)?;
58 c.put_bytes(self.bitmap)
59 }
60}
61
62impl fmt::Display for Wks<'_> {
63 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 write!(f, "{} {}", self.address, self.protocol)?;
66 for port in self.ports() {
67 write!(f, " {port}")?;
68 }
69 Ok(())
70 }
71}
72
73const PROTOCOLS: &[(&str, u8)] = &[("ICMP", 1), ("IGMP", 2), ("TCP", 6), ("UDP", 17)];
76
77const SERVICES: &[(&str, u16)] = &[
81 ("echo", 7),
82 ("discard", 9),
83 ("daytime", 13),
84 ("ftp-data", 20),
85 ("ftp", 21),
86 ("ssh", 22),
87 ("telnet", 23),
88 ("smtp", 25),
89 ("time", 37),
90 ("whois", 43),
91 ("nicname", 43),
92 ("domain", 53),
93 ("tftp", 69),
94 ("gopher", 70),
95 ("finger", 79),
96 ("http", 80),
97 ("www", 80),
98 ("kerberos", 88),
99 ("pop3", 110),
100 ("sunrpc", 111),
101 ("auth", 113),
102 ("nntp", 119),
103 ("ntp", 123),
104 ("imap", 143),
105 ("snmp", 161),
106 ("ldap", 389),
107 ("https", 443),
108 ("submission", 587),
109];
110
111fn lookup<'t, T: Copy>(
113 t: Token<'t>,
114 table: &[(&str, T)],
115 number: fn(&Token<'t>) -> Result<T>,
116) -> Result<T> {
117 if t.as_bytes().first().is_some_and(u8::is_ascii_digit) {
118 return number(&t);
119 }
120 table
121 .iter()
122 .find(|(name, _)| t.is(name))
123 .map(|&(_, v)| v)
124 .ok_or(Error::UnknownMnemonic)
125}
126
127impl ParseRdataText for Wks<'_> {
128 fn parse_text<B: OutBuf + ?Sized>(s: &mut Scanner<'_>, out: &mut B) -> Result<()> {
133 out.put_bytes(&s.ipv4()?.octets())?;
134 out.put_u8(lookup(s.word()?, PROTOCOLS, Token::u8)?)?;
135 let mut bitmap = [0u8; 8192];
137 let mut len = 0;
138 while let Some(t) = s.next_token()? {
139 if t.is_quoted() {
140 return Err(Error::InvalidText);
141 }
142 let port = usize::from(lookup(t, SERVICES, Token::u16)?);
143 if let Some(byte) = bitmap.get_mut(port / 8) {
144 *byte |= 0x80 >> (port % 8);
145 }
146 len = len.max(port / 8 + 1);
147 }
148 out.put_bytes(bitmap.get(..len).unwrap_or(&[]))
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use crate::rdata::tests::{text_error, text_parse, text_round_trip};
155 use crate::{Error, Rtype};
156
157 #[test]
158 fn text() {
159 text_round_trip(
161 Rtype::WKS,
162 "10.0.0.1 tcp ( smtp 37 )",
163 &[10, 0, 0, 1, 6, 0x00, 0x00, 0x00, 0x40, 0x04],
164 "10.0.0.1 6 25 37",
165 );
166 text_round_trip(Rtype::WKS, "192.0.2.1 17", &[192, 0, 2, 1, 17], "192.0.2.1 17");
167 text_round_trip(
168 Rtype::WKS,
169 "192.0.2.1 UDP 1023 domain 0 DOMAIN",
170 &{
171 let mut w = std::vec![192, 0, 2, 1, 17];
172 w.resize(5 + 128, 0);
173 w[5] = 0x80;
174 w[5 + 6] = 0x04;
175 w[5 + 127] = 0x01;
176 w
177 },
178 "192.0.2.1 17 0 53 1023",
179 );
180 let top = text_parse(Rtype::WKS, "192.0.2.1 6 65535").unwrap();
181 assert_eq!((top.len(), top.last()), (5 + 8192, Some(&0x01)));
182 assert_eq!(text_error(Rtype::WKS, "10.0.0.1 xtp 25"), Error::UnknownMnemonic);
183 assert_eq!(text_error(Rtype::WKS, "10.0.0.1 6 bogus"), Error::UnknownMnemonic);
184 assert_eq!(text_error(Rtype::WKS, "10.0.0.1 6 65536"), Error::InvalidText);
185 assert_eq!(text_error(Rtype::WKS, "10.0.0.1 256 25"), Error::InvalidText);
186 assert_eq!(text_error(Rtype::WKS, "10.0.0.1 6 \"25\""), Error::InvalidText);
187 assert_eq!(text_error(Rtype::WKS, "10.0.0.1"), Error::UnexpectedEof);
188 }
189}