1#![cfg_attr(not(feature = "std"), no_std)]
2#![warn(clippy::large_futures)]
3#![allow(clippy::uninlined_format_args)]
4#![allow(unknown_lints)]
5
6use core::str::Utf8Error;
8
9pub use core::net::Ipv4Addr;
10
11use num_enum::TryFromPrimitive;
12
13use edge_raw::bytes::{self, BytesIn, BytesOut};
14
15pub(crate) mod fmt;
17
18pub mod client;
19pub mod server;
20
21#[cfg(feature = "io")]
22pub mod io;
23
24#[derive(Copy, Clone, Debug, Eq, PartialEq)]
25pub enum Error {
26 DataUnderflow,
27 BufferOverflow,
28 InvalidPacket,
29 InvalidUtf8Str(Utf8Error),
30 InvalidMessageType,
31 MissingCookie,
32 InvalidHlen,
33}
34
35impl From<bytes::Error> for Error {
36 fn from(value: bytes::Error) -> Self {
37 match value {
38 bytes::Error::BufferOverflow => Self::BufferOverflow,
39 bytes::Error::DataUnderflow => Self::DataUnderflow,
40 bytes::Error::InvalidFormat => Self::InvalidPacket,
41 }
42 }
43}
44
45impl core::fmt::Display for Error {
46 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
47 let str = match self {
48 Self::DataUnderflow => "Data underflow",
49 Self::BufferOverflow => "Buffer overflow",
50 Self::InvalidPacket => "Invalid packet",
51 Self::InvalidUtf8Str(_) => "Invalid Utf8 string",
52 Self::InvalidMessageType => "Invalid message type",
53 Self::MissingCookie => "Missing cookie",
54 Self::InvalidHlen => "Invalid hlen",
55 };
56
57 write!(f, "{}", str)
58 }
59}
60
61#[cfg(feature = "defmt")]
62impl defmt::Format for Error {
63 fn format(&self, f: defmt::Formatter<'_>) {
64 let str = match self {
65 Self::DataUnderflow => "Data underflow",
66 Self::BufferOverflow => "Buffer overflow",
67 Self::InvalidPacket => "Invalid packet",
68 Self::InvalidUtf8Str(_) => "Invalid Utf8 string",
69 Self::InvalidMessageType => "Invalid message type",
70 Self::MissingCookie => "Missing cookie",
71 Self::InvalidHlen => "Invalid hlen",
72 };
73
74 defmt::write!(f, "{}", str)
75 }
76}
77
78impl core::error::Error for Error {}
79
80#[derive(Copy, Clone, PartialEq, Eq, Debug, TryFromPrimitive)]
92#[repr(u8)]
93pub enum MessageType {
94 Discover = 1,
96
97 Offer = 2,
99
100 Request = 3,
105
106 Decline = 4,
108
109 Ack = 5,
111
112 Nak = 6,
115
116 Release = 7,
118
119 Inform = 8,
122}
123
124impl core::fmt::Display for MessageType {
125 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
126 match self {
127 Self::Discover => "DHCPDISCOVER",
128 Self::Offer => "DHCPOFFER",
129 Self::Request => "DHCPREQUEST",
130 Self::Decline => "DHCPDECLINE",
131 Self::Ack => "DHCPACK",
132 Self::Nak => "DHCPNAK",
133 Self::Release => "DHCPRELEASE",
134 Self::Inform => "DHCPINFORM",
135 }
136 .fmt(f)
137 }
138}
139
140#[cfg(feature = "defmt")]
141impl defmt::Format for MessageType {
142 fn format(&self, f: defmt::Formatter<'_>) {
143 match self {
144 Self::Discover => "DHCPDISCOVER",
145 Self::Offer => "DHCPOFFER",
146 Self::Request => "DHCPREQUEST",
147 Self::Decline => "DHCPDECLINE",
148 Self::Ack => "DHCPACK",
149 Self::Nak => "DHCPNAK",
150 Self::Release => "DHCPRELEASE",
151 Self::Inform => "DHCPINFORM",
152 }
153 .format(f)
154 }
155}
156
157#[derive(Clone, PartialEq, Eq, Debug)]
159#[cfg_attr(feature = "defmt", derive(defmt::Format))]
160pub struct Packet<'a> {
161 pub reply: bool,
162 pub hops: u8,
163 pub xid: u32,
164 pub secs: u16,
165 pub broadcast: bool,
166 pub ciaddr: Ipv4Addr,
167 pub yiaddr: Ipv4Addr,
168 pub siaddr: Ipv4Addr,
169 pub giaddr: Ipv4Addr,
170 pub chaddr: [u8; 16],
171 pub options: Options<'a>,
172}
173
174impl<'a> Packet<'a> {
175 const COOKIE: [u8; 4] = [99, 130, 83, 99];
176
177 const BOOT_REQUEST: u8 = 1; const BOOT_REPLY: u8 = 2; const SERVER_NAME_AND_FILE_NAME: usize = 64 + 128;
181
182 const END: u8 = 255;
183 const PAD: u8 = 0;
184
185 pub fn new_request(
186 mac: [u8; 6],
187 xid: u32,
188 secs: u16,
189 our_ip: Option<Ipv4Addr>,
190 broadcast: bool,
191 options: Options<'a>,
192 ) -> Self {
193 let mut chaddr = [0; 16];
194 chaddr[..6].copy_from_slice(&mac);
195
196 Self {
197 reply: false,
198 hops: 0,
199 xid,
200 secs,
201 broadcast,
202 ciaddr: our_ip.unwrap_or(Ipv4Addr::UNSPECIFIED),
203 yiaddr: our_ip.unwrap_or(Ipv4Addr::UNSPECIFIED),
204 siaddr: Ipv4Addr::UNSPECIFIED,
205 giaddr: Ipv4Addr::UNSPECIFIED,
206 chaddr,
207 options,
208 }
209 }
210
211 pub fn new_reply<'b>(&self, ip: Option<Ipv4Addr>, options: Options<'b>) -> Packet<'b> {
212 let mut ciaddr = Ipv4Addr::UNSPECIFIED;
213 if ip.is_some() {
214 for opt in self.options.iter() {
215 if matches!(opt, DhcpOption::MessageType(MessageType::Request)) {
216 ciaddr = self.ciaddr;
217 break;
218 }
219 }
220 }
221
222 Packet {
223 reply: true,
224 hops: 0,
225 xid: self.xid,
226 secs: 0,
227 broadcast: self.broadcast,
228 ciaddr,
229 yiaddr: ip.unwrap_or(Ipv4Addr::UNSPECIFIED),
230 siaddr: Ipv4Addr::UNSPECIFIED,
231 giaddr: self.giaddr,
232 chaddr: self.chaddr,
233 options,
234 }
235 }
236
237 pub fn is_for_us(&self, mac: &[u8; 6], xid: u32) -> bool {
238 const MAC_TRAILING_ZEROS: [u8; 10] = [0; 10];
239
240 self.chaddr[0..6] == *mac
241 && self.chaddr[6..16] == MAC_TRAILING_ZEROS
242 && self.xid == xid
243 && self.reply
244 }
245
246 pub fn decode(data: &'a [u8]) -> Result<Self, Error> {
248 let mut bytes = BytesIn::new(data);
249
250 Ok(Self {
251 reply: {
252 let reply = bytes.byte()? == Self::BOOT_REPLY;
253 let _htype = bytes.byte()?; let hlen = bytes.byte()?;
255
256 if hlen != 6 {
257 Err(Error::InvalidHlen)?;
258 }
259
260 reply
261 },
262 hops: bytes.byte()?,
263 xid: u32::from_be_bytes(bytes.arr()?),
264 secs: u16::from_be_bytes(bytes.arr()?),
265 broadcast: u16::from_be_bytes(bytes.arr()?) & 128 != 0,
266 ciaddr: bytes.arr()?.into(),
267 yiaddr: bytes.arr()?.into(),
268 siaddr: bytes.arr()?.into(),
269 giaddr: bytes.arr()?.into(),
270 chaddr: bytes.arr()?,
271 options: {
272 for _ in 0..Self::SERVER_NAME_AND_FILE_NAME {
273 bytes.byte()?;
274 }
275
276 if bytes.arr()? != Self::COOKIE {
277 Err(Error::MissingCookie)?;
278 }
279
280 Options(OptionsInner::decode(bytes.remaining())?)
281 },
282 })
283 }
284
285 pub fn encode<'o>(&self, buf: &'o mut [u8]) -> Result<&'o [u8], Error> {
287 let mut bytes = BytesOut::new(buf);
288
289 bytes
290 .push(&[if self.reply {
291 Self::BOOT_REPLY
292 } else {
293 Self::BOOT_REQUEST
294 }])?
295 .byte(1)?
296 .byte(6)?
297 .byte(self.hops)?
298 .push(&u32::to_be_bytes(self.xid))?
299 .push(&u16::to_be_bytes(self.secs))?
300 .push(&u16::to_be_bytes(if self.broadcast { 128 } else { 0 }))?
301 .push(&self.ciaddr.octets())?
302 .push(&self.yiaddr.octets())?
303 .push(&self.siaddr.octets())?
304 .push(&self.giaddr.octets())?
305 .push(&self.chaddr)?;
306
307 for _ in 0..Self::SERVER_NAME_AND_FILE_NAME {
308 bytes.byte(0)?;
309 }
310
311 bytes.push(&Self::COOKIE)?;
312
313 self.options.0.encode(&mut bytes)?;
314
315 bytes.byte(Self::END)?;
316
317 while bytes.len() < 272 {
318 bytes.byte(Self::PAD)?;
319 }
320
321 let len = bytes.len();
322
323 Ok(&buf[..len])
324 }
325}
326
327#[derive(Clone, Debug)]
328#[cfg_attr(feature = "defmt", derive(defmt::Format))]
329#[non_exhaustive]
330pub struct Settings<'a> {
331 pub ip: Ipv4Addr,
332 pub server_ip: Option<Ipv4Addr>,
333 pub lease_time_secs: Option<u32>,
334 pub gateway: Option<Ipv4Addr>,
335 pub subnet: Option<Ipv4Addr>,
336 pub dns1: Option<Ipv4Addr>,
337 pub dns2: Option<Ipv4Addr>,
338 pub captive_url: Option<&'a str>,
339}
340
341impl<'a> Settings<'a> {
342 pub fn new(packet: &Packet<'a>) -> Self {
343 Self {
344 ip: packet.yiaddr,
345 server_ip: packet.options.iter().find_map(|option| {
346 if let DhcpOption::ServerIdentifier(ip) = option {
347 Some(ip)
348 } else {
349 None
350 }
351 }),
352 lease_time_secs: packet.options.iter().find_map(|option| {
353 if let DhcpOption::IpAddressLeaseTime(lease_time_secs) = option {
354 Some(lease_time_secs)
355 } else {
356 None
357 }
358 }),
359 gateway: packet.options.iter().find_map(|option| {
360 if let DhcpOption::Router(ips) = option {
361 ips.iter().next()
362 } else {
363 None
364 }
365 }),
366 subnet: packet.options.iter().find_map(|option| {
367 if let DhcpOption::SubnetMask(subnet) = option {
368 Some(subnet)
369 } else {
370 None
371 }
372 }),
373 dns1: packet.options.iter().find_map(|option| {
374 if let DhcpOption::DomainNameServer(ips) = option {
375 ips.iter().next()
376 } else {
377 None
378 }
379 }),
380 dns2: packet.options.iter().find_map(|option| {
381 if let DhcpOption::DomainNameServer(ips) = option {
382 ips.iter().nth(1)
383 } else {
384 None
385 }
386 }),
387 captive_url: packet.options.iter().find_map(|option| {
388 if let DhcpOption::CaptiveUrl(url) = option {
389 Some(url)
390 } else {
391 None
392 }
393 }),
394 }
395 }
396}
397
398#[derive(Clone, PartialEq, Eq)]
399#[cfg_attr(feature = "defmt", derive(defmt::Format))]
400pub struct Options<'a>(OptionsInner<'a>);
401
402impl<'a> Options<'a> {
403 const REQUEST_PARAMS: &'static [u8] = &[
404 DhcpOption::CODE_ROUTER,
405 DhcpOption::CODE_SUBNET,
406 DhcpOption::CODE_DNS,
407 ];
408
409 pub const fn new(options: &'a [DhcpOption<'a>]) -> Self {
410 Self(OptionsInner::DataSlice(options))
411 }
412
413 #[inline(always)]
414 pub const fn buf() -> [DhcpOption<'a>; 8] {
415 [DhcpOption::Message(""); 8]
416 }
417
418 pub fn discover(requested_ip: Option<Ipv4Addr>, buf: &'a mut [DhcpOption<'a>]) -> Self {
419 buf[0] = DhcpOption::MessageType(MessageType::Discover);
420
421 let mut offset = 1;
422
423 if let Some(requested_ip) = requested_ip {
424 buf[1] = DhcpOption::RequestedIpAddress(requested_ip);
425 offset += 1;
426 }
427
428 Self::new(&buf[..offset])
429 }
430
431 pub fn request(ip: Ipv4Addr, buf: &'a mut [DhcpOption<'a>]) -> Self {
432 buf[0] = DhcpOption::MessageType(MessageType::Request);
433 buf[1] = DhcpOption::RequestedIpAddress(ip);
434 buf[2] = DhcpOption::ParameterRequestList(Self::REQUEST_PARAMS);
435
436 Self::new(&buf[..3])
437 }
438
439 pub fn release(buf: &'a mut [DhcpOption<'a>]) -> Self {
440 buf[0] = DhcpOption::MessageType(MessageType::Release);
441
442 Self::new(&buf[..1])
443 }
444
445 pub fn decline(buf: &'a mut [DhcpOption<'a>]) -> Self {
446 buf[0] = DhcpOption::MessageType(MessageType::Decline);
447
448 Self::new(&buf[..1])
449 }
450
451 #[allow(clippy::too_many_arguments)]
452 pub fn reply<'b>(
453 &self,
454 mt: MessageType,
455 server_ip: Ipv4Addr,
456 lease_duration_secs: u32,
457 gateways: &'b [Ipv4Addr],
458 subnet: Option<Ipv4Addr>,
459 dns: &'b [Ipv4Addr],
460 captive_url: Option<&'b str>,
461 buf: &'b mut [DhcpOption<'b>],
462 ) -> Options<'b> {
463 let requested = self.iter().find_map(|option| {
464 if let DhcpOption::ParameterRequestList(requested) = option {
465 Some(requested)
466 } else {
467 None
468 }
469 });
470
471 Options::internal_reply(
472 requested,
473 mt,
474 server_ip,
475 lease_duration_secs,
476 gateways,
477 subnet,
478 dns,
479 captive_url,
480 buf,
481 )
482 }
483
484 #[allow(clippy::too_many_arguments)]
485 fn internal_reply(
486 requested: Option<&[u8]>,
487 mt: MessageType,
488 server_ip: Ipv4Addr,
489 lease_duration_secs: u32,
490 gateways: &'a [Ipv4Addr],
491 subnet: Option<Ipv4Addr>,
492 dns: &'a [Ipv4Addr],
493 captive_url: Option<&'a str>,
494 buf: &'a mut [DhcpOption<'a>],
495 ) -> Self {
496 buf[0] = DhcpOption::MessageType(mt);
497 buf[1] = DhcpOption::ServerIdentifier(server_ip);
498 buf[2] = DhcpOption::IpAddressLeaseTime(lease_duration_secs);
499
500 let mut offset = 3;
501
502 if !matches!(mt, MessageType::Nak) {
503 if let Some(requested) = requested {
504 for code in requested {
505 if !buf[0..offset].iter().any(|option| option.code() == *code) {
506 let option = match *code {
507 DhcpOption::CODE_ROUTER => (!gateways.is_empty())
508 .then_some(DhcpOption::Router(Ipv4Addrs::new(gateways))),
509 DhcpOption::CODE_DNS => (!dns.is_empty())
510 .then_some(DhcpOption::DomainNameServer(Ipv4Addrs::new(dns))),
511 DhcpOption::CODE_SUBNET => subnet.map(DhcpOption::SubnetMask),
512 DhcpOption::CODE_CAPTIVE_URL => captive_url.map(DhcpOption::CaptiveUrl),
513 _ => None,
514 };
515
516 if let Some(option) = option {
517 buf[offset] = option;
518 offset += 1;
519 }
520 }
521
522 if offset == buf.len() {
523 break;
524 }
525 }
526 }
527 }
528
529 Self::new(&buf[..offset])
530 }
531
532 pub fn iter(&self) -> impl Iterator<Item = DhcpOption<'a>> + 'a {
533 self.0.iter()
534 }
535
536 pub(crate) fn requested_ip(&self) -> Option<Ipv4Addr> {
537 self.iter().find_map(|option| {
538 if let DhcpOption::RequestedIpAddress(ip) = option {
539 Some(ip)
540 } else {
541 None
542 }
543 })
544 }
545}
546
547impl core::fmt::Debug for Options<'_> {
548 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
549 f.debug_set().entries(self.iter()).finish()
550 }
551}
552
553#[derive(Clone, PartialEq, Eq, Debug)]
554#[cfg_attr(feature = "defmt", derive(defmt::Format))]
555enum OptionsInner<'a> {
556 ByteSlice(&'a [u8]),
557 DataSlice(&'a [DhcpOption<'a>]),
558}
559
560impl<'a> OptionsInner<'a> {
561 fn decode(data: &'a [u8]) -> Result<Self, Error> {
562 let mut bytes = BytesIn::new(data);
563
564 while DhcpOption::decode(&mut bytes)?.is_some() {}
565
566 Ok(Self::ByteSlice(data))
567 }
568
569 fn encode(&self, buf: &mut BytesOut) -> Result<(), Error> {
570 for option in self.iter() {
571 option.encode(buf)?;
572 }
573
574 Ok(())
575 }
576
577 fn iter(&self) -> impl Iterator<Item = DhcpOption<'a>> + 'a {
578 struct ByteSliceDhcpOptions<'a>(BytesIn<'a>);
579
580 impl<'a> Iterator for ByteSliceDhcpOptions<'a> {
581 type Item = DhcpOption<'a>;
582
583 fn next(&mut self) -> Option<Self::Item> {
584 if self.0.is_empty() {
585 None
586 } else {
587 unwrap!(DhcpOption::decode(&mut self.0))
588 }
589 }
590 }
591
592 match self {
593 Self::ByteSlice(data) => {
594 EitherIterator::First(ByteSliceDhcpOptions(BytesIn::new(data)))
595 }
596 Self::DataSlice(data) => EitherIterator::Second(data.iter().cloned()),
597 }
598 }
599}
600
601#[derive(Copy, Clone, PartialEq, Eq, Debug)]
602#[cfg_attr(feature = "defmt", derive(defmt::Format))]
603pub enum DhcpOption<'a> {
604 MessageType(MessageType),
606 ServerIdentifier(Ipv4Addr),
608 ParameterRequestList(&'a [u8]),
610 RequestedIpAddress(Ipv4Addr),
612 HostName(&'a str),
614 Router(Ipv4Addrs<'a>),
616 DomainNameServer(Ipv4Addrs<'a>),
618 IpAddressLeaseTime(u32),
620 SubnetMask(Ipv4Addr),
622 Message(&'a str),
624 MaximumMessageSize(u16),
626 ClientIdentifier(&'a [u8]),
628 CaptiveUrl(&'a str),
630 Unrecognized(u8, &'a [u8]),
632}
633
634impl DhcpOption<'_> {
635 pub const CODE_ROUTER: u8 = DhcpOption::Router(Ipv4Addrs::new(&[])).code();
636 pub const CODE_DNS: u8 = DhcpOption::DomainNameServer(Ipv4Addrs::new(&[])).code();
637 pub const CODE_SUBNET: u8 = DhcpOption::SubnetMask(Ipv4Addr::new(0, 0, 0, 0)).code();
638 pub const CODE_CAPTIVE_URL: u8 = DhcpOption::CaptiveUrl("").code();
639
640 fn decode<'o>(bytes: &mut BytesIn<'o>) -> Result<Option<DhcpOption<'o>>, Error> {
641 let code = bytes.byte()?;
642 if code == Packet::END {
643 Ok(None)
644 } else {
645 let len = bytes.byte()? as usize;
646 let mut bytes = BytesIn::new(bytes.slice(len)?);
647
648 let option = match code {
649 DHCP_MESSAGE_TYPE => DhcpOption::MessageType(
650 TryFromPrimitive::try_from_primitive(bytes.remaining_byte()?)
651 .map_err(|_| Error::InvalidMessageType)?,
652 ),
653 SERVER_IDENTIFIER => {
654 DhcpOption::ServerIdentifier(Ipv4Addr::from(bytes.remaining_arr()?))
655 }
656 PARAMETER_REQUEST_LIST => DhcpOption::ParameterRequestList(bytes.remaining()),
657 REQUESTED_IP_ADDRESS => {
658 DhcpOption::RequestedIpAddress(Ipv4Addr::from(bytes.remaining_arr()?))
659 }
660 HOST_NAME => DhcpOption::HostName(
661 core::str::from_utf8(bytes.remaining()).map_err(Error::InvalidUtf8Str)?,
662 ),
663 MAXIMUM_DHCP_MESSAGE_SIZE => {
664 DhcpOption::MaximumMessageSize(u16::from_be_bytes(bytes.remaining_arr()?))
665 }
666 ROUTER => {
667 DhcpOption::Router(Ipv4Addrs(Ipv4AddrsInner::ByteSlice(bytes.remaining())))
668 }
669 DOMAIN_NAME_SERVER => DhcpOption::DomainNameServer(Ipv4Addrs(
670 Ipv4AddrsInner::ByteSlice(bytes.remaining()),
671 )),
672 IP_ADDRESS_LEASE_TIME => {
673 DhcpOption::IpAddressLeaseTime(u32::from_be_bytes(bytes.remaining_arr()?))
674 }
675 SUBNET_MASK => DhcpOption::SubnetMask(Ipv4Addr::from(bytes.remaining_arr()?)),
676 MESSAGE => DhcpOption::Message(
677 core::str::from_utf8(bytes.remaining()).map_err(Error::InvalidUtf8Str)?,
678 ),
679 CLIENT_IDENTIFIER => {
680 if len < 2 {
681 return Err(Error::DataUnderflow);
682 }
683
684 DhcpOption::ClientIdentifier(bytes.remaining())
685 }
686 CAPTIVE_URL => DhcpOption::HostName(
687 core::str::from_utf8(bytes.remaining()).map_err(Error::InvalidUtf8Str)?,
688 ),
689 _ => DhcpOption::Unrecognized(code, bytes.remaining()),
690 };
691
692 Ok(Some(option))
693 }
694 }
695
696 fn encode(&self, out: &mut BytesOut) -> Result<(), Error> {
697 out.byte(self.code())?;
698
699 self.data(|data| {
700 out.byte(data.len() as _)?;
701 out.push(data)?;
702
703 Ok(())
704 })
705 }
706
707 pub const fn code(&self) -> u8 {
708 match self {
709 Self::MessageType(_) => DHCP_MESSAGE_TYPE,
710 Self::ServerIdentifier(_) => SERVER_IDENTIFIER,
711 Self::ParameterRequestList(_) => PARAMETER_REQUEST_LIST,
712 Self::RequestedIpAddress(_) => REQUESTED_IP_ADDRESS,
713 Self::HostName(_) => HOST_NAME,
714 Self::Router(_) => ROUTER,
715 Self::DomainNameServer(_) => DOMAIN_NAME_SERVER,
716 Self::IpAddressLeaseTime(_) => IP_ADDRESS_LEASE_TIME,
717 Self::SubnetMask(_) => SUBNET_MASK,
718 Self::MaximumMessageSize(_) => MAXIMUM_DHCP_MESSAGE_SIZE,
719 Self::Message(_) => MESSAGE,
720 Self::ClientIdentifier(_) => CLIENT_IDENTIFIER,
721 Self::CaptiveUrl(_) => CAPTIVE_URL,
722 Self::Unrecognized(code, _) => *code,
723 }
724 }
725
726 fn data(&self, mut f: impl FnMut(&[u8]) -> Result<(), Error>) -> Result<(), Error> {
727 match self {
728 Self::MessageType(mtype) => f(&[*mtype as _]),
729 Self::ServerIdentifier(addr) => f(&addr.octets()),
730 Self::ParameterRequestList(prl) => f(prl),
731 Self::RequestedIpAddress(addr) => f(&addr.octets()),
732 Self::HostName(name) => f(name.as_bytes()),
733 Self::Router(addrs) | Self::DomainNameServer(addrs) => {
734 for addr in addrs.iter() {
735 f(&addr.octets())?;
736 }
737
738 Ok(())
739 }
740 Self::IpAddressLeaseTime(secs) => f(&secs.to_be_bytes()),
741 Self::SubnetMask(mask) => f(&mask.octets()),
742 Self::Message(msg) => f(msg.as_bytes()),
743 Self::MaximumMessageSize(size) => f(&size.to_be_bytes()),
744 Self::ClientIdentifier(id) => f(id),
745 Self::CaptiveUrl(name) => f(name.as_bytes()),
746 Self::Unrecognized(_, data) => f(data),
747 }
748 }
749}
750
751#[derive(Copy, Clone, PartialEq, Eq, Debug)]
752#[cfg_attr(feature = "defmt", derive(defmt::Format))]
753pub struct Ipv4Addrs<'a>(Ipv4AddrsInner<'a>);
754
755impl<'a> Ipv4Addrs<'a> {
756 pub const fn new(addrs: &'a [Ipv4Addr]) -> Self {
757 Self(Ipv4AddrsInner::DataSlice(addrs))
758 }
759
760 pub fn iter(&self) -> impl Iterator<Item = Ipv4Addr> + 'a {
761 self.0.iter()
762 }
763}
764
765#[derive(Copy, Clone, PartialEq, Eq, Debug)]
766#[cfg_attr(feature = "defmt", derive(defmt::Format))]
767enum Ipv4AddrsInner<'a> {
768 ByteSlice(&'a [u8]),
769 DataSlice(&'a [Ipv4Addr]),
770}
771
772impl<'a> Ipv4AddrsInner<'a> {
773 fn iter(&self) -> impl Iterator<Item = Ipv4Addr> + 'a {
774 match self {
775 Self::ByteSlice(data) => {
776 EitherIterator::First((0..data.len()).step_by(4).map(|offset| {
777 let octets: [u8; 4] = unwrap!(data[offset..offset + 4].try_into());
778
779 octets.into()
780 }))
781 }
782 Self::DataSlice(data) => EitherIterator::Second(data.iter().cloned()),
783 }
784 }
785}
786
787enum EitherIterator<F, S> {
788 First(F),
789 Second(S),
790}
791
792impl<F, S> Iterator for EitherIterator<F, S>
793where
794 F: Iterator,
795 S: Iterator<Item = F::Item>,
796{
797 type Item = F::Item;
798
799 fn next(&mut self) -> Option<Self::Item> {
800 match self {
801 Self::First(iter) => iter.next(),
802 Self::Second(iter) => iter.next(),
803 }
804 }
805}
806
807const SUBNET_MASK: u8 = 1;
809const ROUTER: u8 = 3;
810const DOMAIN_NAME_SERVER: u8 = 6;
811const HOST_NAME: u8 = 12;
812
813const REQUESTED_IP_ADDRESS: u8 = 50;
815const IP_ADDRESS_LEASE_TIME: u8 = 51;
816const DHCP_MESSAGE_TYPE: u8 = 53;
817const SERVER_IDENTIFIER: u8 = 54;
818const PARAMETER_REQUEST_LIST: u8 = 55;
819const MESSAGE: u8 = 56;
820const MAXIMUM_DHCP_MESSAGE_SIZE: u8 = 57;
821const CLIENT_IDENTIFIER: u8 = 61;
822const CAPTIVE_URL: u8 = 114;