Skip to main content

toe_beans/v4/message/options/
mod.rs

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