Skip to main content

toe_beans/v4/message/options/
options.rs

1use super::super::Slicer;
2use super::{
3    AddressListOption, AddressOption, FileOption, MaxMessage, MessageTypes, OpaqueOption,
4    OverloadOptions, SNameOption, StringOption, TimeOffset, TimeOption,
5};
6use crate::v4::{MIN_OPTIONS_SIZE, NON_MAGIC_SIZE};
7use log::{trace, warn};
8use serde::{Deserialize, Serialize};
9
10/// Variants of an options field in [Message](crate::v4::Message).
11/// Options are also referred to as "configuration parameters".
12///
13/// Most options are documented in [RFC-2132](https://datatracker.ietf.org/doc/html/rfc2132) unless noted otherwise.
14#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
15pub enum MessageOptions {
16    /// 0
17    Pad,
18    /// 1
19    Netmask(AddressOption),
20    /// 2, Deprecated by options 100 and 101 in [RFC-4833](https://datatracker.ietf.org/doc/html/rfc4833)
21    TimeOffset(TimeOffset),
22    /// 3
23    Router(AddressListOption),
24    /// 6
25    DnsServer(AddressListOption),
26    /// 12
27    /// See RFC 1035 for character set restrictions.
28    HostName(StringOption),
29    /// 15
30    DomainName(StringOption),
31    /// 28
32    Broadcast(AddressOption),
33    // /// 43
34    // VendorClassOpt(Vec<u8>), // TODO this is a subformat in a subformat
35    /// 50
36    RequestedIp(AddressOption),
37    /// 51
38    ///
39    /// Number of seconds an ip assignment is valid for.
40    /// A client may use this option in a Discover or Request MessageType.
41    /// A server may use this option in a Offer MessageType.
42    LeaseTime(TimeOption),
43    /// 52
44    Overload(OverloadOptions),
45    /// 53
46    MessageType(MessageTypes),
47    /// 54
48    ServerIdentifier(AddressOption),
49    /// 55
50    RequestedOptions(Vec<u8>),
51    /// 56
52    Message(StringOption),
53    /// 57
54    MaxMessage(MaxMessage),
55    /// 58
56    T1(TimeOption),
57    /// 59
58    T2(TimeOption),
59    /// 60
60    VendorId(OpaqueOption),
61    /// 61
62    ClientId(OpaqueOption),
63    /// 66, variable length unlike the fixed Message field
64    SName(SNameOption),
65    /// 67, variable length unlike the fixed Message field
66    FileName(FileOption),
67    /// 80, [RFC-4039](https://datatracker.ietf.org/doc/html/rfc4039)
68    ///
69    /// Used to assign an IP through a faster 2-message (Discover-Ack) exchange
70    /// instead of the traditional 4-message (Discover-Offer-Request-Ack) exchange.
71    ///
72    /// Warning: A client broadcasts its Discover message, so multiple DHCP servers
73    /// on the same subnet might receive it. In such a scenario:
74    /// - A 4-message exchange Requests an IP address from one of the DHCP servers.
75    /// - A 2-message exchange would commit an IP address from each DHCP server.
76    ///
77    /// A client may use this option only in a Discover MessageType.
78    /// A server may use this option only in an Ack MessageType.
79    RapidCommit,
80    /// 118, [RFC-3011](https://datatracker.ietf.org/doc/rfc3011/)
81    SubnetSelect(AddressOption),
82    /// 255
83    End,
84    /// Unknown option
85    Other(u8, u8, Vec<u8>),
86}
87
88impl MessageOptions {
89    /// Converts a MessageOption's enum to a u8
90    /// that describes the "T" (tag) in "TLV" (tag-length-value)
91    pub fn to_tag(&self) -> u8 {
92        match self {
93            Self::Pad => 0,
94            Self::Netmask(_) => 1,
95            Self::TimeOffset(_) => 2,
96            Self::Router(_) => 3,
97            Self::DnsServer(_) => 6,
98            Self::HostName(_) => 12,
99            Self::DomainName(_) => 15,
100            Self::Broadcast(_) => 28,
101            Self::RequestedIp(_) => 50,
102            Self::LeaseTime(_) => 51,
103            Self::Overload(_) => 52,
104            Self::MessageType(_) => 53,
105            Self::ServerIdentifier(_) => 54,
106            Self::RequestedOptions(_) => 55,
107            Self::Message(_) => 56,
108            Self::MaxMessage(_) => 57,
109            Self::T1(_) => 58,
110            Self::T2(_) => 59,
111            Self::VendorId(_) => 60,
112            Self::ClientId(_) => 61,
113            Self::SName(_) => 66,
114            Self::FileName(_) => 67,
115            Self::RapidCommit => 80,
116            Self::SubnetSelect(_) => 118,
117            Self::End => 255,
118            Self::Other(x, _, _) => *x,
119        }
120    }
121
122    /// Convert tag number to enum for options with a value.
123    /// Pad, End, and bools like RapidCommit can be handled specially.
124    fn from_tag_value(tag: u8, value: &[u8]) -> Self {
125        match tag {
126            1 => Self::Netmask(value.into()),
127            2 => Self::TimeOffset(value.into()),
128            3 => Self::Router(value.into()),
129            6 => Self::DnsServer(value.into()),
130            12 => Self::HostName(value.into()),
131            15 => Self::DomainName(value.into()),
132            28 => Self::Broadcast(value.into()),
133            50 => Self::RequestedIp(value.into()),
134            51 => Self::LeaseTime(value.into()),
135            52 => Self::Overload(value.into()),
136            53 => Self::MessageType(MessageTypes::from(value)),
137            54 => Self::ServerIdentifier(value.into()),
138            55 => Self::RequestedOptions(value.into()),
139            56 => Self::Message(value.into()),
140            57 => Self::MaxMessage(value.into()),
141            58 => Self::T1(value.into()),
142            59 => Self::T2(value.into()),
143            60 => Self::VendorId(value.into()),
144            61 => Self::ClientId(value.into()),
145            66 => Self::SName(value.into()),
146            67 => Self::FileName(value.into()),
147            118 => Self::SubnetSelect(value.into()),
148            _ => Self::Other(tag, value.len() as u8, value.into()),
149        }
150    }
151
152    /// for use when encoding a collection of options into bytes
153    #[inline]
154    pub fn extend_into(&self, bytes: &mut Vec<u8>) {
155        match self {
156            Self::Pad => bytes.push(0),
157            Self::End => {
158                warn!("The End option is automatically handled during encode/decode. Don't use it.")
159            }
160            Self::RapidCommit => bytes.push(80),
161            Self::LeaseTime(x) => x.extend_into(bytes, 51),
162            Self::T1(x) => x.extend_into(bytes, 58),
163            Self::T2(x) => x.extend_into(bytes, 59),
164            Self::RequestedIp(x) => x.extend_into(bytes, 50),
165            Self::ServerIdentifier(x) => x.extend_into(bytes, 54),
166            Self::MessageType(x) => x.extend_into(bytes, 53),
167            Self::Netmask(x) => x.extend_into(bytes, 1),
168            Self::Broadcast(x) => x.extend_into(bytes, 28),
169            Self::Overload(x) => x.extend_into(bytes, 52),
170            Self::SName(x) => x.extend_into(bytes, 66),
171            Self::FileName(x) => x.extend_into(bytes, 67),
172            Self::Router(x) => x.extend_into(bytes, 3),
173            Self::DnsServer(x) => x.extend_into(bytes, 6),
174            Self::MaxMessage(x) => x.extend_into(bytes, 57),
175            Self::HostName(x) => x.extend_into(bytes, 12),
176            Self::DomainName(x) => x.extend_into(bytes, 15),
177            Self::Message(x) => x.extend_into(bytes, 56),
178            Self::SubnetSelect(x) => x.extend_into(bytes, 118),
179            Self::VendorId(x) => x.extend_into(bytes, 60),
180            Self::ClientId(x) => x.extend_into(bytes, 61),
181            Self::TimeOffset(x) => x.extend_into(bytes, 2),
182
183            Self::RequestedOptions(v) => {
184                bytes.push(55);
185                bytes.push(v.len() as u8);
186                bytes.extend(v.iter());
187            }
188
189            Self::Other(t, l, v) => {
190                bytes.push(*t);
191                bytes.push(*l);
192                bytes.extend(v);
193            }
194        };
195    }
196}
197
198/// A collection of `MessageOptions`.
199#[derive(Debug, PartialEq)]
200pub struct MessageOptionsList(Vec<MessageOptions>);
201
202impl MessageOptionsList {
203    /// Returns the `MessageOptions` that has the passed tag within the `MessageOptionsList` if it exists.
204    #[inline]
205    pub fn find_option(&self, tag: u8) -> Option<&MessageOptions> {
206        self.0.iter().find(|&option| option.to_tag() == tag)
207    }
208
209    /// Appends a `MessageOptions` onto the `MessageOptionsList`.
210    #[inline]
211    pub fn add_option(&mut self, option: MessageOptions) {
212        self.0.push(option);
213    }
214
215    /// Returns the `MessageOptions` with the passed `index` in the list.
216    /// WARNING: be careful using this as it might not always be available
217    #[deprecated]
218    pub fn get(&self, index: usize) -> Option<&MessageOptions> {
219        self.0.get(index)
220    }
221
222    /// Changes the `MessageOptions` at the passed `index` in the list.
223    /// WARNING: be careful using this as it might not always be available
224    #[deprecated]
225    pub fn set(&mut self, index: usize, option: MessageOptions) {
226        self.0[index] = option;
227    }
228
229    /// Takes an array slice and parses the TLV sub-format
230    #[inline]
231    pub fn from_bytes(bytes: &[u8]) -> Self {
232        let mut slicer = Slicer::new(bytes);
233        let mut options: Vec<MessageOptions> = Vec::with_capacity(NON_MAGIC_SIZE);
234
235        // TODO: https://datatracker.ietf.org/doc/rfc3396/
236        while let Some(tag) = slicer.slice(1) {
237            trace!("parsing option {:?}", tag);
238            match tag[0] {
239                0 => {} // discard Pad bytes
240                80 => {
241                    options.push(MessageOptions::RapidCommit);
242                }
243                255 => {
244                    // no length to parse
245                    break; // End must be the last option
246                }
247                tag_with_length => {
248                    // must parse length
249                    match slicer.slice(1) {
250                        Some(length) => match slicer.slice(length[0] as usize) {
251                            Some(value) => {
252                                options.push(MessageOptions::from_tag_value(tag_with_length, value))
253                            }
254                            None => break, // no more bytes (invalid option, length without value)
255                        },
256                        None => break, // no more bytes (invalid option, tag without length)
257                    }
258                }
259            }
260        }
261
262        Self(options)
263    }
264}
265
266impl From<Vec<MessageOptions>> for MessageOptionsList {
267    fn from(options: Vec<MessageOptions>) -> Self {
268        Self(options)
269    }
270}
271
272impl From<&MessageOptionsList> for Vec<u8> {
273    #[inline]
274    fn from(list: &MessageOptionsList) -> Self {
275        let mut encoded = Vec::with_capacity(MIN_OPTIONS_SIZE);
276
277        list.0
278            .iter()
279            .for_each(|option| option.extend_into(&mut encoded));
280
281        encoded
282    }
283}