1use core::fmt::Debug;
2
3use super::*;
4
5#[derive(Clone, Debug)]
6#[cfg_attr(feature = "defmt", derive(defmt::Format))]
7pub struct Lease {
8 mac: [u8; 16],
9 expires: u64,
10}
11
12#[derive(Clone, Debug)]
13#[cfg_attr(feature = "defmt", derive(defmt::Format))]
14pub enum Action<'a> {
15 Discover(Option<Ipv4Addr>, &'a [u8; 16]),
16 Request(Ipv4Addr, &'a [u8; 16]),
17 Release(Ipv4Addr, &'a [u8; 16]),
18 Decline(Ipv4Addr, &'a [u8; 16]),
19}
20
21#[derive(Clone, Debug)]
22#[cfg_attr(feature = "defmt", derive(defmt::Format))]
23#[non_exhaustive]
24pub struct ServerOptions<'a> {
25 pub ip: Ipv4Addr,
26 pub gateways: &'a [Ipv4Addr],
27 pub subnet: Option<Ipv4Addr>,
28 pub dns: &'a [Ipv4Addr],
29 pub captive_url: Option<&'a str>,
30 pub lease_duration_secs: u32,
31}
32
33impl<'a> ServerOptions<'a> {
34 pub fn new(ip: Ipv4Addr, gw_buf: Option<&'a mut [Ipv4Addr; 1]>) -> Self {
35 let gateways = if let Some(gw_buf) = gw_buf {
36 gw_buf[0] = ip;
37 gw_buf.as_slice()
38 } else {
39 &[]
40 };
41
42 Self {
43 ip,
44 gateways,
45 subnet: Some(Ipv4Addr::new(255, 255, 255, 0)),
46 dns: &[],
47 captive_url: None,
48 lease_duration_secs: 7200,
49 }
50 }
51
52 pub fn process<'o>(&self, request: &'o Packet<'o>) -> Option<Action<'o>> {
53 if request.reply {
54 return None;
55 }
56
57 let message_type = request.options.iter().find_map(|option| {
58 if let DhcpOption::MessageType(message_type) = option {
59 Some(message_type)
60 } else {
61 None
62 }
63 });
64
65 let message_type = if let Some(message_type) = message_type {
66 message_type
67 } else {
68 warn!(
69 "Ignoring DHCP request, no message type found: {:?}",
70 request
71 );
72 return None;
73 };
74
75 let server_identifier = request.options.iter().find_map(|option| {
76 if let DhcpOption::ServerIdentifier(ip) = option {
77 Some(ip)
78 } else {
79 None
80 }
81 });
82
83 if server_identifier.is_some() && server_identifier != Some(self.ip) {
84 warn!(
85 "Ignoring {} request, not addressed to this server: {:?}",
86 message_type, request
87 );
88 return None;
89 }
90
91 debug!("Received {} request: {:?}", message_type, request);
92 match message_type {
93 MessageType::Discover => Some(Action::Discover(
94 request.options.requested_ip(),
95 &request.chaddr,
96 )),
97 MessageType::Request => {
98 let requested_ip = request.options.requested_ip().or_else(|| {
99 if request.ciaddr.is_unspecified() {
100 None
101 } else {
102 Some(request.ciaddr)
103 }
104 })?;
105
106 Some(Action::Request(requested_ip, &request.chaddr))
107 }
108 MessageType::Release if server_identifier == Some(self.ip) => {
109 Some(Action::Release(request.yiaddr, &request.chaddr))
110 }
111 MessageType::Decline if server_identifier == Some(self.ip) => {
112 Some(Action::Decline(request.yiaddr, &request.chaddr))
113 }
114 _ => None,
115 }
116 }
117
118 pub fn offer(
119 &self,
120 request: &Packet,
121 yiaddr: Ipv4Addr,
122 opt_buf: &'a mut [DhcpOption<'a>],
123 ) -> Packet<'a> {
124 self.reply(request, MessageType::Offer, Some(yiaddr), opt_buf)
125 }
126
127 pub fn ack_nak(
128 &self,
129 request: &Packet,
130 ip: Option<Ipv4Addr>,
131 opt_buf: &'a mut [DhcpOption<'a>],
132 ) -> Packet<'a> {
133 self.reply(
134 request,
135 if ip.is_some() {
136 MessageType::Ack
137 } else {
138 MessageType::Nak
139 },
140 ip,
141 opt_buf,
142 )
143 }
144
145 fn reply(
146 &self,
147 request: &Packet,
148 message_type: MessageType,
149 ip: Option<Ipv4Addr>,
150 buf: &'a mut [DhcpOption<'a>],
151 ) -> Packet<'a> {
152 let reply = request.new_reply(
153 ip,
154 request.options.reply(
155 message_type,
156 self.ip,
157 self.lease_duration_secs as _,
158 self.gateways,
159 self.subnet,
160 self.dns,
161 self.captive_url,
162 buf,
163 ),
164 );
165
166 debug!("Sending {} reply: {:?}", message_type, reply);
167
168 reply
169 }
170}
171
172#[derive(Clone, Debug)]
176#[cfg_attr(feature = "defmt", derive(defmt::Format))]
177pub struct Server<F, const N: usize> {
178 pub now: F,
179 pub range_start: Ipv4Addr,
180 pub range_end: Ipv4Addr,
181 pub leases: heapless::LinearMap<Ipv4Addr, Lease, N>,
182}
183
184impl<F, const N: usize> Server<F, N>
185where
186 F: FnMut() -> u64,
187{
188 pub const fn new(now: F, ip: Ipv4Addr) -> Self {
194 let octets = ip.octets();
195
196 Self {
197 now,
198 range_start: Ipv4Addr::new(octets[0], octets[1], octets[2], 50),
199 range_end: Ipv4Addr::new(octets[0], octets[1], octets[2], 200),
200 leases: heapless::LinearMap::new(),
201 }
202 }
203
204 pub fn handle_request<'o>(
205 &mut self,
206 opt_buf: &'o mut [DhcpOption<'o>],
207 server_options: &'o ServerOptions,
208 request: &Packet,
209 ) -> Option<Packet<'o>> {
210 server_options
211 .process(request)
212 .and_then(|action| match action {
213 Action::Discover(requested_ip, mac) => {
214 let ip = requested_ip
215 .and_then(|ip| self.is_available(mac, ip).then_some(ip))
216 .or_else(|| self.current_lease(mac))
217 .or_else(|| self.available());
218
219 ip.map(|ip| server_options.offer(request, ip, opt_buf))
220 }
221 Action::Request(ip, mac) => {
222 let now = (self.now)();
223
224 let ip = (self.is_available(mac, ip)
225 && self.add_lease(
226 ip,
227 request.chaddr,
228 now + server_options.lease_duration_secs as u64,
229 ))
230 .then_some(ip);
231
232 Some(server_options.ack_nak(request, ip, opt_buf))
233 }
234 Action::Release(_ip, mac) | Action::Decline(_ip, mac) => {
235 self.remove_lease(mac);
236
237 None
238 }
239 })
240 }
241
242 fn is_available(&mut self, mac: &[u8; 16], addr: Ipv4Addr) -> bool {
243 let pos: u32 = addr.into();
244
245 let start: u32 = self.range_start.into();
246 let end: u32 = self.range_end.into();
247
248 pos >= start
249 && pos <= end
250 && match self.leases.get(&addr) {
251 Some(lease) => lease.mac == *mac || (self.now)() > lease.expires,
252 None => true,
253 }
254 }
255
256 fn available(&mut self) -> Option<Ipv4Addr> {
257 let start: u32 = self.range_start.into();
258 let end: u32 = self.range_end.into();
259
260 for pos in start..end + 1 {
261 let addr = pos.into();
262
263 if !self.leases.contains_key(&addr) {
264 return Some(addr);
265 }
266 }
267
268 if let Some(addr) = self
269 .leases
270 .iter()
271 .find_map(|(addr, lease)| ((self.now)() > lease.expires).then_some(*addr))
272 {
273 self.leases.remove(&addr);
274
275 Some(addr)
276 } else {
277 None
278 }
279 }
280
281 fn current_lease(&self, mac: &[u8; 16]) -> Option<Ipv4Addr> {
282 self.leases
283 .iter()
284 .find_map(|(addr, lease)| (lease.mac == *mac).then_some(*addr))
285 }
286
287 fn add_lease(&mut self, addr: Ipv4Addr, mac: [u8; 16], expires: u64) -> bool {
288 self.remove_lease(&mac);
289
290 self.leases.insert(addr, Lease { mac, expires }).is_ok()
291 }
292
293 fn remove_lease(&mut self, mac: &[u8; 16]) -> bool {
294 if let Some(addr) = self.current_lease(mac) {
295 self.leases.remove(&addr);
296
297 true
298 } else {
299 false
300 }
301 }
302}
303
304#[cfg(feature = "io")]
305impl<const N: usize> Server<fn() -> u64, N> {
306 pub const fn new_with_et(ip: Ipv4Addr) -> Self {
311 Self::new(|| embassy_time::Instant::now().as_secs(), ip)
312 }
313}