1use rand_core::RngCore;
2
3use super::*;
4
5pub struct Client<T> {
11 pub rng: T,
12 pub mac: [u8; 6],
13}
14
15impl<T> Client<T>
16where
17 T: RngCore,
18{
19 pub const fn new(rng: T, mac: [u8; 6]) -> Self {
20 Self { rng, mac }
21 }
22
23 pub fn discover<'o>(
24 &mut self,
25 opt_buf: &'o mut [DhcpOption<'o>],
26 secs: u16,
27 ip: Option<Ipv4Addr>,
28 ) -> (Packet<'o>, u32) {
29 self.bootp_request(secs, None, true, Options::discover(ip, opt_buf))
30 }
31
32 pub fn request<'o>(
33 &mut self,
34 opt_buf: &'o mut [DhcpOption<'o>],
35 secs: u16,
36 ip: Ipv4Addr,
37 broadcast: bool,
38 ) -> (Packet<'o>, u32) {
39 self.bootp_request(secs, None, broadcast, Options::request(ip, opt_buf))
40 }
41
42 pub fn release<'o>(
43 &mut self,
44 opt_buf: &'o mut [DhcpOption<'o>],
45 secs: u16,
46 ip: Ipv4Addr,
47 ) -> Packet<'o> {
48 self.bootp_request(secs, Some(ip), false, Options::release(opt_buf))
49 .0
50 }
51
52 pub fn decline<'o>(
53 &mut self,
54 opt_buf: &'o mut [DhcpOption<'o>],
55 secs: u16,
56 ip: Ipv4Addr,
57 ) -> Packet<'o> {
58 self.bootp_request(secs, Some(ip), false, Options::decline(opt_buf))
59 .0
60 }
61
62 pub fn is_offer(&self, reply: &Packet<'_>, xid: u32) -> bool {
63 self.is_bootp_reply_for_us(reply, xid, Some(&[MessageType::Offer]))
64 }
65
66 pub fn is_ack(&self, reply: &Packet<'_>, xid: u32) -> bool {
67 self.is_bootp_reply_for_us(reply, xid, Some(&[MessageType::Ack]))
68 }
69
70 pub fn is_nak(&self, reply: &Packet<'_>, xid: u32) -> bool {
71 self.is_bootp_reply_for_us(reply, xid, Some(&[MessageType::Nak]))
72 }
73
74 #[allow(clippy::too_many_arguments)]
75 pub fn bootp_request<'o>(
76 &mut self,
77 secs: u16,
78 ip: Option<Ipv4Addr>,
79 broadcast: bool,
80 options: Options<'o>,
81 ) -> (Packet<'o>, u32) {
82 let xid = self.rng.next_u32();
83
84 (
85 Packet::new_request(self.mac, xid, secs, ip, broadcast, options),
86 xid,
87 )
88 }
89
90 pub fn is_bootp_reply_for_us(
91 &self,
92 reply: &Packet<'_>,
93 xid: u32,
94 expected_message_types: Option<&[MessageType]>,
95 ) -> bool {
96 if reply.reply && reply.is_for_us(&self.mac, xid) {
97 if let Some(expected_message_types) = expected_message_types {
98 let mt = reply.options.iter().find_map(|option| {
99 if let DhcpOption::MessageType(mt) = option {
100 Some(mt)
101 } else {
102 None
103 }
104 });
105
106 expected_message_types.iter().any(|emt| mt == Some(*emt))
107 } else {
108 true
109 }
110 } else {
111 false
112 }
113 }
114}