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