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, warn};
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 encoding 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 => {
157                warn!("The End option is automatically handled during encode/decode. Don't use it.")
158            }
159            Self::RapidCommit => bytes.push(80),
160            Self::LeaseTime(x) => x.extend_into(bytes, 51),
161            Self::T1(x) => x.extend_into(bytes, 58),
162            Self::T2(x) => x.extend_into(bytes, 59),
163            Self::RequestedIp(x) => x.extend_into(bytes, 50),
164            Self::ServerIdentifier(x) => x.extend_into(bytes, 54),
165            Self::MessageType(x) => x.extend_into(bytes, 53),
166            Self::Netmask(x) => x.extend_into(bytes, 1),
167            Self::Broadcast(x) => x.extend_into(bytes, 28),
168            Self::Overload(x) => x.extend_into(bytes, 52),
169            Self::SName(x) => x.extend_into(bytes, 66),
170            Self::FileName(x) => x.extend_into(bytes, 67),
171            Self::Router(x) => x.extend_into(bytes, 3),
172            Self::DnsServer(x) => x.extend_into(bytes, 6),
173            Self::MaxMessage(x) => x.extend_into(bytes, 57),
174            Self::HostName(x) => x.extend_into(bytes, 12),
175            Self::DomainName(x) => x.extend_into(bytes, 15),
176            Self::Message(x) => x.extend_into(bytes, 56),
177            Self::SubnetSelect(x) => x.extend_into(bytes, 118),
178            Self::VendorId(x) => x.extend_into(bytes, 60),
179            Self::ClientId(x) => x.extend_into(bytes, 61),
180            Self::TimeOffset(x) => x.extend_into(bytes, 2),
181
182            Self::RequestedOptions(v) => {
183                bytes.push(55);
184                bytes.push(v.len() as u8);
185                bytes.extend(v.iter());
186            }
187
188            Self::Other(t, l, v) => {
189                bytes.push(*t);
190                bytes.push(*l);
191                bytes.extend(v);
192            }
193        };
194    }
195
196    /// Takes an array slice and parses the TLV sub-format
197    pub fn from_bytes(bytes: &[u8]) -> Vec<Self> {
198        let mut slicer = Slicer::new(bytes);
199        let mut options: Vec<MessageOptions> = Vec::with_capacity(308);
200
201        // TODO: https://datatracker.ietf.org/doc/rfc3396/
202        while let Some(tag) = slicer.slice(1) {
203            trace!("parsing option {:?}", tag);
204            match tag[0] {
205                0 => {} // discard Pad bytes
206                80 => {
207                    options.push(Self::RapidCommit);
208                }
209                255 => {
210                    // no length to parse
211                    break; // End must be the last option
212                }
213                tag_with_length => {
214                    // must parse length
215                    match slicer.slice(1) {
216                        Some(length) => match slicer.slice(length[0] as usize) {
217                            Some(value) => {
218                                options.push(MessageOptions::from_tag_value(tag_with_length, value))
219                            }
220                            None => break, // no more bytes (invalid option, length without value)
221                        },
222                        None => break, // no more bytes (invalid option, tag without length)
223                    }
224                }
225            }
226        }
227
228        options
229    }
230}