dhcpv4/
server.rs

1// Copyright 2018 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::configuration::ServerParameters;
6use crate::protocol::identifier::ClientIdentifier;
7use crate::protocol::{DhcpOption, Message, MessageType, OpCode, OptionCode, ProtocolError};
8
9#[cfg(target_os = "fuchsia")]
10use crate::protocol::{FidlCompatible, FromFidlExt, IntoFidlExt};
11
12use anyhow::{Context as _, Error};
13
14#[cfg(target_os = "fuchsia")]
15use zx::Status;
16
17#[cfg(target_os = "fuchsia")]
18use log::info;
19
20use log::{error, warn};
21use net_types::ethernet::Mac as MacAddr;
22use net_types::ip::{Ipv4, PrefixLength};
23use serde::{Deserialize, Serialize};
24use std::collections::{BTreeSet, HashMap};
25use std::net::Ipv4Addr;
26use thiserror::Error;
27
28/// A minimal DHCP server.
29///
30/// This comment will be expanded upon in future CLs as the server design
31/// is iterated upon.
32pub struct Server<DS: DataStore, TS: SystemTimeSource = StdSystemTime> {
33    records: ClientRecords,
34    pool: AddressPool,
35    params: ServerParameters,
36    store: Option<DS>,
37    options_repo: HashMap<OptionCode, DhcpOption>,
38    time_source: TS,
39}
40
41// An interface for Server to retrieve the current time.
42pub trait SystemTimeSource {
43    fn with_current_time() -> Self;
44    fn now(&self) -> std::time::SystemTime;
45}
46
47// SystemTimeSource that uses std::time::SystemTime::now().
48pub struct StdSystemTime;
49
50impl SystemTimeSource for StdSystemTime {
51    fn with_current_time() -> Self {
52        StdSystemTime
53    }
54
55    fn now(&self) -> std::time::SystemTime {
56        std::time::SystemTime::now()
57    }
58}
59
60/// An interface for storing and loading DHCP server data.
61pub trait DataStore {
62    type Error: std::error::Error + std::marker::Send + std::marker::Sync + 'static;
63
64    /// Inserts the client record associated with the identifier.
65    fn insert(
66        &mut self,
67        client_id: &ClientIdentifier,
68        record: &LeaseRecord,
69    ) -> Result<(), Self::Error>;
70
71    /// Stores the DHCP option values served by the server.
72    fn store_options(&mut self, opts: &[DhcpOption]) -> Result<(), Self::Error>;
73
74    /// Stores the DHCP server's configuration parameters.
75    fn store_parameters(&mut self, params: &ServerParameters) -> Result<(), Self::Error>;
76
77    /// Deletes the client record associated with the identifier.
78    fn delete(&mut self, client_id: &ClientIdentifier) -> Result<(), Self::Error>;
79}
80
81/// The default string used by the Server to identify itself to the Stash service.
82pub const DEFAULT_STASH_ID: &str = "dhcpd";
83
84/// This enumerates the actions a DHCP server can take in response to a
85/// received client message. A `SendResponse(Message, Ipv4Addr)` indicates
86/// that a `Message` needs to be delivered back to the client.
87/// The server may optionally send a destination `Ipv4Addr` (if the protocol
88/// warrants it) to direct the response `Message` to.
89/// The other two variants indicate a successful processing of a client
90/// `Decline` or `Release`.
91/// Implements `PartialEq` for test assertions.
92#[derive(Debug, PartialEq)]
93pub enum ServerAction {
94    SendResponse(Message, ResponseTarget),
95    AddressDecline(Ipv4Addr),
96    AddressRelease(Ipv4Addr),
97}
98
99/// The destinations to which a response can be targeted. A `Broadcast`
100/// will be targeted to the IPv4 Broadcast address. A `Unicast` will be
101/// targeted to its `Ipv4Addr` associated value. If a `MacAddr` is supplied,
102/// the target may not yet have the `Ipv4Addr` assigned, so the response
103/// should be manually directed to the `MacAddr`, typically by updating the
104/// ARP cache.
105#[derive(Debug, PartialEq)]
106pub enum ResponseTarget {
107    Broadcast,
108    Unicast(Ipv4Addr, Option<MacAddr>),
109}
110
111/// A wrapper around the error types which can be returned by DHCP Server
112/// in response to client requests.
113/// Implements `PartialEq` for test assertions.
114#[derive(Debug, Error, PartialEq)]
115pub enum ServerError {
116    #[error("unexpected client message type: {}", _0)]
117    UnexpectedClientMessageType(MessageType),
118
119    #[error("requested ip parsing failure: {}", _0)]
120    BadRequestedIpv4Addr(String),
121
122    #[error("local address pool manipulation error: {}", _0)]
123    ServerAddressPoolFailure(AddressPoolError),
124
125    #[error("incorrect server ip in client message: {}", _0)]
126    IncorrectDHCPServer(Ipv4Addr),
127
128    #[error("requested ip mismatch with offered ip: {} {}", _0, _1)]
129    RequestedIpOfferIpMismatch(Ipv4Addr, Ipv4Addr),
130
131    #[error("expired client lease record")]
132    ExpiredLeaseRecord,
133
134    #[error("requested ip absent from server pool: {}", _0)]
135    UnidentifiedRequestedIp(Ipv4Addr),
136
137    #[error("unknown client identifier: {}", _0)]
138    UnknownClientId(ClientIdentifier),
139
140    #[error("init reboot request did not include ip")]
141    NoRequestedAddrAtInitReboot,
142
143    #[error("unidentified client state during request")]
144    UnknownClientStateDuringRequest,
145
146    #[error("decline request did not include ip")]
147    NoRequestedAddrForDecline,
148
149    #[error("client request error: {}", _0)]
150    ClientMessageError(ProtocolError),
151
152    #[error("error manipulating server data store: {}", _0)]
153    DataStoreUpdateFailure(DataStoreError),
154
155    #[error("server not configured with an ip address")]
156    ServerMissingIpAddr,
157
158    #[error("missing required dhcp option: {:?}", _0)]
159    MissingRequiredDhcpOption(OptionCode),
160
161    #[error("missing server identifier in response")]
162    // According to RFC 2131, page 28, all server responses MUST include server identifier.
163    //
164    // https://tools.ietf.org/html/rfc2131#page-29
165    MissingServerIdentifier,
166
167    #[error("unable to get system time")]
168    // The underlying error is not provided to this variant as it (std::time::SystemTimeError) does
169    // not implement PartialEq.
170    ServerTimeError,
171
172    #[error("inconsistent initial server state: {}", _0)]
173    InconsistentInitialServerState(AddressPoolError),
174
175    #[error("client request message missing requested ip addr")]
176    MissingRequestedAddr,
177
178    #[error("decline from unrecognized client: {:?}", _0)]
179    DeclineFromUnrecognizedClient(ClientIdentifier),
180
181    #[error(
182        "declined ip mismatched with lease: got declined addr {:?}, want client addr {:?}",
183        declined,
184        client
185    )]
186    DeclineIpMismatch { declined: Option<Ipv4Addr>, client: Option<Ipv4Addr> },
187}
188
189impl From<AddressPoolError> for ServerError {
190    fn from(e: AddressPoolError) -> Self {
191        ServerError::ServerAddressPoolFailure(e)
192    }
193}
194
195/// This struct is used to hold the error returned by the server's
196/// DataStore manipulation methods. We manually implement `PartialEq` so this
197/// struct could be included in the `ServerError` enum,
198/// which are asserted for equality in tests.
199#[derive(Debug, Error)]
200#[error(transparent)]
201pub struct DataStoreError(#[from] anyhow::Error);
202
203impl PartialEq for DataStoreError {
204    fn eq(&self, _other: &Self) -> bool {
205        false
206    }
207}
208
209impl<DS: DataStore, TS: SystemTimeSource> Server<DS, TS> {
210    /// Attempts to instantiate a new `Server` value from the persisted state contained in the
211    /// provided parts. If the client leases and address pool contained in the provided parts are
212    /// inconsistent with one another, then instantiation will fail.
213    pub fn new_from_state(
214        store: DS,
215        params: ServerParameters,
216        options_repo: HashMap<OptionCode, DhcpOption>,
217        records: ClientRecords,
218    ) -> Result<Self, Error> {
219        Self::new_with_time_source(store, params, options_repo, records, TS::with_current_time())
220    }
221
222    pub fn new_with_time_source(
223        store: DS,
224        params: ServerParameters,
225        options_repo: HashMap<OptionCode, DhcpOption>,
226        records: ClientRecords,
227        time_source: TS,
228    ) -> Result<Self, Error> {
229        let mut pool = AddressPool::new(params.managed_addrs.pool_range());
230        for client_addr in records.iter().filter_map(|(_id, LeaseRecord { current, .. })| *current)
231        {
232            let () = pool
233                .allocate_addr(client_addr)
234                .map_err(ServerError::InconsistentInitialServerState)?;
235        }
236        let mut server =
237            Self { records, pool, params, store: Some(store), options_repo, time_source };
238        let () = server.release_expired_leases()?;
239        Ok(server)
240    }
241
242    /// Instantiates a new `Server`, without persisted state, from the supplied parameters.
243    pub fn new(store: Option<DS>, params: ServerParameters) -> Self {
244        Self {
245            records: HashMap::new(),
246            pool: AddressPool::new(params.managed_addrs.pool_range()),
247            params,
248            store,
249            options_repo: HashMap::new(),
250            time_source: TS::with_current_time(),
251        }
252    }
253
254    /// Dispatches an incoming DHCP message to the appropriate handler for processing.
255    ///
256    /// If the incoming message is a valid client DHCP message, then the server will attempt to
257    /// take appropriate action to serve the client's request, update the internal server state,
258    /// and return the suitable response.
259    /// If the incoming message is invalid, or the server is unable to serve the request,
260    /// or the processing of the client's request resulted in an error, then `dispatch()`
261    /// will return the fitting `Err` indicating what went wrong.
262    pub fn dispatch(&mut self, msg: Message) -> Result<ServerAction, ServerError> {
263        match msg.get_dhcp_type().map_err(ServerError::ClientMessageError)? {
264            MessageType::DHCPDISCOVER => self.handle_discover(msg),
265            MessageType::DHCPOFFER => {
266                Err(ServerError::UnexpectedClientMessageType(MessageType::DHCPOFFER))
267            }
268            MessageType::DHCPREQUEST => self.handle_request(msg),
269            MessageType::DHCPDECLINE => self.handle_decline(msg),
270            MessageType::DHCPACK => {
271                Err(ServerError::UnexpectedClientMessageType(MessageType::DHCPACK))
272            }
273            MessageType::DHCPNAK => {
274                Err(ServerError::UnexpectedClientMessageType(MessageType::DHCPNAK))
275            }
276            MessageType::DHCPRELEASE => self.handle_release(msg),
277            MessageType::DHCPINFORM => self.handle_inform(msg),
278        }
279    }
280
281    /// This method calculates the destination address of the server response
282    /// based on the conditions specified in -
283    /// https://tools.ietf.org/html/rfc2131#section-4.1 Page 22, Paragraph 4.
284    fn get_destination(&mut self, client_msg: &Message, offered: Ipv4Addr) -> ResponseTarget {
285        if !client_msg.giaddr.is_unspecified() {
286            ResponseTarget::Unicast(client_msg.giaddr, None)
287        } else if !client_msg.ciaddr.is_unspecified() {
288            ResponseTarget::Unicast(client_msg.ciaddr, None)
289        } else if client_msg.bdcast_flag {
290            ResponseTarget::Broadcast
291        } else {
292            ResponseTarget::Unicast(offered, Some(client_msg.chaddr))
293        }
294    }
295
296    fn handle_discover(&mut self, disc: Message) -> Result<ServerAction, ServerError> {
297        let () = validate_discover(&disc)?;
298        let client_id = ClientIdentifier::from(&disc);
299        let offered = self.get_offered(&disc)?;
300        let dest = self.get_destination(&disc, offered);
301        let offer = self.build_offer(disc, offered)?;
302        match self.store_client_record(offered, client_id, &offer.options) {
303            Ok(()) => Ok(ServerAction::SendResponse(offer, dest)),
304            Err(e) => Err(ServerError::DataStoreUpdateFailure(e.into())),
305        }
306    }
307
308    // Determine the address to offer to the client.
309    //
310    // This function follows the address offer algorithm in
311    // https://tools.ietf.org/html/rfc2131#section-4.3.1:
312    //
313    // If an address is available, the new address SHOULD be chosen as follows:
314    //
315    // o The client's current address as recorded in the client's current
316    // binding, ELSE
317    //
318    // o The client's previous address as recorded in the client's (now
319    // expired or released) binding, if that address is in the server's
320    // pool of available addresses and not already allocated, ELSE
321    //
322    // o The address requested in the 'Requested IP Address' option, if that
323    // address is valid and not already allocated, ELSE
324    //
325    // o A new address allocated from the server's pool of available
326    // addresses; the address is selected based on the subnet from which
327    // the message was received (if 'giaddr' is 0) or on the address of
328    // the relay agent that forwarded the message ('giaddr' when not 0).
329    fn get_offered(&mut self, client: &Message) -> Result<Ipv4Addr, ServerError> {
330        let id = ClientIdentifier::from(client);
331        if let Some(LeaseRecord { current, previous, .. }) = self.records.get(&id) {
332            if let Some(current) = current {
333                if !self.pool.addr_is_allocated(*current) {
334                    panic!("address {} from active lease is unallocated in address pool", current);
335                }
336                return Ok(*current);
337            }
338            if let Some(previous) = previous {
339                if self.pool.addr_is_available(*previous) {
340                    return Ok(*previous);
341                }
342            }
343        }
344        if let Some(requested_addr) = get_requested_ip_addr(&client) {
345            if self.pool.addr_is_available(requested_addr) {
346                return Ok(requested_addr);
347            }
348        }
349        // TODO(https://fxbug.dev/42095285): The ip should be handed out based on
350        // client subnet. Currently, the server blindly hands out the next
351        // available ip from its available ip pool, without any subnet analysis.
352        if let Some(addr) = self.pool.available().next() {
353            return Ok(addr);
354        }
355        let () = self.release_expired_leases()?;
356        if let Some(addr) = self.pool.available().next() {
357            return Ok(addr);
358        }
359        Err(ServerError::ServerAddressPoolFailure(AddressPoolError::Ipv4AddrExhaustion))
360    }
361
362    fn store_client_record(
363        &mut self,
364        offered: Ipv4Addr,
365        client_id: ClientIdentifier,
366        client_opts: &[DhcpOption],
367    ) -> Result<(), Error> {
368        let lease_length_seconds = client_opts
369            .iter()
370            .find_map(|opt| match opt {
371                DhcpOption::IpAddressLeaseTime(v) => Some(*v),
372                _ => None,
373            })
374            .ok_or(ServerError::MissingRequiredDhcpOption(OptionCode::IpAddressLeaseTime))?;
375        let options = client_opts
376            .iter()
377            .filter(|opt| {
378                // DhcpMessageType is part of transaction semantics and should not be stored.
379                opt.code() != OptionCode::DhcpMessageType
380            })
381            .cloned()
382            .collect();
383        let record =
384            LeaseRecord::new(Some(offered), options, self.time_source.now(), lease_length_seconds)?;
385        let Self { records, pool, store, .. } = self;
386        let entry = records.entry(client_id);
387        let current = match &entry {
388            std::collections::hash_map::Entry::Occupied(occupied) => {
389                let LeaseRecord { current, .. } = occupied.get();
390                *current
391            }
392            std::collections::hash_map::Entry::Vacant(_vacant) => None,
393        };
394        let () = match current {
395            Some(current) => {
396                // If there is a lease record with a currently leased address, and the offered address
397                // does not match that leased address, then the offered address was calculated contrary
398                // to RFC2131#section4.3.1.
399                assert_eq!(
400                    current, offered,
401                    "server offered address does not match address in lease record"
402                );
403            }
404            None => {
405                match pool.allocate_addr(offered) {
406                    Ok(()) => (),
407                    // An error here indicates that the offered address is already allocated, an
408                    // irrecoverable inconsistency.
409                    Err(e) => panic!("fatal server address allocation failure: {}", e),
410                }
411            }
412        };
413        if let Some(store) = store {
414            let () =
415                store.insert(entry.key(), &record).context("failed to store client in stash")?;
416        }
417        // TODO(https://github.com/rust-lang/rust/issues/65225): use entry.insert.
418        let () = match entry {
419            std::collections::hash_map::Entry::Occupied(mut occupied) => {
420                let _: LeaseRecord = occupied.insert(record);
421            }
422            std::collections::hash_map::Entry::Vacant(vacant) => {
423                let _: &mut LeaseRecord = vacant.insert(record);
424            }
425        };
426        Ok(())
427    }
428
429    fn handle_request(&mut self, req: Message) -> Result<ServerAction, ServerError> {
430        match get_client_state(&req).map_err(|()| ServerError::UnknownClientStateDuringRequest)? {
431            ClientState::Selecting => self.handle_request_selecting(req),
432            ClientState::InitReboot => self.handle_request_init_reboot(req),
433            ClientState::Renewing => self.handle_request_renewing(req),
434        }
435    }
436
437    fn handle_request_selecting(&mut self, req: Message) -> Result<ServerAction, ServerError> {
438        let requested_ip = get_requested_ip_addr(&req)
439            .ok_or(ServerError::MissingRequiredDhcpOption(OptionCode::RequestedIpAddress))?;
440        if !is_recipient(&self.params.server_ips, &req) {
441            Err(ServerError::IncorrectDHCPServer(
442                *self.params.server_ips.first().ok_or(ServerError::ServerMissingIpAddr)?,
443            ))
444        } else {
445            self.build_response(req, requested_ip)
446        }
447    }
448
449    fn build_response(
450        &mut self,
451        req: Message,
452        requested_ip: Ipv4Addr,
453    ) -> Result<ServerAction, ServerError> {
454        match self.validate_requested_addr_with_client(&req, requested_ip) {
455            Ok(()) => {
456                let dest = self.get_destination(&req, requested_ip);
457                Ok(ServerAction::SendResponse(self.build_ack(req, requested_ip)?, dest))
458            }
459            Err(e) => {
460                let (nak, dest) = self.build_nak(req, NakReason::ClientValidationFailure(e))?;
461                Ok(ServerAction::SendResponse(nak, dest))
462            }
463        }
464    }
465
466    /// The function below validates if the `requested_ip` is correctly
467    /// associated with the client whose request `req` is being processed.
468    ///
469    /// It first checks if the client bindings can be found in server records.
470    /// If not, the association is wrong and it returns an `Err()`.
471    ///
472    /// If the server can correctly locate the client bindings in its records,
473    /// it further verifies if the `requested_ip` is the same as the ip address
474    /// represented in the bindings and the binding is not expired and that the
475    /// `requested_ip` is no longer available in the server address pool. If
476    /// all the above conditions are met, it returns an `Ok(())` else the
477    /// appropriate `Err()` value is returned.
478    fn validate_requested_addr_with_client(
479        &self,
480        req: &Message,
481        requested_ip: Ipv4Addr,
482    ) -> Result<(), ServerError> {
483        let client_id = ClientIdentifier::from(req);
484        if let Some(record) = self.records.get(&client_id) {
485            let now = self
486                .time_source
487                .now()
488                .duration_since(std::time::UNIX_EPOCH)
489                .map_err(|std::time::SystemTimeError { .. }| ServerError::ServerTimeError)?;
490            if let Some(client_addr) = record.current {
491                if client_addr != requested_ip {
492                    Err(ServerError::RequestedIpOfferIpMismatch(requested_ip, client_addr))
493                } else if record.expired(now) {
494                    Err(ServerError::ExpiredLeaseRecord)
495                } else if !self.pool.addr_is_allocated(requested_ip) {
496                    Err(ServerError::UnidentifiedRequestedIp(requested_ip))
497                } else {
498                    Ok(())
499                }
500            } else {
501                Err(ServerError::MissingRequestedAddr)
502            }
503        } else {
504            Err(ServerError::UnknownClientId(client_id))
505        }
506    }
507
508    fn handle_request_init_reboot(&mut self, req: Message) -> Result<ServerAction, ServerError> {
509        let requested_ip =
510            get_requested_ip_addr(&req).ok_or(ServerError::NoRequestedAddrAtInitReboot)?;
511        if !is_in_subnet(&req, &self.params) {
512            let (nak, dest) = self.build_nak(req, NakReason::DifferentSubnets)?;
513            return Ok(ServerAction::SendResponse(nak, dest));
514        }
515        let client_id = ClientIdentifier::from(&req);
516        if !self.records.contains_key(&client_id) {
517            return Err(ServerError::UnknownClientId(client_id));
518        }
519        self.build_response(req, requested_ip)
520    }
521
522    fn handle_request_renewing(&mut self, req: Message) -> Result<ServerAction, ServerError> {
523        let client_ip = req.ciaddr;
524        self.build_response(req, client_ip)
525    }
526
527    // RFC 2131 provides limited guidance for implementation of DHCPDECLINE handling. From
528    // https://tools.ietf.org/html/rfc2131#section-4.3.3:
529    //
530    //   If the server receives a DHCPDECLINE message... The server MUST mark the network address
531    //   as not available...
532    //
533    // However, the RFC does not specify what a valid DHCPDECLINE message looks like. If all
534    // DHCPDECLINE messages are acted upon, then the server will be exposed to DoS attacks.
535    //
536    // We define a valid DHCPDECLINE message as:
537    //   * ServerIdentifier matches the server
538    //   * server has a record of a lease to the client
539    //   * the declined IP matches the leased IP
540    //
541    // Only if those three conditions obtain, the server will then invalidate the lease and mark
542    // the address as allocated and unavailable for assignment (if it isn't already).
543    fn handle_decline(&mut self, dec: Message) -> Result<ServerAction, ServerError> {
544        let Self { records, params, pool, store, .. } = self;
545        let declined_ip =
546            get_requested_ip_addr(&dec).ok_or_else(|| ServerError::NoRequestedAddrForDecline)?;
547        let id = ClientIdentifier::from(&dec);
548        if !is_recipient(&params.server_ips, &dec) {
549            return Err(ServerError::IncorrectDHCPServer(
550                get_server_id_from(&dec).ok_or(ServerError::MissingServerIdentifier)?,
551            ));
552        }
553        let entry = match records.entry(id) {
554            std::collections::hash_map::Entry::Occupied(v) => v,
555            std::collections::hash_map::Entry::Vacant(v) => {
556                return Err(ServerError::DeclineFromUnrecognizedClient(v.into_key()));
557            }
558        };
559        let LeaseRecord { current, .. } = entry.get();
560        if *current != Some(declined_ip) {
561            return Err(ServerError::DeclineIpMismatch {
562                declined: Some(declined_ip),
563                client: *current,
564            });
565        }
566        // The declined address must be marked allocated/unavailable. Depending on whether the
567        // client declines the address after an OFFER or an ACK, a declined address may already be
568        // marked allocated. Attempt to allocate the declined address, but treat the address
569        // already being allocated as success.
570        let () = pool.allocate_addr(declined_ip).or_else(|e| match e {
571            AddressPoolError::AllocatedIpv4AddrAllocation(ip) if ip == declined_ip => Ok(()),
572            e @ AddressPoolError::Ipv4AddrExhaustion
573            | e @ AddressPoolError::AllocatedIpv4AddrAllocation(Ipv4Addr { .. })
574            | e @ AddressPoolError::UnallocatedIpv4AddrRelease(Ipv4Addr { .. })
575            | e @ AddressPoolError::UnmanagedIpv4Addr(Ipv4Addr { .. }) => Err(e),
576        })?;
577        let (id, LeaseRecord { .. }) = entry.remove_entry();
578        if let Some(store) = store {
579            let () = store
580                .delete(&id)
581                .map_err(|e| ServerError::DataStoreUpdateFailure(anyhow::Error::from(e).into()))?;
582        }
583        Ok(ServerAction::AddressDecline(declined_ip))
584    }
585
586    fn handle_release(&mut self, rel: Message) -> Result<ServerAction, ServerError> {
587        let Self { records, pool, store, .. } = self;
588        let client_id = ClientIdentifier::from(&rel);
589        if let Some(record) = records.get_mut(&client_id) {
590            // From https://tools.ietf.org/html/rfc2131#section-4.3.4:
591            //
592            // Upon receipt of a DHCPRELEASE message, the server marks the network address as not
593            // allocated.  The server SHOULD retain a record of the client's initialization
594            // parameters for possible reuse in response to subsequent requests from the client.
595            let () = release_leased_addr(&client_id, record, pool, store)?;
596            Ok(ServerAction::AddressRelease(rel.ciaddr))
597        } else {
598            Err(ServerError::UnknownClientId(client_id))
599        }
600    }
601
602    fn handle_inform(&mut self, inf: Message) -> Result<ServerAction, ServerError> {
603        // When responding to an INFORM, the server must leave yiaddr zeroed.
604        let yiaddr = Ipv4Addr::UNSPECIFIED;
605        let dest = self.get_destination(&inf, inf.ciaddr);
606        let ack = self.build_inform_ack(inf, yiaddr)?;
607        Ok(ServerAction::SendResponse(ack, dest))
608    }
609
610    fn build_offer(&self, disc: Message, offered_ip: Ipv4Addr) -> Result<Message, ServerError> {
611        let server_ip = self.get_server_ip(&disc)?;
612        build_offer(
613            disc,
614            OfferOptions {
615                offered_ip,
616                server_ip,
617                lease_length_config: self.params.lease_length.clone(),
618                renewal_time_value: self.options_repo.get(&OptionCode::RenewalTimeValue).map(|v| {
619                    match v {
620                        DhcpOption::RenewalTimeValue(v) => *v,
621                        v => panic!(
622                            "options repo contains code-value mismatch: key={:?} value={:?}",
623                            &OptionCode::RenewalTimeValue,
624                            v
625                        ),
626                    }
627                }),
628                rebinding_time_value: self.options_repo.get(&OptionCode::RebindingTimeValue).map(
629                    |v| match v {
630                        DhcpOption::RebindingTimeValue(v) => *v,
631                        v => panic!(
632                            "options repo contains code-value mismatch: key={:?} value={:?}",
633                            &OptionCode::RenewalTimeValue,
634                            v
635                        ),
636                    },
637                ),
638                subnet_mask: self.params.managed_addrs.mask.into(),
639            },
640            &self.options_repo,
641        )
642    }
643
644    fn get_requested_options(&self, client_opts: &[DhcpOption]) -> Vec<DhcpOption> {
645        get_requested_options(
646            client_opts,
647            &self.options_repo,
648            self.params.managed_addrs.mask.into(),
649        )
650    }
651
652    fn build_ack(&self, req: Message, requested_ip: Ipv4Addr) -> Result<Message, ServerError> {
653        let client_id = ClientIdentifier::from(&req);
654        let options = match self.records.get(&client_id) {
655            Some(record) => {
656                let mut options = Vec::with_capacity(record.options.len() + 1);
657                options.push(DhcpOption::DhcpMessageType(MessageType::DHCPACK));
658                options.extend(record.options.iter().cloned());
659                options
660            }
661            None => return Err(ServerError::UnknownClientId(client_id)),
662        };
663        let ack = Message { op: OpCode::BOOTREPLY, secs: 0, yiaddr: requested_ip, options, ..req };
664        Ok(ack)
665    }
666
667    fn build_inform_ack(&self, inf: Message, client_ip: Ipv4Addr) -> Result<Message, ServerError> {
668        let server_ip = self.get_server_ip(&inf)?;
669        let mut options = Vec::new();
670        options.push(DhcpOption::DhcpMessageType(MessageType::DHCPACK));
671        options.push(DhcpOption::ServerIdentifier(server_ip));
672        options.extend_from_slice(&self.get_requested_options(&inf.options));
673        let ack = Message { op: OpCode::BOOTREPLY, secs: 0, yiaddr: client_ip, options, ..inf };
674        Ok(ack)
675    }
676
677    fn build_nak(
678        &self,
679        req: Message,
680        reason: NakReason,
681    ) -> Result<(Message, ResponseTarget), ServerError> {
682        let options = vec![
683            DhcpOption::DhcpMessageType(MessageType::DHCPNAK),
684            DhcpOption::ServerIdentifier(self.get_server_ip(&req)?),
685            DhcpOption::Message(format!("{}", reason)),
686        ];
687        let mut nak = Message {
688            op: OpCode::BOOTREPLY,
689            secs: 0,
690            ciaddr: Ipv4Addr::UNSPECIFIED,
691            yiaddr: Ipv4Addr::UNSPECIFIED,
692            siaddr: Ipv4Addr::UNSPECIFIED,
693            options,
694            ..req
695        };
696        // https://tools.ietf.org/html/rfc2131#section-4.3.2
697        // Page 31, Paragraph 2-3.
698        if nak.giaddr.is_unspecified() {
699            Ok((nak, ResponseTarget::Broadcast))
700        } else {
701            nak.bdcast_flag = true;
702            let giaddr = nak.giaddr;
703            Ok((nak, ResponseTarget::Unicast(giaddr, None)))
704        }
705    }
706
707    /// Determines the server identifier to use in DHCP responses. This
708    /// identifier is also the address the server should use to communicate with
709    /// the client.
710    ///
711    /// RFC 2131, Section 4.1, https://tools.ietf.org/html/rfc2131#section-4.1
712    ///
713    ///   The 'server identifier' field is used both to identify a DHCP server
714    ///   in a DHCP message and as a destination address from clients to
715    ///   servers.  A server with multiple network addresses MUST be prepared
716    ///   to to accept any of its network addresses as identifying that server
717    ///   in a DHCP message.  To accommodate potentially incomplete network
718    ///   connectivity, a server MUST choose an address as a 'server
719    ///   identifier' that, to the best of the server's knowledge, is reachable
720    ///   from the client.  For example, if the DHCP server and the DHCP client
721    ///   are connected to the same subnet (i.e., the 'giaddr' field in the
722    ///   message from the client is zero), the server SHOULD select the IP
723    ///   address the server is using for communication on that subnet as the
724    ///   'server identifier'.
725    fn get_server_ip(&self, req: &Message) -> Result<Ipv4Addr, ServerError> {
726        match get_server_id_from(&req) {
727            Some(addr) => {
728                if self.params.server_ips.contains(&addr) {
729                    Ok(addr)
730                } else {
731                    Err(ServerError::IncorrectDHCPServer(addr))
732                }
733            }
734            // TODO(https://fxbug.dev/42095285): This IP should be chosen based on the
735            // subnet of the client.
736            None => Ok(*self.params.server_ips.first().ok_or(ServerError::ServerMissingIpAddr)?),
737        }
738    }
739
740    /// Releases all allocated IP addresses whose leases have expired back to
741    /// the pool of addresses available for allocation.
742    fn release_expired_leases(&mut self) -> Result<(), ServerError> {
743        let Self { records, pool, time_source, store, .. } = self;
744        let now = time_source
745            .now()
746            .duration_since(std::time::UNIX_EPOCH)
747            .map_err(|std::time::SystemTimeError { .. }| ServerError::ServerTimeError)?;
748        records
749            .iter_mut()
750            .filter(|(_id, record)| record.current.is_some() && record.expired(now))
751            .try_for_each(|(id, record)| {
752                let () = match release_leased_addr(id, record, pool, store) {
753                    Ok(()) => (),
754                    // Panic because server's state is irrecoverably inconsistent.
755                    Err(ServerError::ServerAddressPoolFailure(e)) => {
756                        panic!("fatal inconsistency in server address pool: {}", e)
757                    }
758                    Err(ServerError::DataStoreUpdateFailure(e)) => {
759                        warn!("failed to update data store: {}", e)
760                    }
761                    Err(e) => return Err(e),
762                };
763                Ok(())
764            })
765    }
766
767    #[cfg(target_os = "fuchsia")]
768    /// Saves current parameters to stash.
769    fn save_params(&mut self) -> Result<(), Status> {
770        if let Some(store) = self.store.as_mut() {
771            store.store_parameters(&self.params).map_err(|e| {
772                warn!("store_parameters({:?}) in stash failed: {}", self.params, e);
773                zx::Status::INTERNAL
774            })
775        } else {
776            Ok(())
777        }
778    }
779}
780
781/// Helper for constructing a repo of `DhcpOption`s.
782pub fn options_repo(
783    options: impl IntoIterator<Item = DhcpOption>,
784) -> HashMap<OptionCode, DhcpOption> {
785    options.into_iter().map(|option| (option.code(), option)).collect()
786}
787
788/// Parameters needed in order to build a DHCPOFFER.
789pub struct OfferOptions {
790    pub offered_ip: Ipv4Addr,
791    pub server_ip: Ipv4Addr,
792    pub lease_length_config: crate::configuration::LeaseLength,
793    pub renewal_time_value: Option<u32>,
794    pub rebinding_time_value: Option<u32>,
795    pub subnet_mask: PrefixLength<Ipv4>,
796}
797
798/// Builds a DHCPOFFER in response to the given DHCPDISCOVER using the provided
799/// `offer_options` and `options_repo`.
800pub fn build_offer(
801    disc: Message,
802    offer_options: OfferOptions,
803    options_repo: &HashMap<OptionCode, DhcpOption>,
804) -> Result<Message, ServerError> {
805    let OfferOptions {
806        offered_ip,
807        server_ip,
808        lease_length_config:
809            crate::configuration::LeaseLength {
810                default_seconds: default_lease_length_seconds,
811                max_seconds: max_lease_length_seconds,
812            },
813        renewal_time_value,
814        rebinding_time_value,
815        subnet_mask,
816    } = offer_options;
817    let mut options = Vec::new();
818    options.push(DhcpOption::DhcpMessageType(MessageType::DHCPOFFER));
819    options.push(DhcpOption::ServerIdentifier(server_ip));
820    let lease_length = match disc.options.iter().find_map(|opt| match opt {
821        DhcpOption::IpAddressLeaseTime(seconds) => Some(*seconds),
822        _ => None,
823    }) {
824        Some(seconds) => std::cmp::min(seconds, max_lease_length_seconds),
825        None => default_lease_length_seconds,
826    };
827    options.push(DhcpOption::IpAddressLeaseTime(lease_length));
828    let v = renewal_time_value.unwrap_or(lease_length / 2);
829    options.push(DhcpOption::RenewalTimeValue(v));
830    let v =
831        rebinding_time_value.unwrap_or_else(|| (lease_length / 4) * 3 + (lease_length % 4) * 3 / 4);
832    options.push(DhcpOption::RebindingTimeValue(v));
833    options.extend_from_slice(&get_requested_options(&disc.options, &options_repo, subnet_mask));
834    let offer = Message {
835        op: OpCode::BOOTREPLY,
836        secs: 0,
837        yiaddr: offered_ip,
838        ciaddr: Ipv4Addr::UNSPECIFIED,
839        siaddr: Ipv4Addr::UNSPECIFIED,
840        sname: String::new(),
841        file: String::new(),
842        options,
843        ..disc
844    };
845    Ok(offer)
846}
847
848/// Given the DHCP options set by the client, retrieves the values of the DHCP
849/// options requested by the client.
850pub fn get_requested_options(
851    client_opts: &[DhcpOption],
852    options_repo: &HashMap<OptionCode, DhcpOption>,
853    subnet_mask: PrefixLength<Ipv4>,
854) -> Vec<DhcpOption> {
855    // TODO(https://fxbug.dev/42056025): We should consider always supplying the
856    // SubnetMask for all DHCPDISCOVER and DHCPREQUEST requests. ISC
857    // does this, and we may desire to for increased compatibility with
858    // non-compliant clients.
859    //
860    // See: https://github.com/isc-projects/dhcp/commit/e9c5964
861
862    let prl = client_opts.iter().find_map(|opt| match opt {
863        DhcpOption::ParameterRequestList(v) => Some(v),
864        _ => None,
865    });
866    prl.map_or(Vec::new(), |requested_opts| {
867        let mut offered_opts: Vec<DhcpOption> = requested_opts
868            .iter()
869            .filter_map(|code| match options_repo.get(code) {
870                Some(opt) => Some(opt.clone()),
871                None => match code {
872                    OptionCode::SubnetMask => Some(DhcpOption::SubnetMask(subnet_mask)),
873                    _ => None,
874                },
875            })
876            .collect();
877
878        //  Enforce ordering SUBNET_MASK by moving it before ROUTER.
879        //  See: https://datatracker.ietf.org/doc/html/rfc2132#section-3.3
880        //
881        //      If both the subnet mask and the router option are specified
882        //      in a DHCP reply, the subnet mask option MUST be first.
883        let mut router_position = None;
884        for (i, option) in offered_opts.iter().enumerate() {
885            match option {
886                DhcpOption::Router(_) => router_position = Some(i),
887                DhcpOption::SubnetMask(_) => {
888                    if let Some(router_index) = router_position {
889                        offered_opts[router_index..(i + 1)].rotate_right(1)
890                    }
891                    // Once we find the subnet mask, we can bail on the for loop.
892                    break;
893                }
894                _ => continue,
895            }
896        }
897
898        offered_opts
899    })
900}
901
902// TODO(https://fxbug.dev/42154741): Find an alternative to panicking.
903fn release_leased_addr<DS: DataStore>(
904    id: &ClientIdentifier,
905    record: &mut LeaseRecord,
906    pool: &mut AddressPool,
907    store: &mut Option<DS>,
908) -> Result<(), ServerError> {
909    if let Some(addr) = record.current.take() {
910        record.previous = Some(addr);
911        let () = pool.release_addr(addr)?;
912        if let Some(store) = store {
913            let () = store
914                .insert(id, record)
915                .map_err(|e| ServerError::DataStoreUpdateFailure(anyhow::Error::from(e).into()))?;
916        }
917    } else {
918        panic!("attempted to release lease that has already been released: {:?}", record);
919    }
920    Ok(())
921}
922
923#[cfg(target_os = "fuchsia")]
924/// The ability to dispatch fuchsia.net.dhcp.Server protocol requests and return a value.
925///
926/// Implementers of this trait can be used as the backing server-side logic of the
927/// fuchsia.net.dhcp.Server protocol. Implementers must maintain a store of DHCP Options, DHCP
928/// server parameters, and leases issued to clients, and support the trait methods to retrieve and
929/// modify these stores.
930pub trait ServerDispatcher {
931    /// Validates the current set of server parameters returning a reference to
932    /// the parameters if the configuration is valid or an error otherwise.
933    fn try_validate_parameters(&self) -> Result<&ServerParameters, Status>;
934
935    /// Retrieves the stored DHCP option value that corresponds to the OptionCode argument.
936    fn dispatch_get_option(
937        &self,
938        code: fidl_fuchsia_net_dhcp::OptionCode,
939    ) -> Result<fidl_fuchsia_net_dhcp::Option_, Status>;
940    /// Retrieves the stored DHCP server parameter value that corresponds to the ParameterName argument.
941    fn dispatch_get_parameter(
942        &self,
943        name: fidl_fuchsia_net_dhcp::ParameterName,
944    ) -> Result<fidl_fuchsia_net_dhcp::Parameter, Status>;
945    /// Updates the stored DHCP option value to the argument.
946    fn dispatch_set_option(&mut self, value: fidl_fuchsia_net_dhcp::Option_) -> Result<(), Status>;
947    /// Updates the stored DHCP server parameter to the argument.
948    fn dispatch_set_parameter(
949        &mut self,
950        value: fidl_fuchsia_net_dhcp::Parameter,
951    ) -> Result<(), Status>;
952    /// Retrieves all of the stored DHCP option values.
953    fn dispatch_list_options(&self) -> Result<Vec<fidl_fuchsia_net_dhcp::Option_>, Status>;
954    /// Retrieves all of the stored DHCP parameter values.
955    fn dispatch_list_parameters(&self) -> Result<Vec<fidl_fuchsia_net_dhcp::Parameter>, Status>;
956    /// Resets all DHCP options to have no value.
957    fn dispatch_reset_options(&mut self) -> Result<(), Status>;
958    /// Resets all DHCP server parameters to their default values in `defaults`.
959    fn dispatch_reset_parameters(&mut self, defaults: &ServerParameters) -> Result<(), Status>;
960    /// Clears all leases from the store maintained by the ServerDispatcher.
961    fn dispatch_clear_leases(&mut self) -> Result<(), Status>;
962}
963
964#[cfg(target_os = "fuchsia")]
965impl<DS: DataStore, TS: SystemTimeSource> ServerDispatcher for Server<DS, TS> {
966    fn try_validate_parameters(&self) -> Result<&ServerParameters, Status> {
967        if !self.params.is_valid() {
968            return Err(Status::INVALID_ARGS);
969        }
970
971        // TODO(https://fxbug.dev/42140964): rethink this check and this function.
972        if self.pool.universe.is_empty() {
973            error!("Server validation failed: Address pool is empty");
974            return Err(Status::INVALID_ARGS);
975        }
976        Ok(&self.params)
977    }
978
979    fn dispatch_get_option(
980        &self,
981        code: fidl_fuchsia_net_dhcp::OptionCode,
982    ) -> Result<fidl_fuchsia_net_dhcp::Option_, Status> {
983        let opt_code =
984            OptionCode::try_from(code as u8).map_err(|_protocol_error| Status::INVALID_ARGS)?;
985        let option = self.options_repo.get(&opt_code).ok_or(Status::NOT_FOUND)?;
986        let option = option.clone();
987        let fidl_option = option.try_into_fidl().map_err(|protocol_error| {
988            warn!(
989                "server dispatcher could not convert dhcp option for fidl transport: {}",
990                protocol_error
991            );
992            Status::INTERNAL
993        })?;
994        Ok(fidl_option)
995    }
996
997    fn dispatch_get_parameter(
998        &self,
999        name: fidl_fuchsia_net_dhcp::ParameterName,
1000    ) -> Result<fidl_fuchsia_net_dhcp::Parameter, Status> {
1001        match name {
1002            fidl_fuchsia_net_dhcp::ParameterName::IpAddrs => {
1003                Ok(fidl_fuchsia_net_dhcp::Parameter::IpAddrs(
1004                    self.params.server_ips.clone().into_fidl(),
1005                ))
1006            }
1007            fidl_fuchsia_net_dhcp::ParameterName::AddressPool => {
1008                Ok(fidl_fuchsia_net_dhcp::Parameter::AddressPool(
1009                    self.params.managed_addrs.clone().into_fidl(),
1010                ))
1011            }
1012            fidl_fuchsia_net_dhcp::ParameterName::LeaseLength => {
1013                Ok(fidl_fuchsia_net_dhcp::Parameter::Lease(
1014                    self.params.lease_length.clone().into_fidl(),
1015                ))
1016            }
1017            fidl_fuchsia_net_dhcp::ParameterName::PermittedMacs => {
1018                Ok(fidl_fuchsia_net_dhcp::Parameter::PermittedMacs(
1019                    self.params.permitted_macs.clone().into_fidl(),
1020                ))
1021            }
1022            fidl_fuchsia_net_dhcp::ParameterName::StaticallyAssignedAddrs => {
1023                Ok(fidl_fuchsia_net_dhcp::Parameter::StaticallyAssignedAddrs(
1024                    self.params.static_assignments.clone().into_fidl(),
1025                ))
1026            }
1027            fidl_fuchsia_net_dhcp::ParameterName::ArpProbe => {
1028                Ok(fidl_fuchsia_net_dhcp::Parameter::ArpProbe(self.params.arp_probe))
1029            }
1030            fidl_fuchsia_net_dhcp::ParameterName::BoundDeviceNames => {
1031                Ok(fidl_fuchsia_net_dhcp::Parameter::BoundDeviceNames(
1032                    self.params.bound_device_names.clone(),
1033                ))
1034            }
1035        }
1036    }
1037
1038    fn dispatch_set_option(&mut self, value: fidl_fuchsia_net_dhcp::Option_) -> Result<(), Status> {
1039        let option = DhcpOption::try_from_fidl(value).map_err(|protocol_error| {
1040            warn!(
1041                "server dispatcher could not convert fidl argument into dhcp option: {}",
1042                protocol_error
1043            );
1044            Status::INVALID_ARGS
1045        })?;
1046        let _old = self.options_repo.insert(option.code(), option);
1047        let opts: Vec<DhcpOption> = self.options_repo.values().cloned().collect();
1048        if let Some(store) = self.store.as_mut() {
1049            let () = store.store_options(&opts).map_err(|e| {
1050                warn!("store_options({:?}) in stash failed: {}", opts, e);
1051                zx::Status::INTERNAL
1052            })?;
1053        }
1054        Ok(())
1055    }
1056
1057    fn dispatch_set_parameter(
1058        &mut self,
1059        value: fidl_fuchsia_net_dhcp::Parameter,
1060    ) -> Result<(), Status> {
1061        let () = match value {
1062            fidl_fuchsia_net_dhcp::Parameter::IpAddrs(ip_addrs) => {
1063                self.params.server_ips = Vec::<Ipv4Addr>::from_fidl(ip_addrs)
1064            }
1065            fidl_fuchsia_net_dhcp::Parameter::AddressPool(managed_addrs) => {
1066                // Be overzealous and do not allow the managed addresses to
1067                // change if we currently have leases.
1068                if !self.records.is_empty() {
1069                    return Err(Status::BAD_STATE);
1070                }
1071
1072                self.params.managed_addrs =
1073                    match crate::configuration::ManagedAddresses::try_from_fidl(managed_addrs) {
1074                        Ok(managed_addrs) => managed_addrs,
1075                        Err(e) => {
1076                            info!(
1077                                "dispatch_set_parameter() got invalid AddressPool argument: {:?}",
1078                                e
1079                            );
1080                            return Err(Status::INVALID_ARGS);
1081                        }
1082                    };
1083                // Update the pool with the new parameters.
1084                self.pool = AddressPool::new(self.params.managed_addrs.pool_range());
1085            }
1086            fidl_fuchsia_net_dhcp::Parameter::Lease(lease_length) => {
1087                self.params.lease_length =
1088                    match crate::configuration::LeaseLength::try_from_fidl(lease_length) {
1089                        Ok(lease_length) => lease_length,
1090                        Err(e) => {
1091                            info!(
1092                                "dispatch_set_parameter() got invalid LeaseLength argument: {}",
1093                                e
1094                            );
1095                            return Err(Status::INVALID_ARGS);
1096                        }
1097                    }
1098            }
1099            fidl_fuchsia_net_dhcp::Parameter::PermittedMacs(permitted_macs) => {
1100                self.params.permitted_macs =
1101                    crate::configuration::PermittedMacs::from_fidl(permitted_macs)
1102            }
1103            fidl_fuchsia_net_dhcp::Parameter::StaticallyAssignedAddrs(static_assignments) => {
1104                self.params.static_assignments =
1105                    match crate::configuration::StaticAssignments::try_from_fidl(static_assignments)
1106                    {
1107                        Ok(static_assignments) => static_assignments,
1108                        Err(e) => {
1109                            info!(
1110                                "dispatch_set_parameter() got invalid StaticallyAssignedAddrs argument: {}",
1111                                e
1112                            );
1113                            return Err(Status::INVALID_ARGS);
1114                        }
1115                    }
1116            }
1117            fidl_fuchsia_net_dhcp::Parameter::ArpProbe(arp_probe) => {
1118                self.params.arp_probe = arp_probe
1119            }
1120            fidl_fuchsia_net_dhcp::Parameter::BoundDeviceNames(bound_device_names) => {
1121                self.params.bound_device_names = bound_device_names
1122            }
1123            fidl_fuchsia_net_dhcp::ParameterUnknown!() => return Err(Status::INVALID_ARGS),
1124        };
1125        let () = self.save_params()?;
1126        Ok(())
1127    }
1128
1129    fn dispatch_list_options(&self) -> Result<Vec<fidl_fuchsia_net_dhcp::Option_>, Status> {
1130        let options = self
1131            .options_repo
1132            .values()
1133            .filter_map(|option| {
1134                option
1135                    .clone()
1136                    .try_into_fidl()
1137                    .map_err(|protocol_error| {
1138                        warn!(
1139                        "server dispatcher could not convert dhcp option for fidl transport: {}",
1140                        protocol_error
1141                    );
1142                        Status::INTERNAL
1143                    })
1144                    .ok()
1145            })
1146            .collect::<Vec<fidl_fuchsia_net_dhcp::Option_>>();
1147        Ok(options)
1148    }
1149
1150    fn dispatch_list_parameters(&self) -> Result<Vec<fidl_fuchsia_net_dhcp::Parameter>, Status> {
1151        // Without this redundant borrow, the compiler will interpret this statement as a moving destructure.
1152        let ServerParameters {
1153            server_ips,
1154            managed_addrs,
1155            lease_length,
1156            permitted_macs,
1157            static_assignments,
1158            arp_probe,
1159            bound_device_names,
1160        } = &self.params;
1161        Ok(vec![
1162            fidl_fuchsia_net_dhcp::Parameter::IpAddrs(server_ips.clone().into_fidl()),
1163            fidl_fuchsia_net_dhcp::Parameter::AddressPool(managed_addrs.clone().into_fidl()),
1164            fidl_fuchsia_net_dhcp::Parameter::Lease(lease_length.clone().into_fidl()),
1165            fidl_fuchsia_net_dhcp::Parameter::PermittedMacs(permitted_macs.clone().into_fidl()),
1166            fidl_fuchsia_net_dhcp::Parameter::StaticallyAssignedAddrs(
1167                static_assignments.clone().into_fidl(),
1168            ),
1169            fidl_fuchsia_net_dhcp::Parameter::ArpProbe(*arp_probe),
1170            fidl_fuchsia_net_dhcp::Parameter::BoundDeviceNames(bound_device_names.clone()),
1171        ])
1172    }
1173
1174    fn dispatch_reset_options(&mut self) -> Result<(), Status> {
1175        let () = self.options_repo.clear();
1176        let opts: Vec<DhcpOption> = self.options_repo.values().cloned().collect();
1177        if let Some(store) = self.store.as_mut() {
1178            let () = store.store_options(&opts).map_err(|e| {
1179                warn!("store_options({:?}) in stash failed: {}", opts, e);
1180                zx::Status::INTERNAL
1181            })?;
1182        }
1183        Ok(())
1184    }
1185
1186    fn dispatch_reset_parameters(&mut self, defaults: &ServerParameters) -> Result<(), Status> {
1187        self.params = defaults.clone();
1188        let () = self.save_params()?;
1189        Ok(())
1190    }
1191
1192    fn dispatch_clear_leases(&mut self) -> Result<(), Status> {
1193        let Self { records, pool, store, .. } = self;
1194        for (id, LeaseRecord { current, .. }) in records.drain() {
1195            if let Some(current) = current {
1196                let () = match pool.release_addr(current) {
1197                    Ok(()) => (),
1198                    // Panic on failure because server has irrecoverable inconsistent state.
1199                    Err(e) => panic!("fatal server release address failure: {}", e),
1200                };
1201            }
1202            if let Some(store) = store {
1203                let () = store.delete(&id).map_err(|e| {
1204                    warn!("delete({}) failed: {:?}", id, e);
1205                    zx::Status::INTERNAL
1206                })?;
1207            }
1208        }
1209        Ok(())
1210    }
1211}
1212
1213/// A mapping of clients to their lease records.
1214///
1215/// The server should store a record for each client to which it has sent
1216/// a DHCPOFFER message.
1217pub type ClientRecords = HashMap<ClientIdentifier, LeaseRecord>;
1218
1219/// A record of a DHCP client's configuration settings.
1220///
1221/// A client's `ClientIdentifier` maps to the `LeaseRecord`: this mapping
1222/// is stored in the `Server`s `ClientRecords` instance at runtime, and in
1223/// `fuchsia.stash` persistent storage.
1224#[derive(Clone, Debug, Deserialize, Serialize)]
1225pub struct LeaseRecord {
1226    current: Option<Ipv4Addr>,
1227    previous: Option<Ipv4Addr>,
1228    options: Vec<DhcpOption>,
1229    lease_start_epoch_seconds: u64,
1230    lease_length_seconds: u32,
1231}
1232
1233#[cfg(test)]
1234impl Default for LeaseRecord {
1235    fn default() -> Self {
1236        LeaseRecord {
1237            current: None,
1238            previous: None,
1239            options: Vec::new(),
1240            lease_start_epoch_seconds: u64::MIN,
1241            lease_length_seconds: std::u32::MAX,
1242        }
1243    }
1244}
1245
1246impl PartialEq for LeaseRecord {
1247    fn eq(&self, other: &Self) -> bool {
1248        // Only compare directly comparable fields.
1249        let LeaseRecord {
1250            current,
1251            previous,
1252            options,
1253            lease_start_epoch_seconds: _not_comparable,
1254            lease_length_seconds,
1255        } = self;
1256        let LeaseRecord {
1257            current: other_current,
1258            previous: other_previous,
1259            options: other_options,
1260            lease_start_epoch_seconds: _other_not_comparable,
1261            lease_length_seconds: other_lease_length_seconds,
1262        } = other;
1263        current == other_current
1264            && previous == other_previous
1265            && options == other_options
1266            && lease_length_seconds == other_lease_length_seconds
1267    }
1268}
1269
1270impl LeaseRecord {
1271    fn new(
1272        current: Option<Ipv4Addr>,
1273        options: Vec<DhcpOption>,
1274        lease_start: std::time::SystemTime,
1275        lease_length_seconds: u32,
1276    ) -> Result<Self, Error> {
1277        let lease_start_epoch_seconds =
1278            lease_start.duration_since(std::time::UNIX_EPOCH)?.as_secs();
1279        Ok(Self {
1280            current,
1281            previous: None,
1282            options,
1283            lease_start_epoch_seconds,
1284            lease_length_seconds,
1285        })
1286    }
1287
1288    fn expired(&self, since_unix_epoch: std::time::Duration) -> bool {
1289        let LeaseRecord { lease_start_epoch_seconds, lease_length_seconds, .. } = self;
1290        let end = std::time::Duration::from_secs(
1291            *lease_start_epoch_seconds + u64::from(*lease_length_seconds),
1292        );
1293        since_unix_epoch >= end
1294    }
1295}
1296
1297/// The pool of addresses managed by the server.
1298#[derive(Debug)]
1299struct AddressPool {
1300    // Morally immutable after construction, this is the full set of addresses
1301    // this pool manages, both allocated and available.
1302    //
1303    // TODO(https://fxbug.dev/42154213): make this type std::ops::Range.
1304    universe: BTreeSet<Ipv4Addr>,
1305    allocated: BTreeSet<Ipv4Addr>,
1306}
1307
1308//This is a wrapper around different errors that could be returned by
1309// the DHCP server address pool during address allocation/de-allocation.
1310#[derive(Debug, Error, PartialEq)]
1311pub enum AddressPoolError {
1312    #[error("address pool does not have any available ip to hand out")]
1313    Ipv4AddrExhaustion,
1314
1315    #[error("attempted to allocate already allocated ip: {}", _0)]
1316    AllocatedIpv4AddrAllocation(Ipv4Addr),
1317
1318    #[error("attempted to release unallocated ip: {}", _0)]
1319    UnallocatedIpv4AddrRelease(Ipv4Addr),
1320
1321    #[error("attempted to interact with out-of-pool ip: {}", _0)]
1322    UnmanagedIpv4Addr(Ipv4Addr),
1323}
1324
1325impl AddressPool {
1326    fn new<T: Iterator<Item = Ipv4Addr>>(addresses: T) -> Self {
1327        Self { universe: addresses.collect(), allocated: BTreeSet::new() }
1328    }
1329
1330    fn available(&self) -> impl Iterator<Item = Ipv4Addr> + '_ {
1331        let Self { universe: range, allocated } = self;
1332        range.difference(allocated).copied()
1333    }
1334
1335    fn allocate_addr(&mut self, addr: Ipv4Addr) -> Result<(), AddressPoolError> {
1336        if !self.universe.contains(&addr) {
1337            Err(AddressPoolError::UnmanagedIpv4Addr(addr))
1338        } else {
1339            if !self.allocated.insert(addr) {
1340                Err(AddressPoolError::AllocatedIpv4AddrAllocation(addr))
1341            } else {
1342                Ok(())
1343            }
1344        }
1345    }
1346
1347    fn release_addr(&mut self, addr: Ipv4Addr) -> Result<(), AddressPoolError> {
1348        if !self.universe.contains(&addr) {
1349            Err(AddressPoolError::UnmanagedIpv4Addr(addr))
1350        } else {
1351            if !self.allocated.remove(&addr) {
1352                Err(AddressPoolError::UnallocatedIpv4AddrRelease(addr))
1353            } else {
1354                Ok(())
1355            }
1356        }
1357    }
1358
1359    fn addr_is_available(&self, addr: Ipv4Addr) -> bool {
1360        self.universe.contains(&addr) && !self.allocated.contains(&addr)
1361    }
1362
1363    fn addr_is_allocated(&self, addr: Ipv4Addr) -> bool {
1364        self.allocated.contains(&addr)
1365    }
1366}
1367
1368#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1369enum ClientState {
1370    Selecting,
1371    InitReboot,
1372    Renewing,
1373}
1374
1375// Cf. RFC 2131 Table 5: https://tools.ietf.org/html/rfc2131#page-37
1376fn validate_discover(disc: &Message) -> Result<(), ServerError> {
1377    use std::string::ToString as _;
1378    if disc.op != OpCode::BOOTREQUEST {
1379        return Err(ServerError::ClientMessageError(ProtocolError::InvalidField {
1380            field: String::from("op"),
1381            value: OpCode::BOOTREPLY.to_string(),
1382            msg_type: MessageType::DHCPDISCOVER,
1383        }));
1384    }
1385    if !disc.ciaddr.is_unspecified() {
1386        return Err(ServerError::ClientMessageError(ProtocolError::InvalidField {
1387            field: String::from("ciaddr"),
1388            value: disc.ciaddr.to_string(),
1389            msg_type: MessageType::DHCPDISCOVER,
1390        }));
1391    }
1392    if !disc.yiaddr.is_unspecified() {
1393        return Err(ServerError::ClientMessageError(ProtocolError::InvalidField {
1394            field: String::from("yiaddr"),
1395            value: disc.yiaddr.to_string(),
1396            msg_type: MessageType::DHCPDISCOVER,
1397        }));
1398    }
1399    if !disc.siaddr.is_unspecified() {
1400        return Err(ServerError::ClientMessageError(ProtocolError::InvalidField {
1401            field: String::from("siaddr"),
1402            value: disc.siaddr.to_string(),
1403            msg_type: MessageType::DHCPDISCOVER,
1404        }));
1405    }
1406    // Do not check giaddr, because although a client will never set it, an
1407    // intervening relay agent may have done.
1408    if let Some(DhcpOption::ServerIdentifier(addr)) = disc.options.iter().find(|opt| match opt {
1409        DhcpOption::ServerIdentifier(_) => true,
1410        _ => false,
1411    }) {
1412        return Err(ServerError::ClientMessageError(ProtocolError::InvalidField {
1413            field: String::from("ServerIdentifier"),
1414            value: addr.to_string(),
1415            msg_type: MessageType::DHCPDISCOVER,
1416        }));
1417    }
1418    Ok(())
1419}
1420
1421fn is_recipient(server_ips: &Vec<Ipv4Addr>, req: &Message) -> bool {
1422    if let Some(server_id) = get_server_id_from(&req) {
1423        server_ips.contains(&server_id)
1424    } else {
1425        false
1426    }
1427}
1428
1429fn is_in_subnet(req: &Message, config: &ServerParameters) -> bool {
1430    let client_ip = match get_requested_ip_addr(&req) {
1431        Some(ip) => ip,
1432        None => return false,
1433    };
1434    config.server_ips.iter().any(|server_ip| {
1435        config.managed_addrs.mask.apply_to(&client_ip)
1436            == config.managed_addrs.mask.apply_to(server_ip)
1437    })
1438}
1439
1440fn get_client_state(msg: &Message) -> Result<ClientState, ()> {
1441    let server_id = get_server_id_from(&msg);
1442    let requested_ip = get_requested_ip_addr(&msg);
1443
1444    // State classification from: https://tools.ietf.org/html/rfc2131#section-4.3.2
1445    //
1446    // DHCPREQUEST generated during SELECTING state:
1447    //
1448    // Client inserts the address of the selected server in 'server identifier', 'ciaddr' MUST be
1449    // zero, 'requested IP address' MUST be filled in with the yiaddr value from the chosen
1450    // DHCPOFFER.
1451    //
1452    // DHCPREQUEST generated during INIT-REBOOT state:
1453    //
1454    // 'server identifier' MUST NOT be filled in, 'requested IP address' option MUST be
1455    // filled in with client's notion of its previously assigned address. 'ciaddr' MUST be
1456    // zero.
1457    //
1458    // DHCPREQUEST generated during RENEWING state:
1459    //
1460    // 'server identifier' MUST NOT be filled in, 'requested IP address' option MUST NOT be filled
1461    // in, 'ciaddr' MUST be filled in with client's IP address.
1462    //
1463    // TODO(https://fxbug.dev/42143639): Distinguish between clients in RENEWING and REBINDING states
1464    if server_id.is_some() && msg.ciaddr.is_unspecified() && requested_ip.is_some() {
1465        Ok(ClientState::Selecting)
1466    } else if server_id.is_none() && requested_ip.is_some() && msg.ciaddr.is_unspecified() {
1467        Ok(ClientState::InitReboot)
1468    } else if server_id.is_none() && requested_ip.is_none() && !msg.ciaddr.is_unspecified() {
1469        Ok(ClientState::Renewing)
1470    } else {
1471        Err(())
1472    }
1473}
1474
1475fn get_requested_ip_addr(req: &Message) -> Option<Ipv4Addr> {
1476    req.options.iter().find_map(|opt| {
1477        if let DhcpOption::RequestedIpAddress(addr) = opt { Some(*addr) } else { None }
1478    })
1479}
1480
1481enum NakReason {
1482    ClientValidationFailure(ServerError),
1483    DifferentSubnets,
1484}
1485
1486impl std::fmt::Display for NakReason {
1487    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1488        match self {
1489            Self::ClientValidationFailure(e) => {
1490                write!(f, "requested ip is not assigned to client: {}", e)
1491            }
1492            Self::DifferentSubnets => {
1493                write!(f, "client and server are in different subnets")
1494            }
1495        }
1496    }
1497}
1498
1499pub fn get_server_id_from(req: &Message) -> Option<Ipv4Addr> {
1500    req.options.iter().find_map(|opt| match opt {
1501        DhcpOption::ServerIdentifier(addr) => Some(*addr),
1502        _ => None,
1503    })
1504}
1505
1506#[cfg(test)]
1507pub mod tests {
1508    use crate::configuration::{
1509        LeaseLength, ManagedAddresses, PermittedMacs, StaticAssignments, SubnetMask,
1510    };
1511    use crate::protocol::{
1512        DhcpOption, FidlCompatible as _, IntoFidlExt as _, Message, MessageType, OpCode,
1513        OptionCode, ProtocolError,
1514    };
1515    use crate::server::{
1516        AddressPool, AddressPoolError, ClientIdentifier, ClientState, DataStore, LeaseRecord,
1517        NakReason, OfferOptions, ResponseTarget, ServerAction, ServerDispatcher, ServerError,
1518        ServerParameters, SystemTimeSource, build_offer, get_client_state, options_repo,
1519        validate_discover,
1520    };
1521    use anyhow::Error;
1522    use datastore::{ActionRecordingDataStore, DataStoreAction};
1523    use dhcp_protocol::{AtLeast, AtMostBytes};
1524    use fidl_fuchsia_net_ext::IntoExt as _;
1525    use net_declare::net::prefix_length_v4;
1526    use net_declare::{fidl_ip_v4, std_ip_v4};
1527    use net_types::ethernet::Mac as MacAddr;
1528    use net_types::ip::{Ipv4, PrefixLength};
1529    use std::cell::RefCell;
1530    use std::collections::{BTreeSet, HashMap, HashSet};
1531    use std::iter::FromIterator as _;
1532    use std::net::Ipv4Addr;
1533    use std::rc::Rc;
1534    use std::time::{Duration, SystemTime};
1535    use test_case::test_case;
1536    use zx::Status;
1537
1538    mod datastore {
1539        use crate::protocol::{DhcpOption, OptionCode};
1540        use crate::server::{
1541            ClientIdentifier, ClientRecords, DataStore, LeaseRecord, ServerParameters,
1542        };
1543        use std::collections::HashMap;
1544
1545        pub struct ActionRecordingDataStore {
1546            actions: Vec<DataStoreAction>,
1547        }
1548
1549        #[derive(Clone, Debug, PartialEq)]
1550        pub enum DataStoreAction {
1551            StoreClientRecord { client_id: ClientIdentifier, record: LeaseRecord },
1552            StoreOptions { opts: Vec<DhcpOption> },
1553            StoreParameters { params: ServerParameters },
1554            LoadClientRecords,
1555            LoadOptions,
1556            Delete { client_id: ClientIdentifier },
1557        }
1558
1559        #[derive(Debug, thiserror::Error)]
1560        #[error(transparent)]
1561        pub struct ActionRecordingError(#[from] anyhow::Error);
1562
1563        impl ActionRecordingDataStore {
1564            pub fn new() -> Self {
1565                Self { actions: Vec::new() }
1566            }
1567
1568            pub fn push_action(&mut self, cmd: DataStoreAction) -> () {
1569                let Self { actions } = self;
1570                actions.push(cmd)
1571            }
1572
1573            pub fn actions(&mut self) -> std::vec::Drain<'_, DataStoreAction> {
1574                let Self { actions } = self;
1575                actions.drain(..)
1576            }
1577
1578            pub fn load_client_records(&mut self) -> Result<ClientRecords, ActionRecordingError> {
1579                let () = self.push_action(DataStoreAction::LoadClientRecords);
1580                Ok(HashMap::new())
1581            }
1582
1583            pub fn load_options(
1584                &mut self,
1585            ) -> Result<HashMap<OptionCode, DhcpOption>, ActionRecordingError> {
1586                let () = self.push_action(DataStoreAction::LoadOptions);
1587                Ok(HashMap::new())
1588            }
1589        }
1590
1591        impl Drop for ActionRecordingDataStore {
1592            fn drop(&mut self) {
1593                let Self { actions } = self;
1594                assert!(actions.is_empty())
1595            }
1596        }
1597
1598        impl DataStore for ActionRecordingDataStore {
1599            type Error = ActionRecordingError;
1600
1601            fn insert(
1602                &mut self,
1603                client_id: &ClientIdentifier,
1604                record: &LeaseRecord,
1605            ) -> Result<(), Self::Error> {
1606                Ok(self.push_action(DataStoreAction::StoreClientRecord {
1607                    client_id: client_id.clone(),
1608                    record: record.clone(),
1609                }))
1610            }
1611
1612            fn store_options(&mut self, opts: &[DhcpOption]) -> Result<(), Self::Error> {
1613                Ok(self.push_action(DataStoreAction::StoreOptions { opts: Vec::from(opts) }))
1614            }
1615
1616            fn store_parameters(&mut self, params: &ServerParameters) -> Result<(), Self::Error> {
1617                Ok(self.push_action(DataStoreAction::StoreParameters { params: params.clone() }))
1618            }
1619
1620            fn delete(&mut self, client_id: &ClientIdentifier) -> Result<(), Self::Error> {
1621                Ok(self.push_action(DataStoreAction::Delete { client_id: client_id.clone() }))
1622            }
1623        }
1624    }
1625
1626    // UTC time can go backwards (https://fuchsia.dev/fuchsia-src/concepts/time/utc/behavior),
1627    // using `SystemTime::now` has the possibility to introduce flakiness to tests. This struct
1628    // makes sure we can get non-decreasing `SystemTime`s in a test environment.
1629    #[derive(Clone)]
1630    struct TestSystemTime(Rc<RefCell<SystemTime>>);
1631
1632    impl SystemTimeSource for TestSystemTime {
1633        fn with_current_time() -> Self {
1634            Self(Rc::new(RefCell::new(SystemTime::now())))
1635        }
1636        fn now(&self) -> SystemTime {
1637            let TestSystemTime(current) = self;
1638            *current.borrow()
1639        }
1640    }
1641
1642    impl TestSystemTime {
1643        pub(super) fn move_forward(&mut self, duration: Duration) {
1644            let TestSystemTime(current) = self;
1645            *current.borrow_mut() += duration;
1646        }
1647    }
1648
1649    type Server<DS = ActionRecordingDataStore> = super::Server<DS, TestSystemTime>;
1650
1651    fn default_server_params() -> Result<ServerParameters, Error> {
1652        test_server_params(
1653            Vec::new(),
1654            LeaseLength { default_seconds: 60 * 60 * 24, max_seconds: 60 * 60 * 24 * 7 },
1655        )
1656    }
1657
1658    fn test_server_params(
1659        server_ips: Vec<Ipv4Addr>,
1660        lease_length: LeaseLength,
1661    ) -> Result<ServerParameters, Error> {
1662        Ok(ServerParameters {
1663            server_ips,
1664            lease_length,
1665            managed_addrs: ManagedAddresses {
1666                mask: SubnetMask::new(prefix_length_v4!(24)),
1667                pool_range_start: net_declare::std::ip_v4!("192.168.0.0"),
1668                pool_range_stop: net_declare::std::ip_v4!("192.168.0.0"),
1669            },
1670            permitted_macs: PermittedMacs(Vec::new()),
1671            static_assignments: StaticAssignments(HashMap::new()),
1672            arp_probe: false,
1673            bound_device_names: Vec::new(),
1674        })
1675    }
1676
1677    pub fn random_ipv4_generator() -> Ipv4Addr {
1678        let octet1: u8 = rand::random();
1679        let octet2: u8 = rand::random();
1680        let octet3: u8 = rand::random();
1681        let octet4: u8 = rand::random();
1682        Ipv4Addr::new(octet1, octet2, octet3, octet4)
1683    }
1684
1685    pub fn random_mac_generator() -> MacAddr {
1686        let octet1: u8 = rand::random();
1687        let octet2: u8 = rand::random();
1688        let octet3: u8 = rand::random();
1689        let octet4: u8 = rand::random();
1690        let octet5: u8 = rand::random();
1691        let octet6: u8 = rand::random();
1692        MacAddr::new([octet1, octet2, octet3, octet4, octet5, octet6])
1693    }
1694
1695    fn extract_message(server_response: ServerAction) -> Message {
1696        if let ServerAction::SendResponse(message, _destination) = server_response {
1697            message
1698        } else {
1699            panic!("expected a message in server response, received {:?}", server_response)
1700        }
1701    }
1702
1703    fn get_router<DS: DataStore>(
1704        server: &Server<DS>,
1705    ) -> Result<
1706        AtLeast<1, AtMostBytes<{ dhcp_protocol::U8_MAX_AS_USIZE }, Vec<Ipv4Addr>>>,
1707        ProtocolError,
1708    > {
1709        let code = OptionCode::Router;
1710        match server.options_repo.get(&code) {
1711            Some(DhcpOption::Router(router)) => Some(router.clone()),
1712            option => panic!("unexpected entry {} => {:?}", &code, option),
1713        }
1714        .ok_or(ProtocolError::MissingOption(code))
1715    }
1716
1717    fn get_dns_server<DS: DataStore>(
1718        server: &Server<DS>,
1719    ) -> Result<
1720        AtLeast<1, AtMostBytes<{ dhcp_protocol::U8_MAX_AS_USIZE }, Vec<Ipv4Addr>>>,
1721        ProtocolError,
1722    > {
1723        let code = OptionCode::DomainNameServer;
1724        match server.options_repo.get(&code) {
1725            Some(DhcpOption::DomainNameServer(dns_server)) => Some(dns_server.clone()),
1726            option => panic!("unexpected entry {} => {:?}", &code, option),
1727        }
1728        .ok_or(ProtocolError::MissingOption(code))
1729    }
1730
1731    fn new_test_minimal_server_with_time_source() -> (Server, TestSystemTime) {
1732        let time_source = TestSystemTime::with_current_time();
1733        let params = test_server_params(
1734            vec![random_ipv4_generator()],
1735            LeaseLength { default_seconds: 100, max_seconds: 60 * 60 * 24 * 7 },
1736        )
1737        .expect("failed to create test server parameters");
1738        (
1739            super::Server {
1740                records: HashMap::new(),
1741                pool: AddressPool::new(params.managed_addrs.pool_range()),
1742                params,
1743                store: Some(ActionRecordingDataStore::new()),
1744                options_repo: HashMap::from_iter(vec![
1745                    (OptionCode::Router, DhcpOption::Router([random_ipv4_generator()].into())),
1746                    (
1747                        OptionCode::DomainNameServer,
1748                        DhcpOption::DomainNameServer(
1749                            [std_ip_v4!("1.2.3.4"), std_ip_v4!("4.3.2.1")].into(),
1750                        ),
1751                    ),
1752                ]),
1753                time_source: time_source.clone(),
1754            },
1755            time_source.clone(),
1756        )
1757    }
1758
1759    fn new_test_minimal_server() -> Server {
1760        let (server, _time_source) = new_test_minimal_server_with_time_source();
1761        server
1762    }
1763
1764    fn new_client_message(message_type: MessageType) -> Message {
1765        new_client_message_with_preset_options(message_type, std::iter::empty())
1766    }
1767
1768    fn new_client_message_with_preset_options(
1769        message_type: MessageType,
1770        options: impl Iterator<Item = DhcpOption>,
1771    ) -> Message {
1772        new_client_message_with_options(
1773            [
1774                DhcpOption::DhcpMessageType(message_type),
1775                DhcpOption::ParameterRequestList(
1776                    [OptionCode::SubnetMask, OptionCode::Router, OptionCode::DomainNameServer]
1777                        .into(),
1778                ),
1779            ]
1780            .into_iter()
1781            .chain(options),
1782        )
1783    }
1784
1785    fn new_client_message_with_options<T: IntoIterator<Item = DhcpOption>>(options: T) -> Message {
1786        Message {
1787            op: OpCode::BOOTREQUEST,
1788            xid: rand::random(),
1789            secs: 0,
1790            bdcast_flag: true,
1791            ciaddr: Ipv4Addr::UNSPECIFIED,
1792            yiaddr: Ipv4Addr::UNSPECIFIED,
1793            siaddr: Ipv4Addr::UNSPECIFIED,
1794            giaddr: Ipv4Addr::UNSPECIFIED,
1795            chaddr: random_mac_generator(),
1796            sname: String::new(),
1797            file: String::new(),
1798            options: options.into_iter().collect(),
1799        }
1800    }
1801
1802    fn new_test_discover() -> Message {
1803        new_test_discover_with_options(std::iter::empty())
1804    }
1805
1806    fn new_test_discover_with_options(options: impl Iterator<Item = DhcpOption>) -> Message {
1807        new_client_message_with_preset_options(MessageType::DHCPDISCOVER, options)
1808    }
1809
1810    fn new_server_message<DS: DataStore>(
1811        message_type: MessageType,
1812        client_message: &Message,
1813        server: &Server<DS>,
1814    ) -> Message {
1815        let Message {
1816            op: _,
1817            xid,
1818            secs: _,
1819            bdcast_flag,
1820            ciaddr: _,
1821            yiaddr: _,
1822            siaddr: _,
1823            giaddr: _,
1824            chaddr,
1825            sname: _,
1826            file: _,
1827            options: _,
1828        } = client_message;
1829        Message {
1830            op: OpCode::BOOTREPLY,
1831            xid: *xid,
1832            secs: 0,
1833            bdcast_flag: *bdcast_flag,
1834            ciaddr: Ipv4Addr::UNSPECIFIED,
1835            yiaddr: Ipv4Addr::UNSPECIFIED,
1836            siaddr: Ipv4Addr::UNSPECIFIED,
1837            giaddr: Ipv4Addr::UNSPECIFIED,
1838            chaddr: *chaddr,
1839            sname: String::new(),
1840            file: String::new(),
1841            options: vec![
1842                DhcpOption::DhcpMessageType(message_type),
1843                DhcpOption::ServerIdentifier(
1844                    server.get_server_ip(client_message).unwrap_or(Ipv4Addr::UNSPECIFIED),
1845                ),
1846            ],
1847        }
1848    }
1849
1850    fn new_server_message_with_lease<DS: DataStore>(
1851        message_type: MessageType,
1852        client_message: &Message,
1853        server: &Server<DS>,
1854    ) -> Message {
1855        let mut msg = new_server_message(message_type, client_message, server);
1856        msg.options.extend([
1857            DhcpOption::IpAddressLeaseTime(100),
1858            DhcpOption::RenewalTimeValue(50),
1859            DhcpOption::RebindingTimeValue(75),
1860        ]);
1861        let () = add_server_options(&mut msg, server);
1862        msg
1863    }
1864
1865    const DEFAULT_PREFIX_LENGTH: PrefixLength<Ipv4> = prefix_length_v4!(24);
1866
1867    fn add_server_options<DS: DataStore>(msg: &mut Message, server: &Server<DS>) {
1868        msg.options.push(DhcpOption::SubnetMask(DEFAULT_PREFIX_LENGTH));
1869        if let Some(routers) = match server.options_repo.get(&OptionCode::Router) {
1870            Some(DhcpOption::Router(v)) => Some(v),
1871            _ => None,
1872        } {
1873            msg.options.push(DhcpOption::Router(routers.clone()));
1874        }
1875        if let Some(servers) = match server.options_repo.get(&OptionCode::DomainNameServer) {
1876            Some(DhcpOption::DomainNameServer(v)) => Some(v),
1877            _ => None,
1878        } {
1879            msg.options.push(DhcpOption::DomainNameServer(servers.clone()));
1880        }
1881    }
1882
1883    fn new_test_offer<DS: DataStore>(disc: &Message, server: &Server<DS>) -> Message {
1884        new_server_message_with_lease(MessageType::DHCPOFFER, disc, server)
1885    }
1886
1887    fn new_test_request() -> Message {
1888        new_client_message(MessageType::DHCPREQUEST)
1889    }
1890
1891    fn new_test_request_selecting_state<DS: DataStore>(
1892        server: &Server<DS>,
1893        requested_ip: Ipv4Addr,
1894    ) -> Message {
1895        let mut req = new_test_request();
1896        req.options.push(DhcpOption::RequestedIpAddress(requested_ip));
1897        req.options.push(DhcpOption::ServerIdentifier(
1898            server.get_server_ip(&req).unwrap_or(Ipv4Addr::UNSPECIFIED),
1899        ));
1900        req
1901    }
1902
1903    fn new_test_ack<DS: DataStore>(req: &Message, server: &Server<DS>) -> Message {
1904        new_server_message_with_lease(MessageType::DHCPACK, req, server)
1905    }
1906
1907    fn new_test_nak<DS: DataStore>(
1908        req: &Message,
1909        server: &Server<DS>,
1910        reason: NakReason,
1911    ) -> Message {
1912        let mut nak = new_server_message(MessageType::DHCPNAK, req, server);
1913        nak.options.push(DhcpOption::Message(format!("{}", reason)));
1914        nak
1915    }
1916
1917    fn new_test_release() -> Message {
1918        new_client_message(MessageType::DHCPRELEASE)
1919    }
1920
1921    fn new_test_inform() -> Message {
1922        new_client_message(MessageType::DHCPINFORM)
1923    }
1924
1925    fn new_test_inform_ack<DS: DataStore>(req: &Message, server: &Server<DS>) -> Message {
1926        let mut msg = new_server_message(MessageType::DHCPACK, req, server);
1927        let () = add_server_options(&mut msg, server);
1928        msg
1929    }
1930
1931    fn new_test_decline<DS: DataStore>(server: &Server<DS>) -> Message {
1932        let mut decline = new_client_message(MessageType::DHCPDECLINE);
1933        decline.options.push(DhcpOption::ServerIdentifier(
1934            server.get_server_ip(&decline).unwrap_or(Ipv4Addr::UNSPECIFIED),
1935        ));
1936        decline
1937    }
1938
1939    #[test]
1940    fn dispatch_with_discover_returns_correct_offer_and_dest_giaddr_when_giaddr_set() {
1941        let mut server = new_test_minimal_server();
1942        let mut disc = new_test_discover();
1943        disc.giaddr = random_ipv4_generator();
1944        let client_id = ClientIdentifier::from(&disc);
1945
1946        let offer_ip = random_ipv4_generator();
1947
1948        assert!(server.pool.universe.insert(offer_ip));
1949
1950        let mut expected_offer = new_test_offer(&disc, &server);
1951        expected_offer.yiaddr = offer_ip;
1952        expected_offer.giaddr = disc.giaddr;
1953
1954        let expected_dest = disc.giaddr;
1955
1956        assert_eq!(
1957            server.dispatch(disc),
1958            Ok(ServerAction::SendResponse(
1959                expected_offer,
1960                ResponseTarget::Unicast(expected_dest, None)
1961            ))
1962        );
1963        assert_matches::assert_matches!(
1964            server.store.expect("missing store").actions().as_slice(),
1965            [DataStoreAction::StoreClientRecord {client_id: id, ..}] if *id == client_id
1966        );
1967    }
1968
1969    #[test]
1970    fn dispatch_with_discover_returns_correct_offer_and_dest_broadcast_when_giaddr_unspecified() {
1971        let mut server = new_test_minimal_server();
1972        let disc = new_test_discover();
1973        let client_id = ClientIdentifier::from(&disc);
1974
1975        let offer_ip = random_ipv4_generator();
1976        assert!(server.pool.universe.insert(offer_ip));
1977        let expected_offer = {
1978            let mut expected_offer = new_test_offer(&disc, &server);
1979            expected_offer.yiaddr = offer_ip;
1980            expected_offer
1981        };
1982
1983        assert_eq!(
1984            server.dispatch(disc),
1985            Ok(ServerAction::SendResponse(expected_offer, ResponseTarget::Broadcast))
1986        );
1987        assert_matches::assert_matches!(
1988            server.store.expect("missing store").actions().as_slice(),
1989            [DataStoreAction::StoreClientRecord {client_id: id, ..}] if *id == client_id
1990        );
1991    }
1992
1993    #[test]
1994    fn dispatch_with_discover_returns_correct_offer_and_dest_yiaddr_when_giaddr_and_ciaddr_unspecified_and_broadcast_bit_unset()
1995     {
1996        let mut server = new_test_minimal_server();
1997        let disc = {
1998            let mut disc = new_test_discover();
1999            disc.bdcast_flag = false;
2000            disc
2001        };
2002        let chaddr = disc.chaddr;
2003        let client_id = ClientIdentifier::from(&disc);
2004
2005        let offer_ip = random_ipv4_generator();
2006        assert!(server.pool.universe.insert(offer_ip));
2007        let expected_offer = {
2008            let mut expected_offer = new_test_offer(&disc, &server);
2009            expected_offer.yiaddr = offer_ip;
2010            expected_offer
2011        };
2012
2013        assert_eq!(
2014            server.dispatch(disc),
2015            Ok(ServerAction::SendResponse(
2016                expected_offer,
2017                ResponseTarget::Unicast(offer_ip, Some(chaddr))
2018            ))
2019        );
2020        assert_matches::assert_matches!(
2021            server.store.expect("missing store").actions().as_slice(),
2022            [DataStoreAction::StoreClientRecord {client_id: id, ..}] if *id == client_id
2023        );
2024    }
2025
2026    #[test]
2027    fn dispatch_with_discover_returns_correct_offer_and_dest_giaddr_if_giaddr_broadcast_bit_is_set()
2028    {
2029        let mut server = new_test_minimal_server();
2030        let giaddr = random_ipv4_generator();
2031        let disc = {
2032            let mut disc = new_test_discover();
2033            disc.giaddr = giaddr;
2034            disc
2035        };
2036        let client_id = ClientIdentifier::from(&disc);
2037
2038        let offer_ip = random_ipv4_generator();
2039        assert!(server.pool.universe.insert(offer_ip));
2040
2041        let expected_offer = {
2042            let mut expected_offer = new_test_offer(&disc, &server);
2043            expected_offer.yiaddr = offer_ip;
2044            expected_offer.giaddr = giaddr;
2045            expected_offer
2046        };
2047
2048        assert_eq!(
2049            server.dispatch(disc),
2050            Ok(ServerAction::SendResponse(expected_offer, ResponseTarget::Unicast(giaddr, None)))
2051        );
2052        assert_matches::assert_matches!(
2053            server.store.expect("missing store").actions().as_slice(),
2054            [DataStoreAction::StoreClientRecord {client_id: id, ..}] if *id == client_id
2055        );
2056    }
2057
2058    #[test]
2059    fn dispatch_with_discover_returns_error_if_ciaddr_set() {
2060        use std::string::ToString as _;
2061        let mut server = new_test_minimal_server();
2062        let ciaddr = random_ipv4_generator();
2063        let disc = {
2064            let mut disc = new_test_discover();
2065            disc.ciaddr = ciaddr;
2066            disc
2067        };
2068
2069        assert!(server.pool.universe.insert(random_ipv4_generator()));
2070
2071        assert_eq!(
2072            server.dispatch(disc),
2073            Err(ServerError::ClientMessageError(ProtocolError::InvalidField {
2074                field: String::from("ciaddr"),
2075                value: ciaddr.to_string(),
2076                msg_type: MessageType::DHCPDISCOVER
2077            }))
2078        );
2079    }
2080
2081    #[test]
2082    fn dispatch_with_discover_updates_server_state() {
2083        let (mut server, time_source) = new_test_minimal_server_with_time_source();
2084        let disc = new_test_discover();
2085
2086        let offer_ip = random_ipv4_generator();
2087        let client_id = ClientIdentifier::from(&disc);
2088
2089        assert!(server.pool.universe.insert(offer_ip));
2090
2091        let server_id = server.params.server_ips.first().unwrap();
2092        let router = get_router(&server).expect("failed to get router");
2093        let dns_server = get_dns_server(&server).expect("failed to get dns server");
2094        let expected_client_record = LeaseRecord::new(
2095            Some(offer_ip),
2096            vec![
2097                DhcpOption::ServerIdentifier(*server_id),
2098                DhcpOption::IpAddressLeaseTime(server.params.lease_length.default_seconds),
2099                DhcpOption::RenewalTimeValue(server.params.lease_length.default_seconds / 2),
2100                DhcpOption::RebindingTimeValue(
2101                    (server.params.lease_length.default_seconds * 3) / 4,
2102                ),
2103                DhcpOption::SubnetMask(DEFAULT_PREFIX_LENGTH),
2104                DhcpOption::Router(router),
2105                DhcpOption::DomainNameServer(dns_server),
2106            ],
2107            time_source.now(),
2108            server.params.lease_length.default_seconds,
2109        )
2110        .expect("failed to create lease record");
2111
2112        let _response = server.dispatch(disc);
2113
2114        let available: Vec<_> = server.pool.available().collect();
2115        assert!(available.is_empty(), "{:?}", available);
2116        assert_eq!(server.pool.allocated.len(), 1);
2117        assert_eq!(server.records.len(), 1);
2118        assert_eq!(server.records.get(&client_id), Some(&expected_client_record));
2119        assert_matches::assert_matches!(
2120            server.store.expect("missing store").actions().as_slice(),
2121            [DataStoreAction::StoreClientRecord {client_id: id, ..}] if *id == client_id
2122        );
2123    }
2124
2125    fn dispatch_with_discover_updates_stash_helper(
2126        additional_options: impl Iterator<Item = DhcpOption>,
2127    ) {
2128        let mut server = new_test_minimal_server();
2129        let disc = new_test_discover_with_options(additional_options);
2130
2131        let client_id = ClientIdentifier::from(&disc);
2132
2133        assert!(server.pool.universe.insert(random_ipv4_generator()));
2134
2135        let server_action = server.dispatch(disc);
2136        assert!(server_action.is_ok());
2137
2138        let client_record = server
2139            .records
2140            .get(&client_id)
2141            .unwrap_or_else(|| panic!("server records missing entry for {}", client_id))
2142            .clone();
2143        assert_matches::assert_matches!(
2144            server.store.expect("missing store").actions().as_slice(),
2145            [
2146                DataStoreAction::StoreClientRecord { client_id: id, record },
2147            ] if *id == client_id && *record == client_record
2148        );
2149    }
2150
2151    #[test]
2152    fn dispatch_with_discover_updates_stash() {
2153        dispatch_with_discover_updates_stash_helper(std::iter::empty())
2154    }
2155
2156    #[test]
2157    fn dispatch_with_discover_with_client_id_updates_stash() {
2158        dispatch_with_discover_updates_stash_helper(std::iter::once(DhcpOption::ClientIdentifier(
2159            [1, 2, 3, 4, 5].into(),
2160        )))
2161    }
2162
2163    #[test]
2164    fn dispatch_with_discover_client_binding_returns_bound_addr() {
2165        let (mut server, time_source) = new_test_minimal_server_with_time_source();
2166        let disc = new_test_discover();
2167        let client_id = ClientIdentifier::from(&disc);
2168
2169        let bound_client_ip = random_ipv4_generator();
2170
2171        assert!(server.pool.allocated.insert(bound_client_ip));
2172        assert!(server.pool.universe.insert(bound_client_ip));
2173
2174        assert_matches::assert_matches!(
2175            server.records.insert(
2176                ClientIdentifier::from(&disc),
2177                LeaseRecord::new(
2178                    Some(bound_client_ip),
2179                    Vec::new(),
2180                    time_source.now(),
2181                    std::u32::MAX
2182                )
2183                .expect("failed to create lease record"),
2184            ),
2185            None
2186        );
2187
2188        let response = server.dispatch(disc).unwrap();
2189
2190        assert_eq!(extract_message(response).yiaddr, bound_client_ip);
2191        assert_matches::assert_matches!(
2192            server.store.expect("missing store").actions().as_slice(),
2193            [
2194                DataStoreAction::StoreClientRecord {client_id: id, record: LeaseRecord {current: Some(ip), previous: None, .. }},
2195            ] if *id == client_id && *ip == bound_client_ip
2196        );
2197    }
2198
2199    #[test]
2200    #[should_panic(expected = "active lease is unallocated in address pool")]
2201    fn dispatch_with_discover_client_binding_panics_when_addr_previously_not_allocated() {
2202        let (mut server, time_source) = new_test_minimal_server_with_time_source();
2203        let disc = new_test_discover();
2204
2205        let bound_client_ip = random_ipv4_generator();
2206
2207        assert!(server.pool.universe.insert(bound_client_ip));
2208
2209        assert_matches::assert_matches!(
2210            server.records.insert(
2211                ClientIdentifier::from(&disc),
2212                LeaseRecord::new(
2213                    Some(bound_client_ip),
2214                    Vec::new(),
2215                    time_source.now(),
2216                    std::u32::MAX
2217                )
2218                .unwrap(),
2219            ),
2220            None
2221        );
2222
2223        let _ = server.dispatch(disc);
2224    }
2225
2226    #[test]
2227    fn dispatch_with_discover_expired_client_binding_returns_available_old_addr() {
2228        let (mut server, time_source) = new_test_minimal_server_with_time_source();
2229        let disc = new_test_discover();
2230        let client_id = ClientIdentifier::from(&disc);
2231
2232        let bound_client_ip = random_ipv4_generator();
2233
2234        assert!(server.pool.universe.insert(bound_client_ip));
2235
2236        assert_matches::assert_matches!(
2237            server.records.insert(
2238                ClientIdentifier::from(&disc),
2239                // Manually initialize because new() assumes an unexpired lease.
2240                LeaseRecord {
2241                    current: None,
2242                    previous: Some(bound_client_ip),
2243                    options: Vec::new(),
2244                    lease_start_epoch_seconds: time_source
2245                        .now()
2246                        .duration_since(std::time::UNIX_EPOCH)
2247                        .expect("invalid time value")
2248                        .as_secs(),
2249                    lease_length_seconds: std::u32::MIN
2250                },
2251            ),
2252            None
2253        );
2254
2255        let response = server.dispatch(disc).unwrap();
2256
2257        assert_eq!(extract_message(response).yiaddr, bound_client_ip);
2258        assert_matches::assert_matches!(
2259            server.store.expect("missing store").actions().as_slice(),
2260            [DataStoreAction::StoreClientRecord {client_id: id, ..}] if *id == client_id
2261        );
2262    }
2263
2264    #[test]
2265    fn dispatch_with_discover_expired_client_binding_unavailable_addr_returns_next_free_addr() {
2266        let (mut server, time_source) = new_test_minimal_server_with_time_source();
2267        let disc = new_test_discover();
2268        let client_id = ClientIdentifier::from(&disc);
2269
2270        let bound_client_ip = random_ipv4_generator();
2271        let free_ip = random_ipv4_generator();
2272
2273        assert!(server.pool.allocated.insert(bound_client_ip));
2274        assert!(server.pool.universe.insert(free_ip));
2275
2276        assert_matches::assert_matches!(
2277            server.records.insert(
2278                ClientIdentifier::from(&disc),
2279                // Manually initialize because new() assumes an unexpired lease.
2280                LeaseRecord {
2281                    current: None,
2282                    previous: Some(bound_client_ip),
2283                    options: Vec::new(),
2284                    lease_start_epoch_seconds: time_source
2285                        .now()
2286                        .duration_since(std::time::UNIX_EPOCH)
2287                        .expect("invalid time value")
2288                        .as_secs(),
2289                    lease_length_seconds: std::u32::MIN
2290                },
2291            ),
2292            None
2293        );
2294
2295        let response = server.dispatch(disc).unwrap();
2296
2297        assert_eq!(extract_message(response).yiaddr, free_ip);
2298        assert_matches::assert_matches!(
2299            server.store.expect("missing store").actions().as_slice(),
2300            [DataStoreAction::StoreClientRecord {client_id: id, ..}] if *id == client_id
2301        );
2302    }
2303
2304    #[test]
2305    fn dispatch_with_discover_expired_client_binding_returns_available_requested_addr() {
2306        let (mut server, time_source) = new_test_minimal_server_with_time_source();
2307        let mut disc = new_test_discover();
2308        let client_id = ClientIdentifier::from(&disc);
2309
2310        let bound_client_ip = random_ipv4_generator();
2311        let requested_ip = random_ipv4_generator();
2312
2313        assert!(server.pool.allocated.insert(bound_client_ip));
2314        assert!(server.pool.universe.insert(requested_ip));
2315
2316        disc.options.push(DhcpOption::RequestedIpAddress(requested_ip));
2317
2318        assert_matches::assert_matches!(
2319            server.records.insert(
2320                ClientIdentifier::from(&disc),
2321                // Manually initialize because new() assumes an unexpired lease.
2322                LeaseRecord {
2323                    current: None,
2324                    previous: Some(bound_client_ip),
2325                    options: Vec::new(),
2326                    lease_start_epoch_seconds: time_source
2327                        .now()
2328                        .duration_since(std::time::UNIX_EPOCH)
2329                        .expect("invalid time value")
2330                        .as_secs(),
2331                    lease_length_seconds: std::u32::MIN
2332                },
2333            ),
2334            None
2335        );
2336
2337        let response = server.dispatch(disc).unwrap();
2338
2339        assert_eq!(extract_message(response).yiaddr, requested_ip);
2340        assert_matches::assert_matches!(
2341            server.store.expect("missing store").actions().as_slice(),
2342            [DataStoreAction::StoreClientRecord {client_id: id, ..}] if *id == client_id
2343        );
2344    }
2345
2346    #[test]
2347    fn dispatch_with_discover_expired_client_binding_returns_next_addr_for_unavailable_requested_addr()
2348     {
2349        let (mut server, time_source) = new_test_minimal_server_with_time_source();
2350        let mut disc = new_test_discover();
2351        let client_id = ClientIdentifier::from(&disc);
2352
2353        let bound_client_ip = random_ipv4_generator();
2354        let requested_ip = random_ipv4_generator();
2355        let free_ip = random_ipv4_generator();
2356
2357        assert!(server.pool.allocated.insert(bound_client_ip));
2358        assert!(server.pool.allocated.insert(requested_ip));
2359        assert!(server.pool.universe.insert(free_ip));
2360
2361        disc.options.push(DhcpOption::RequestedIpAddress(requested_ip));
2362
2363        assert_matches::assert_matches!(
2364            server.records.insert(
2365                ClientIdentifier::from(&disc),
2366                // Manually initialize because new() assumes an unexpired lease.
2367                LeaseRecord {
2368                    current: None,
2369                    previous: Some(bound_client_ip),
2370                    options: Vec::new(),
2371                    lease_start_epoch_seconds: time_source
2372                        .now()
2373                        .duration_since(std::time::UNIX_EPOCH)
2374                        .expect("invalid time value")
2375                        .as_secs(),
2376                    lease_length_seconds: std::u32::MIN
2377                },
2378            ),
2379            None
2380        );
2381
2382        let response = server.dispatch(disc).unwrap();
2383
2384        assert_eq!(extract_message(response).yiaddr, free_ip);
2385        assert_matches::assert_matches!(
2386            server.store.expect("missing store").actions().as_slice(),
2387            [DataStoreAction::StoreClientRecord {client_id: id, ..}] if *id == client_id
2388        );
2389    }
2390
2391    #[test]
2392    fn dispatch_with_discover_available_requested_addr_returns_requested_addr() {
2393        let mut server = new_test_minimal_server();
2394        let mut disc = new_test_discover();
2395        let client_id = ClientIdentifier::from(&disc);
2396
2397        let requested_ip = random_ipv4_generator();
2398        let free_ip_1 = random_ipv4_generator();
2399        let free_ip_2 = random_ipv4_generator();
2400
2401        assert!(server.pool.universe.insert(free_ip_1));
2402        assert!(server.pool.universe.insert(requested_ip));
2403        assert!(server.pool.universe.insert(free_ip_2));
2404
2405        // Update discover message to request for a specific ip
2406        // which is available in server pool.
2407        disc.options.push(DhcpOption::RequestedIpAddress(requested_ip));
2408
2409        let response = server.dispatch(disc).unwrap();
2410
2411        assert_eq!(extract_message(response).yiaddr, requested_ip);
2412        assert_matches::assert_matches!(
2413            server.store.expect("missing store").actions().as_slice(),
2414            [DataStoreAction::StoreClientRecord {client_id: id, ..}] if *id == client_id
2415        );
2416    }
2417
2418    #[test]
2419    fn dispatch_with_discover_unavailable_requested_addr_returns_next_free_addr() {
2420        let mut server = new_test_minimal_server();
2421        let mut disc = new_test_discover();
2422        let client_id = ClientIdentifier::from(&disc);
2423
2424        let requested_ip = random_ipv4_generator();
2425        let free_ip_1 = random_ipv4_generator();
2426
2427        assert!(server.pool.allocated.insert(requested_ip));
2428        assert!(server.pool.universe.insert(free_ip_1));
2429
2430        disc.options.push(DhcpOption::RequestedIpAddress(requested_ip));
2431
2432        let response = server.dispatch(disc).unwrap();
2433
2434        assert_eq!(extract_message(response).yiaddr, free_ip_1);
2435        assert_matches::assert_matches!(
2436            server.store.expect("missing store").actions().as_slice(),
2437            [DataStoreAction::StoreClientRecord {client_id: id, ..}] if *id == client_id
2438        );
2439    }
2440
2441    #[test]
2442    fn dispatch_with_discover_unavailable_requested_addr_no_available_addr_returns_error() {
2443        let mut server = new_test_minimal_server();
2444        let mut disc = new_test_discover();
2445
2446        let requested_ip = random_ipv4_generator();
2447
2448        assert!(server.pool.allocated.insert(requested_ip));
2449
2450        disc.options.push(DhcpOption::RequestedIpAddress(requested_ip));
2451
2452        assert_eq!(
2453            server.dispatch(disc),
2454            Err(ServerError::ServerAddressPoolFailure(AddressPoolError::Ipv4AddrExhaustion))
2455        );
2456    }
2457
2458    #[test]
2459    fn dispatch_with_discover_no_requested_addr_no_available_addr_returns_error() {
2460        let mut server = new_test_minimal_server();
2461        let disc = new_test_discover();
2462        server.pool.universe.clear();
2463
2464        assert_eq!(
2465            server.dispatch(disc),
2466            Err(ServerError::ServerAddressPoolFailure(AddressPoolError::Ipv4AddrExhaustion))
2467        );
2468    }
2469
2470    fn test_dispatch_with_bogus_client_message_returns_error(message_type: MessageType) {
2471        let mut server = new_test_minimal_server();
2472
2473        assert_eq!(
2474            server.dispatch(Message {
2475                op: OpCode::BOOTREQUEST,
2476                xid: 0,
2477                secs: 0,
2478                bdcast_flag: false,
2479                ciaddr: Ipv4Addr::UNSPECIFIED,
2480                yiaddr: Ipv4Addr::UNSPECIFIED,
2481                siaddr: Ipv4Addr::UNSPECIFIED,
2482                giaddr: Ipv4Addr::UNSPECIFIED,
2483                chaddr: MacAddr::new([0; 6]),
2484                sname: String::new(),
2485                file: String::new(),
2486                options: vec![DhcpOption::DhcpMessageType(message_type),],
2487            }),
2488            Err(ServerError::UnexpectedClientMessageType(message_type))
2489        );
2490    }
2491
2492    #[test]
2493    fn dispatch_with_client_offer_message_returns_error() {
2494        test_dispatch_with_bogus_client_message_returns_error(MessageType::DHCPOFFER)
2495    }
2496
2497    #[test]
2498    fn dispatch_with_client_ack_message_returns_error() {
2499        test_dispatch_with_bogus_client_message_returns_error(MessageType::DHCPACK)
2500    }
2501
2502    #[test]
2503    fn dispatch_with_client_nak_message_returns_error() {
2504        test_dispatch_with_bogus_client_message_returns_error(MessageType::DHCPNAK)
2505    }
2506
2507    #[test]
2508    fn dispatch_with_selecting_request_returns_correct_ack() {
2509        test_selecting(true)
2510    }
2511
2512    #[test]
2513    fn dispatch_with_selecting_request_bdcast_unset_returns_unicast_ack() {
2514        test_selecting(false)
2515    }
2516
2517    fn test_selecting(broadcast: bool) {
2518        let (mut server, time_source) = new_test_minimal_server_with_time_source();
2519        let requested_ip = random_ipv4_generator();
2520        let req = {
2521            let mut req = new_test_request_selecting_state(&server, requested_ip);
2522            req.bdcast_flag = broadcast;
2523            req
2524        };
2525
2526        assert!(server.pool.allocated.insert(requested_ip));
2527
2528        let server_id = server.params.server_ips.first().unwrap();
2529        let router = get_router(&server).expect("failed to get router from server");
2530        let dns_server = get_dns_server(&server).expect("failed to get dns server from the server");
2531        assert_matches::assert_matches!(
2532            server.records.insert(
2533                ClientIdentifier::from(&req),
2534                LeaseRecord::new(
2535                    Some(requested_ip),
2536                    vec![
2537                        DhcpOption::ServerIdentifier(*server_id),
2538                        DhcpOption::IpAddressLeaseTime(server.params.lease_length.default_seconds),
2539                        DhcpOption::RenewalTimeValue(
2540                            server.params.lease_length.default_seconds / 2
2541                        ),
2542                        DhcpOption::RebindingTimeValue(
2543                            (server.params.lease_length.default_seconds * 3) / 4,
2544                        ),
2545                        DhcpOption::SubnetMask(DEFAULT_PREFIX_LENGTH),
2546                        DhcpOption::Router(router),
2547                        DhcpOption::DomainNameServer(dns_server),
2548                    ],
2549                    time_source.now(),
2550                    std::u32::MAX,
2551                )
2552                .expect("failed to create lease record"),
2553            ),
2554            None
2555        );
2556
2557        let mut expected_ack = new_test_ack(&req, &server);
2558        expected_ack.yiaddr = requested_ip;
2559        let expected_response = if broadcast {
2560            Ok(ServerAction::SendResponse(expected_ack, ResponseTarget::Broadcast))
2561        } else {
2562            Ok(ServerAction::SendResponse(
2563                expected_ack,
2564                ResponseTarget::Unicast(requested_ip, Some(req.chaddr)),
2565            ))
2566        };
2567        assert_eq!(server.dispatch(req), expected_response,);
2568    }
2569
2570    #[test]
2571    fn dispatch_with_selecting_request_maintains_server_invariants() {
2572        let (mut server, time_source) = new_test_minimal_server_with_time_source();
2573        let requested_ip = random_ipv4_generator();
2574        let req = new_test_request_selecting_state(&server, requested_ip);
2575
2576        let client_id = ClientIdentifier::from(&req);
2577
2578        assert!(server.pool.allocated.insert(requested_ip));
2579        assert_matches::assert_matches!(
2580            server.records.insert(
2581                client_id.clone(),
2582                LeaseRecord::new(Some(requested_ip), Vec::new(), time_source.now(), std::u32::MAX)
2583                    .expect("failed to create lease record"),
2584            ),
2585            None
2586        );
2587        let _response = server.dispatch(req).unwrap();
2588        assert!(server.records.contains_key(&client_id));
2589        assert!(server.pool.addr_is_allocated(requested_ip));
2590    }
2591
2592    #[test]
2593    fn dispatch_with_selecting_request_wrong_server_ip_returns_error() {
2594        let mut server = new_test_minimal_server();
2595        let mut req = new_test_request_selecting_state(&server, random_ipv4_generator());
2596
2597        // Update request to have a server ip different from actual server ip.
2598        assert_matches::assert_matches!(
2599            req.options.remove(req.options.len() - 1),
2600            DhcpOption::ServerIdentifier { .. }
2601        );
2602        req.options.push(DhcpOption::ServerIdentifier(random_ipv4_generator()));
2603
2604        let server_ip = *server.params.server_ips.first().expect("server missing IP address");
2605        assert_eq!(server.dispatch(req), Err(ServerError::IncorrectDHCPServer(server_ip)));
2606    }
2607
2608    #[test]
2609    fn dispatch_with_selecting_request_unknown_client_mac_returns_nak_maintains_server_invariants()
2610    {
2611        let mut server = new_test_minimal_server();
2612        let requested_ip = random_ipv4_generator();
2613        let req = new_test_request_selecting_state(&server, requested_ip);
2614
2615        let client_id = ClientIdentifier::from(&req);
2616
2617        let expected_nak = new_test_nak(
2618            &req,
2619            &server,
2620            NakReason::ClientValidationFailure(ServerError::UnknownClientId(client_id.clone())),
2621        );
2622        assert_eq!(
2623            server.dispatch(req),
2624            Ok(ServerAction::SendResponse(expected_nak, ResponseTarget::Broadcast))
2625        );
2626        assert!(!server.records.contains_key(&client_id));
2627        assert!(!server.pool.addr_is_allocated(requested_ip));
2628    }
2629
2630    #[test]
2631    fn dispatch_with_selecting_request_mismatched_requested_addr_returns_nak() {
2632        let (mut server, time_source) = new_test_minimal_server_with_time_source();
2633        let client_requested_ip = random_ipv4_generator();
2634        let req = new_test_request_selecting_state(&server, client_requested_ip);
2635
2636        let server_offered_ip = random_ipv4_generator();
2637
2638        assert!(server.pool.allocated.insert(server_offered_ip));
2639
2640        assert_matches::assert_matches!(
2641            server.records.insert(
2642                ClientIdentifier::from(&req),
2643                LeaseRecord::new(
2644                    Some(server_offered_ip),
2645                    Vec::new(),
2646                    time_source.now(),
2647                    std::u32::MAX,
2648                )
2649                .expect("failed to create lease record"),
2650            ),
2651            None
2652        );
2653
2654        let expected_nak = new_test_nak(
2655            &req,
2656            &server,
2657            NakReason::ClientValidationFailure(ServerError::RequestedIpOfferIpMismatch(
2658                client_requested_ip,
2659                server_offered_ip,
2660            )),
2661        );
2662        assert_eq!(
2663            server.dispatch(req),
2664            Ok(ServerAction::SendResponse(expected_nak, ResponseTarget::Broadcast))
2665        );
2666    }
2667
2668    #[test]
2669    fn dispatch_with_selecting_request_expired_client_binding_returns_nak() {
2670        let (mut server, time_source) = new_test_minimal_server_with_time_source();
2671        let requested_ip = random_ipv4_generator();
2672        let req = new_test_request_selecting_state(&server, requested_ip);
2673
2674        assert!(server.pool.universe.insert(requested_ip));
2675        assert!(server.pool.allocated.insert(requested_ip));
2676
2677        assert_matches::assert_matches!(
2678            server.records.insert(
2679                ClientIdentifier::from(&req),
2680                LeaseRecord::new(Some(requested_ip), Vec::new(), time_source.now(), std::u32::MIN)
2681                    .expect("failed to create lease record"),
2682            ),
2683            None
2684        );
2685
2686        let expected_nak = new_test_nak(
2687            &req,
2688            &server,
2689            NakReason::ClientValidationFailure(ServerError::ExpiredLeaseRecord),
2690        );
2691        assert_eq!(
2692            server.dispatch(req),
2693            Ok(ServerAction::SendResponse(expected_nak, ResponseTarget::Broadcast))
2694        );
2695    }
2696
2697    #[test]
2698    fn dispatch_with_selecting_request_no_reserved_addr_returns_nak() {
2699        let (mut server, time_source) = new_test_minimal_server_with_time_source();
2700        let requested_ip = random_ipv4_generator();
2701        let req = new_test_request_selecting_state(&server, requested_ip);
2702
2703        assert_matches::assert_matches!(
2704            server.records.insert(
2705                ClientIdentifier::from(&req),
2706                LeaseRecord::new(Some(requested_ip), Vec::new(), time_source.now(), std::u32::MAX)
2707                    .expect("failed to create lese record"),
2708            ),
2709            None
2710        );
2711
2712        let expected_nak = new_test_nak(
2713            &req,
2714            &server,
2715            NakReason::ClientValidationFailure(ServerError::UnidentifiedRequestedIp(requested_ip)),
2716        );
2717        assert_eq!(
2718            server.dispatch(req),
2719            Ok(ServerAction::SendResponse(expected_nak, ResponseTarget::Broadcast))
2720        );
2721    }
2722
2723    #[test]
2724    fn dispatch_with_init_boot_request_returns_correct_ack() {
2725        test_init_reboot(true)
2726    }
2727
2728    #[test]
2729    fn dispatch_with_init_boot_bdcast_unset_request_returns_correct_ack() {
2730        test_init_reboot(false)
2731    }
2732
2733    fn test_init_reboot(broadcast: bool) {
2734        let (mut server, time_source) = new_test_minimal_server_with_time_source();
2735        let mut req = new_test_request();
2736        req.bdcast_flag = broadcast;
2737
2738        // For init-reboot, server and requested ip must be on the same subnet.
2739        // Hard-coding ip values here to achieve that.
2740        let init_reboot_client_ip = std_ip_v4!("192.168.1.60");
2741        server.params.server_ips = vec![std_ip_v4!("192.168.1.1")];
2742
2743        assert!(server.pool.allocated.insert(init_reboot_client_ip));
2744
2745        // Update request to have the test requested ip.
2746        req.options.push(DhcpOption::RequestedIpAddress(init_reboot_client_ip));
2747
2748        let server_id = server.params.server_ips.first().unwrap();
2749        let router = get_router(&server).expect("failed to get router");
2750        let dns_server = get_dns_server(&server).expect("failed to get dns server");
2751        assert_matches::assert_matches!(
2752            server.records.insert(
2753                ClientIdentifier::from(&req),
2754                LeaseRecord::new(
2755                    Some(init_reboot_client_ip),
2756                    vec![
2757                        DhcpOption::ServerIdentifier(*server_id),
2758                        DhcpOption::IpAddressLeaseTime(server.params.lease_length.default_seconds),
2759                        DhcpOption::RenewalTimeValue(
2760                            server.params.lease_length.default_seconds / 2
2761                        ),
2762                        DhcpOption::RebindingTimeValue(
2763                            (server.params.lease_length.default_seconds * 3) / 4,
2764                        ),
2765                        DhcpOption::SubnetMask(DEFAULT_PREFIX_LENGTH),
2766                        DhcpOption::Router(router),
2767                        DhcpOption::DomainNameServer(dns_server),
2768                    ],
2769                    time_source.now(),
2770                    std::u32::MAX,
2771                )
2772                .expect("failed to create lease record"),
2773            ),
2774            None
2775        );
2776
2777        let mut expected_ack = new_test_ack(&req, &server);
2778        expected_ack.yiaddr = init_reboot_client_ip;
2779
2780        let expected_response = if broadcast {
2781            Ok(ServerAction::SendResponse(expected_ack, ResponseTarget::Broadcast))
2782        } else {
2783            Ok(ServerAction::SendResponse(
2784                expected_ack,
2785                ResponseTarget::Unicast(init_reboot_client_ip, Some(req.chaddr)),
2786            ))
2787        };
2788        assert_eq!(server.dispatch(req), expected_response,);
2789    }
2790
2791    #[test]
2792    fn dispatch_with_init_boot_request_client_on_wrong_subnet_returns_nak() {
2793        let mut server = new_test_minimal_server();
2794        let mut req = new_test_request();
2795
2796        // Update request to have requested ip not on same subnet as server.
2797        req.options.push(DhcpOption::RequestedIpAddress(random_ipv4_generator()));
2798
2799        // The returned nak should be from this recipient server.
2800        let expected_nak = new_test_nak(&req, &server, NakReason::DifferentSubnets);
2801        assert_eq!(
2802            server.dispatch(req),
2803            Ok(ServerAction::SendResponse(expected_nak, ResponseTarget::Broadcast))
2804        );
2805    }
2806
2807    #[test]
2808    fn dispatch_with_init_boot_request_with_giaddr_set_returns_nak_with_broadcast_bit_set() {
2809        let mut server = new_test_minimal_server();
2810        let mut req = new_test_request();
2811        req.giaddr = random_ipv4_generator();
2812
2813        // Update request to have requested ip not on same subnet as server,
2814        // to ensure we get a nak.
2815        req.options.push(DhcpOption::RequestedIpAddress(random_ipv4_generator()));
2816
2817        let response = server.dispatch(req).unwrap();
2818
2819        assert!(extract_message(response).bdcast_flag);
2820    }
2821
2822    #[test]
2823    fn dispatch_with_init_boot_request_unknown_client_mac_returns_error() {
2824        let mut server = new_test_minimal_server();
2825        let mut req = new_test_request();
2826
2827        let client_id = ClientIdentifier::from(&req);
2828
2829        // Update requested ip and server ip to be on the same subnet.
2830        req.options.push(DhcpOption::RequestedIpAddress(std_ip_v4!("192.165.30.45")));
2831        server.params.server_ips = vec![std_ip_v4!("192.165.30.1")];
2832
2833        assert_eq!(server.dispatch(req), Err(ServerError::UnknownClientId(client_id)));
2834    }
2835
2836    #[test]
2837    fn dispatch_with_init_boot_request_mismatched_requested_addr_returns_nak() {
2838        let (mut server, time_source) = new_test_minimal_server_with_time_source();
2839        let mut req = new_test_request();
2840
2841        // Update requested ip and server ip to be on the same subnet.
2842        let init_reboot_client_ip = std_ip_v4!("192.165.25.4");
2843        req.options.push(DhcpOption::RequestedIpAddress(init_reboot_client_ip));
2844        server.params.server_ips = vec![std_ip_v4!("192.165.25.1")];
2845
2846        let server_cached_ip = std_ip_v4!("192.165.25.10");
2847        assert!(server.pool.allocated.insert(server_cached_ip));
2848        assert_matches::assert_matches!(
2849            server.records.insert(
2850                ClientIdentifier::from(&req),
2851                LeaseRecord::new(
2852                    Some(server_cached_ip),
2853                    Vec::new(),
2854                    time_source.now(),
2855                    std::u32::MAX,
2856                )
2857                .expect("failed to create lease record"),
2858            ),
2859            None
2860        );
2861
2862        let expected_nak = new_test_nak(
2863            &req,
2864            &server,
2865            NakReason::ClientValidationFailure(ServerError::RequestedIpOfferIpMismatch(
2866                init_reboot_client_ip,
2867                server_cached_ip,
2868            )),
2869        );
2870        assert_eq!(
2871            server.dispatch(req),
2872            Ok(ServerAction::SendResponse(expected_nak, ResponseTarget::Broadcast))
2873        );
2874    }
2875
2876    #[test]
2877    fn dispatch_with_init_boot_request_expired_client_binding_returns_nak() {
2878        let (mut server, time_source) = new_test_minimal_server_with_time_source();
2879        let mut req = new_test_request();
2880
2881        let init_reboot_client_ip = std_ip_v4!("192.165.25.4");
2882        req.options.push(DhcpOption::RequestedIpAddress(init_reboot_client_ip));
2883        server.params.server_ips = vec![std_ip_v4!("192.165.25.1")];
2884
2885        assert!(server.pool.universe.insert(init_reboot_client_ip));
2886        assert!(server.pool.allocated.insert(init_reboot_client_ip));
2887        // Expire client binding to make it invalid.
2888        assert_matches::assert_matches!(
2889            server.records.insert(
2890                ClientIdentifier::from(&req),
2891                LeaseRecord::new(
2892                    Some(init_reboot_client_ip),
2893                    Vec::new(),
2894                    time_source.now(),
2895                    std::u32::MIN,
2896                )
2897                .expect("failed to create lease record"),
2898            ),
2899            None
2900        );
2901
2902        let expected_nak = new_test_nak(
2903            &req,
2904            &server,
2905            NakReason::ClientValidationFailure(ServerError::ExpiredLeaseRecord),
2906        );
2907
2908        assert_eq!(
2909            server.dispatch(req),
2910            Ok(ServerAction::SendResponse(expected_nak, ResponseTarget::Broadcast))
2911        );
2912    }
2913
2914    #[test]
2915    fn dispatch_with_init_boot_request_no_reserved_addr_returns_nak() {
2916        let (mut server, time_source) = new_test_minimal_server_with_time_source();
2917        let mut req = new_test_request();
2918
2919        let init_reboot_client_ip = std_ip_v4!("192.165.25.4");
2920        req.options.push(DhcpOption::RequestedIpAddress(init_reboot_client_ip));
2921        server.params.server_ips = vec![std_ip_v4!("192.165.25.1")];
2922
2923        assert_matches::assert_matches!(
2924            server.records.insert(
2925                ClientIdentifier::from(&req),
2926                LeaseRecord::new(
2927                    Some(init_reboot_client_ip),
2928                    Vec::new(),
2929                    time_source.now(),
2930                    std::u32::MAX,
2931                )
2932                .expect("failed to create lease record"),
2933            ),
2934            None
2935        );
2936
2937        let expected_nak = new_test_nak(
2938            &req,
2939            &server,
2940            NakReason::ClientValidationFailure(ServerError::UnidentifiedRequestedIp(
2941                init_reboot_client_ip,
2942            )),
2943        );
2944
2945        assert_eq!(
2946            server.dispatch(req),
2947            Ok(ServerAction::SendResponse(expected_nak, ResponseTarget::Broadcast))
2948        );
2949    }
2950
2951    #[test]
2952    fn dispatch_with_renewing_request_returns_correct_ack() {
2953        let (mut server, time_source) = new_test_minimal_server_with_time_source();
2954        let mut req = new_test_request();
2955
2956        let bound_client_ip = random_ipv4_generator();
2957
2958        assert!(server.pool.allocated.insert(bound_client_ip));
2959        req.ciaddr = bound_client_ip;
2960
2961        let server_id = server.params.server_ips.first().unwrap();
2962        let router = get_router(&server).expect("failed to get router");
2963        let dns_server = get_dns_server(&server).expect("failed to get dns server");
2964        assert_matches::assert_matches!(
2965            server.records.insert(
2966                ClientIdentifier::from(&req),
2967                LeaseRecord::new(
2968                    Some(bound_client_ip),
2969                    vec![
2970                        DhcpOption::ServerIdentifier(*server_id),
2971                        DhcpOption::IpAddressLeaseTime(server.params.lease_length.default_seconds),
2972                        DhcpOption::RenewalTimeValue(
2973                            server.params.lease_length.default_seconds / 2
2974                        ),
2975                        DhcpOption::RebindingTimeValue(
2976                            (server.params.lease_length.default_seconds * 3) / 4,
2977                        ),
2978                        DhcpOption::SubnetMask(DEFAULT_PREFIX_LENGTH),
2979                        DhcpOption::Router(router),
2980                        DhcpOption::DomainNameServer(dns_server),
2981                    ],
2982                    time_source.now(),
2983                    std::u32::MAX,
2984                )
2985                .expect("failed to create lease record"),
2986            ),
2987            None
2988        );
2989
2990        let mut expected_ack = new_test_ack(&req, &server);
2991        expected_ack.yiaddr = bound_client_ip;
2992        expected_ack.ciaddr = bound_client_ip;
2993
2994        let expected_dest = req.ciaddr;
2995
2996        assert_eq!(
2997            server.dispatch(req),
2998            Ok(ServerAction::SendResponse(
2999                expected_ack,
3000                ResponseTarget::Unicast(expected_dest, None)
3001            ))
3002        );
3003    }
3004
3005    #[test]
3006    fn dispatch_with_renewing_request_unknown_client_mac_returns_nak() {
3007        let mut server = new_test_minimal_server();
3008        let mut req = new_test_request();
3009
3010        let bound_client_ip = random_ipv4_generator();
3011        let client_id = ClientIdentifier::from(&req);
3012
3013        req.ciaddr = bound_client_ip;
3014
3015        let expected_nak = new_test_nak(
3016            &req,
3017            &server,
3018            NakReason::ClientValidationFailure(ServerError::UnknownClientId(client_id)),
3019        );
3020        assert_eq!(
3021            server.dispatch(req),
3022            Ok(ServerAction::SendResponse(expected_nak, ResponseTarget::Broadcast))
3023        );
3024    }
3025
3026    #[test]
3027    fn dispatch_with_renewing_request_mismatched_requested_addr_returns_nak() {
3028        let (mut server, time_source) = new_test_minimal_server_with_time_source();
3029        let mut req = new_test_request();
3030
3031        let client_renewal_ip = random_ipv4_generator();
3032        let bound_client_ip = random_ipv4_generator();
3033
3034        assert!(server.pool.allocated.insert(bound_client_ip));
3035        req.ciaddr = client_renewal_ip;
3036
3037        assert_matches::assert_matches!(
3038            server.records.insert(
3039                ClientIdentifier::from(&req),
3040                LeaseRecord::new(
3041                    Some(bound_client_ip),
3042                    Vec::new(),
3043                    time_source.now(),
3044                    std::u32::MAX
3045                )
3046                .expect("failed to create lease record"),
3047            ),
3048            None
3049        );
3050
3051        let expected_nak = new_test_nak(
3052            &req,
3053            &server,
3054            NakReason::ClientValidationFailure(ServerError::RequestedIpOfferIpMismatch(
3055                client_renewal_ip,
3056                bound_client_ip,
3057            )),
3058        );
3059        assert_eq!(
3060            server.dispatch(req),
3061            Ok(ServerAction::SendResponse(expected_nak, ResponseTarget::Broadcast))
3062        );
3063    }
3064
3065    #[test]
3066    fn dispatch_with_renewing_request_expired_client_binding_returns_nak() {
3067        let (mut server, time_source) = new_test_minimal_server_with_time_source();
3068        let mut req = new_test_request();
3069
3070        let bound_client_ip = random_ipv4_generator();
3071
3072        assert!(server.pool.universe.insert(bound_client_ip));
3073        assert!(server.pool.allocated.insert(bound_client_ip));
3074        req.ciaddr = bound_client_ip;
3075
3076        assert_matches::assert_matches!(
3077            server.records.insert(
3078                ClientIdentifier::from(&req),
3079                LeaseRecord::new(
3080                    Some(bound_client_ip),
3081                    Vec::new(),
3082                    time_source.now(),
3083                    std::u32::MIN
3084                )
3085                .expect("failed to create lease record"),
3086            ),
3087            None
3088        );
3089
3090        let expected_nak = new_test_nak(
3091            &req,
3092            &server,
3093            NakReason::ClientValidationFailure(ServerError::ExpiredLeaseRecord),
3094        );
3095        assert_eq!(
3096            server.dispatch(req),
3097            Ok(ServerAction::SendResponse(expected_nak, ResponseTarget::Broadcast))
3098        );
3099    }
3100
3101    #[test]
3102    fn dispatch_with_renewing_request_no_reserved_addr_returns_nak() {
3103        let (mut server, time_source) = new_test_minimal_server_with_time_source();
3104        let mut req = new_test_request();
3105
3106        let bound_client_ip = random_ipv4_generator();
3107        req.ciaddr = bound_client_ip;
3108
3109        assert_matches::assert_matches!(
3110            server.records.insert(
3111                ClientIdentifier::from(&req),
3112                LeaseRecord::new(
3113                    Some(bound_client_ip),
3114                    Vec::new(),
3115                    time_source.now(),
3116                    std::u32::MAX
3117                )
3118                .expect("failed to create lease record"),
3119            ),
3120            None
3121        );
3122
3123        let expected_nak = new_test_nak(
3124            &req,
3125            &server,
3126            NakReason::ClientValidationFailure(ServerError::UnidentifiedRequestedIp(
3127                bound_client_ip,
3128            )),
3129        );
3130        assert_eq!(
3131            server.dispatch(req),
3132            Ok(ServerAction::SendResponse(expected_nak, ResponseTarget::Broadcast))
3133        );
3134    }
3135
3136    #[test]
3137    fn dispatch_with_unknown_client_state_returns_error() {
3138        let mut server = new_test_minimal_server();
3139
3140        let req = new_test_request();
3141
3142        assert_eq!(server.dispatch(req), Err(ServerError::UnknownClientStateDuringRequest));
3143    }
3144
3145    #[test]
3146    fn get_client_state_with_selecting_returns_selecting() {
3147        let mut req = new_test_request();
3148
3149        // Selecting state request must have server id and requested ip populated.
3150        req.options.push(DhcpOption::ServerIdentifier(random_ipv4_generator()));
3151        req.options.push(DhcpOption::RequestedIpAddress(random_ipv4_generator()));
3152
3153        assert_eq!(get_client_state(&req), Ok(ClientState::Selecting));
3154    }
3155
3156    #[test]
3157    fn get_client_state_with_initreboot_returns_initreboot() {
3158        let mut req = new_test_request();
3159
3160        // Init reboot state request must have requested ip populated.
3161        req.options.push(DhcpOption::RequestedIpAddress(random_ipv4_generator()));
3162
3163        assert_eq!(get_client_state(&req), Ok(ClientState::InitReboot));
3164    }
3165
3166    #[test]
3167    fn get_client_state_with_renewing_returns_renewing() {
3168        let mut req = new_test_request();
3169
3170        // Renewing state request must have ciaddr populated.
3171        req.ciaddr = random_ipv4_generator();
3172
3173        assert_eq!(get_client_state(&req), Ok(ClientState::Renewing));
3174    }
3175
3176    #[test]
3177    fn get_client_state_with_unknown_returns_unknown() {
3178        let msg = new_test_request();
3179
3180        assert_eq!(get_client_state(&msg), Err(()));
3181    }
3182
3183    #[test]
3184    fn dispatch_with_client_msg_missing_message_type_option_returns_error() {
3185        let mut server = new_test_minimal_server();
3186        let mut msg = new_test_request();
3187        msg.options.clear();
3188
3189        assert_eq!(
3190            server.dispatch(msg),
3191            Err(ServerError::ClientMessageError(ProtocolError::MissingOption(
3192                OptionCode::DhcpMessageType
3193            )))
3194        );
3195    }
3196
3197    #[test]
3198    fn release_expired_leases_with_none_expired_releases_none() {
3199        let (mut server, mut time_source) = new_test_minimal_server_with_time_source();
3200        server.pool.universe.clear();
3201
3202        // Insert client 1 bindings.
3203        let client_1_ip = random_ipv4_generator();
3204        let client_1_id = ClientIdentifier::from(random_mac_generator());
3205        let client_opts = [DhcpOption::IpAddressLeaseTime(u32::MAX)];
3206        assert!(server.pool.universe.insert(client_1_ip));
3207        server
3208            .store_client_record(client_1_ip, client_1_id.clone(), &client_opts)
3209            .expect("failed to store client record");
3210
3211        // Insert client 2 bindings.
3212        let client_2_ip = random_ipv4_generator();
3213        let client_2_id = ClientIdentifier::from(random_mac_generator());
3214        assert!(server.pool.universe.insert(client_2_ip));
3215        server
3216            .store_client_record(client_2_ip, client_2_id.clone(), &client_opts)
3217            .expect("failed to store client record");
3218
3219        // Insert client 3 bindings.
3220        let client_3_ip = random_ipv4_generator();
3221        let client_3_id = ClientIdentifier::from(random_mac_generator());
3222        assert!(server.pool.universe.insert(client_3_ip));
3223        server
3224            .store_client_record(client_3_ip, client_3_id.clone(), &client_opts)
3225            .expect("failed to store client record");
3226
3227        let () = time_source.move_forward(Duration::from_secs(1));
3228        let () = server.release_expired_leases().expect("failed to release expired leases");
3229
3230        let client_ips: BTreeSet<_> = [client_1_ip, client_2_ip, client_3_ip].into();
3231        assert_matches::assert_matches!(server.records.get(&client_1_id), Some(LeaseRecord {current: Some(ip), previous: None, ..}) if *ip == client_1_ip);
3232        assert_matches::assert_matches!(server.records.get(&client_2_id), Some(LeaseRecord {current: Some(ip), previous: None, ..}) if *ip == client_2_ip);
3233        assert_matches::assert_matches!(server.records.get(&client_3_id), Some(LeaseRecord {current: Some(ip), previous: None, ..}) if *ip == client_3_ip);
3234        let available: Vec<_> = server.pool.available().collect();
3235        assert!(available.is_empty(), "{:?}", available);
3236        assert_eq!(server.pool.allocated, client_ips);
3237        assert_matches::assert_matches!(
3238            server.store.expect("missing store").actions().as_slice(),
3239            [
3240                DataStoreAction::StoreClientRecord { client_id: id_1, record: LeaseRecord { current: Some(ip1), previous: None, ..}, .. },
3241                DataStoreAction::StoreClientRecord { client_id: id_2, record: LeaseRecord { current: Some(ip2), previous: None, ..},.. },
3242                DataStoreAction::StoreClientRecord { client_id: id_3, record: LeaseRecord { current: Some(ip3), previous: None, ..},.. },
3243            ] if *id_1 == client_1_id && *id_2 == client_2_id && *id_3 == client_3_id &&
3244                *ip1 == client_1_ip && *ip2 == client_2_ip && *ip3 == client_3_ip
3245        );
3246    }
3247
3248    #[test]
3249    fn release_expired_leases_with_all_expired_releases_all() {
3250        let (mut server, mut time_source) = new_test_minimal_server_with_time_source();
3251        server.pool.universe.clear();
3252
3253        let client_1_ip = random_ipv4_generator();
3254        assert!(server.pool.universe.insert(client_1_ip));
3255        let client_1_id = ClientIdentifier::from(random_mac_generator());
3256        let () = server
3257            .store_client_record(
3258                client_1_ip,
3259                client_1_id.clone(),
3260                &[DhcpOption::IpAddressLeaseTime(0)],
3261            )
3262            .expect("failed to store client record");
3263
3264        let client_2_ip = random_ipv4_generator();
3265        assert!(server.pool.universe.insert(client_2_ip));
3266        let client_2_id = ClientIdentifier::from(random_mac_generator());
3267        let () = server
3268            .store_client_record(
3269                client_2_ip,
3270                client_2_id.clone(),
3271                &[DhcpOption::IpAddressLeaseTime(0)],
3272            )
3273            .expect("failed to store client record");
3274
3275        let client_3_ip = random_ipv4_generator();
3276        assert!(server.pool.universe.insert(client_3_ip));
3277        let client_3_id = ClientIdentifier::from(random_mac_generator());
3278        let () = server
3279            .store_client_record(
3280                client_3_ip,
3281                client_3_id.clone(),
3282                &[DhcpOption::IpAddressLeaseTime(0)],
3283            )
3284            .expect("failed to store client record");
3285
3286        let () = time_source.move_forward(Duration::from_secs(1));
3287        let () = server.release_expired_leases().expect("failed to release expired leases");
3288
3289        assert_eq!(server.records.len(), 3);
3290        assert_matches::assert_matches!(server.records.get(&client_1_id), Some(LeaseRecord {current: None, previous: Some(ip), ..}) if *ip == client_1_ip);
3291        assert_matches::assert_matches!(server.records.get(&client_2_id), Some(LeaseRecord {current: None, previous: Some(ip), ..}) if *ip == client_2_ip);
3292        assert_matches::assert_matches!(server.records.get(&client_3_id), Some(LeaseRecord {current: None, previous: Some(ip), ..}) if *ip == client_3_ip);
3293        assert_eq!(
3294            server.pool.available().collect::<HashSet<_>>(),
3295            [client_1_ip, client_2_ip, client_3_ip].into(),
3296        );
3297        assert!(server.pool.allocated.is_empty(), "{:?}", server.pool.allocated);
3298        // Delete actions occur in non-deterministic (HashMap iteration) order, so we must not
3299        // assert on the ordering of the deleted ids.
3300        assert_matches::assert_matches!(
3301            &server.store.expect("missing store").actions().as_slice()[..],
3302            [
3303                DataStoreAction::StoreClientRecord { client_id: id_1, record: LeaseRecord { current: Some(ip1), previous: None, ..}, .. },
3304                DataStoreAction::StoreClientRecord { client_id: id_2, record: LeaseRecord { current: Some(ip2), previous: None, ..},.. },
3305                DataStoreAction::StoreClientRecord { client_id: id_3, record: LeaseRecord { current: Some(ip3), previous: None, ..},.. },
3306                DataStoreAction::StoreClientRecord { client_id: update_id_1, record: LeaseRecord { current: None, previous: Some(update_ip_1), ..}, .. },
3307                DataStoreAction::StoreClientRecord { client_id: update_id_2, record: LeaseRecord { current: None, previous: Some(update_ip_2), ..}, .. },
3308                DataStoreAction::StoreClientRecord { client_id: update_id_3, record: LeaseRecord { current: None, previous: Some(update_ip_3), ..}, .. },
3309            ] if *id_1 == client_1_id && *id_2 == client_2_id && *id_3 == client_3_id &&
3310                *ip1 == client_1_ip && *ip2 == client_2_ip && *ip3 == client_3_ip &&
3311                [update_id_1, update_id_2, update_id_3].iter().all(|id| {
3312                    [&client_1_id, &client_2_id, &client_3_id].contains(id)
3313                }) &&
3314                [update_ip_1, update_ip_2, update_ip_3].iter().all(|ip| {
3315                    [&client_1_ip, &client_2_ip, &client_3_ip].contains(ip)
3316                })
3317        );
3318    }
3319
3320    #[test]
3321    fn release_expired_leases_with_some_expired_releases_expired() {
3322        let (mut server, mut time_source) = new_test_minimal_server_with_time_source();
3323        server.pool.universe.clear();
3324
3325        let client_1_ip = random_ipv4_generator();
3326        assert!(server.pool.universe.insert(client_1_ip));
3327        let client_1_id = ClientIdentifier::from(random_mac_generator());
3328        let () = server
3329            .store_client_record(
3330                client_1_ip,
3331                client_1_id.clone(),
3332                &[DhcpOption::IpAddressLeaseTime(u32::MAX)],
3333            )
3334            .expect("failed to store client record");
3335
3336        let client_2_ip = random_ipv4_generator();
3337        assert!(server.pool.universe.insert(client_2_ip));
3338        let client_2_id = ClientIdentifier::from(random_mac_generator());
3339        let () = server
3340            .store_client_record(
3341                client_2_ip,
3342                client_2_id.clone(),
3343                &[DhcpOption::IpAddressLeaseTime(0)],
3344            )
3345            .expect("failed to store client record");
3346
3347        let client_3_ip = random_ipv4_generator();
3348        assert!(server.pool.universe.insert(client_3_ip));
3349        let client_3_id = ClientIdentifier::from(random_mac_generator());
3350        let () = server
3351            .store_client_record(
3352                client_3_ip,
3353                client_3_id.clone(),
3354                &[DhcpOption::IpAddressLeaseTime(u32::MAX)],
3355            )
3356            .expect("failed to store client record");
3357
3358        let () = time_source.move_forward(Duration::from_secs(1));
3359        let () = server.release_expired_leases().expect("failed to release expired leases");
3360
3361        let client_ips: BTreeSet<_> = [client_1_ip, client_3_ip].into();
3362        assert_matches::assert_matches!(server.records.get(&client_1_id), Some(LeaseRecord {current: Some(ip), previous: None, ..}) if *ip == client_1_ip);
3363        assert_matches::assert_matches!(server.records.get(&client_2_id), Some(LeaseRecord {current: None, previous: Some(ip), ..}) if *ip == client_2_ip);
3364        assert_matches::assert_matches!(server.records.get(&client_3_id), Some(LeaseRecord {current: Some(ip), previous: None, ..}) if *ip == client_3_ip);
3365        assert_eq!(server.pool.available().collect::<Vec<_>>(), vec![client_2_ip]);
3366        assert_eq!(server.pool.allocated, client_ips);
3367        assert_matches::assert_matches!(
3368            server.store.expect("missing store").actions().as_slice(),
3369            [
3370                DataStoreAction::StoreClientRecord { client_id: id_1, record: LeaseRecord { current: Some(ip1), previous: None, ..}, .. },
3371                DataStoreAction::StoreClientRecord { client_id: id_2, record: LeaseRecord { current: Some(ip2), previous: None, ..},.. },
3372                DataStoreAction::StoreClientRecord { client_id: id_3, record: LeaseRecord { current: Some(ip3), previous: None, ..},.. },
3373                DataStoreAction::StoreClientRecord { client_id: update_id_1, record: LeaseRecord { current: None, previous: Some(update_ip_1), ..}, ..},
3374            ] if *id_1 == client_1_id && *id_2 == client_2_id && *id_3 == client_3_id && *update_id_1 == client_2_id &&
3375                *ip1 == client_1_ip && *ip2 == client_2_ip && *ip3 == client_3_ip && *update_ip_1 == client_2_ip
3376        );
3377    }
3378
3379    #[test]
3380    fn dispatch_with_known_release_updates_address_pool_retains_client_record() {
3381        let (mut server, time_source) = new_test_minimal_server_with_time_source();
3382        let mut release = new_test_release();
3383
3384        let release_ip = random_ipv4_generator();
3385        let client_id = ClientIdentifier::from(&release);
3386
3387        assert!(server.pool.universe.insert(release_ip));
3388        assert!(server.pool.allocated.insert(release_ip));
3389        release.ciaddr = release_ip;
3390
3391        let dns = random_ipv4_generator();
3392        let opts = vec![DhcpOption::DomainNameServer([dns].into())];
3393        let test_client_record = |client_addr: Option<Ipv4Addr>, opts: Vec<DhcpOption>| {
3394            LeaseRecord::new(client_addr, opts, time_source.now(), u32::MAX).unwrap()
3395        };
3396
3397        assert_matches::assert_matches!(
3398            server
3399                .records
3400                .insert(client_id.clone(), test_client_record(Some(release_ip), opts.clone())),
3401            None
3402        );
3403
3404        assert_eq!(server.dispatch(release), Ok(ServerAction::AddressRelease(release_ip)));
3405        assert_matches::assert_matches!(
3406            server.store.expect("missing store").actions().as_slice(),
3407            [
3408                DataStoreAction::StoreClientRecord { client_id: id, record: LeaseRecord {current: None, previous: Some(ip), options, ..}}
3409            ] if *id == client_id  &&  *ip == release_ip && *options == opts
3410        );
3411        assert!(!server.pool.addr_is_allocated(release_ip), "addr marked allocated");
3412        assert!(server.pool.addr_is_available(release_ip), "addr not marked available");
3413        assert!(server.records.contains_key(&client_id), "client record not retained");
3414        assert_matches::assert_matches!(
3415            server.records.get(&client_id),
3416            Some(LeaseRecord {current: None, previous: Some(ip), options, lease_length_seconds, ..})
3417               if *ip == release_ip && *options == opts && *lease_length_seconds == u32::MAX
3418        );
3419    }
3420
3421    #[test]
3422    fn dispatch_with_unknown_release_maintains_server_state_returns_unknown_mac_error() {
3423        let mut server = new_test_minimal_server();
3424        let mut release = new_test_release();
3425
3426        let release_ip = random_ipv4_generator();
3427        let client_id = ClientIdentifier::from(&release);
3428
3429        assert!(server.pool.allocated.insert(release_ip));
3430        release.ciaddr = release_ip;
3431
3432        assert_eq!(server.dispatch(release), Err(ServerError::UnknownClientId(client_id)));
3433
3434        assert!(server.pool.addr_is_allocated(release_ip), "addr not marked allocated");
3435        assert!(!server.pool.addr_is_available(release_ip), "addr still marked available");
3436    }
3437
3438    #[test]
3439    fn dispatch_with_inform_returns_correct_ack() {
3440        let mut server = new_test_minimal_server();
3441        let mut inform = new_test_inform();
3442
3443        let inform_client_ip = random_ipv4_generator();
3444
3445        inform.ciaddr = inform_client_ip;
3446
3447        let mut expected_ack = new_test_inform_ack(&inform, &server);
3448        expected_ack.ciaddr = inform_client_ip;
3449
3450        let expected_dest = inform.ciaddr;
3451
3452        assert_eq!(
3453            server.dispatch(inform),
3454            Ok(ServerAction::SendResponse(
3455                expected_ack,
3456                ResponseTarget::Unicast(expected_dest, None)
3457            ))
3458        );
3459    }
3460
3461    #[test_case(
3462        [OptionCode::DomainNameServer, OptionCode::SubnetMask, OptionCode::Router].into(),
3463        [OptionCode::DomainNameServer, OptionCode::SubnetMask, OptionCode::Router].into();
3464        "Valid order should be unmodified"
3465    )]
3466    #[test_case(
3467        [OptionCode::Router, OptionCode::SubnetMask].into(),
3468        [OptionCode::SubnetMask, OptionCode::Router].into();
3469        "SubnetMask should be moved to before Router"
3470    )]
3471    #[test_case(
3472        [OptionCode::Router, OptionCode::DomainNameServer, OptionCode::SubnetMask].into(),
3473        [OptionCode::SubnetMask, OptionCode::Router, OptionCode::DomainNameServer].into();
3474        "When SubnetMask is moved, Router should maintain its relative position"
3475    )]
3476    fn enforce_subnet_option_order(
3477        req_order: AtLeast<1, AtMostBytes<{ dhcp_protocol::U8_MAX_AS_USIZE }, Vec<OptionCode>>>,
3478        expected_order: AtLeast<
3479            1,
3480            AtMostBytes<{ dhcp_protocol::U8_MAX_AS_USIZE }, Vec<OptionCode>>,
3481        >,
3482    ) {
3483        // According to spec, subnet mask must be provided before the Router.
3484        // This test creates various PRLs and expects the server to move the
3485        // subnet mask when necessary.
3486        let mut server = new_test_minimal_server();
3487        let inform = new_client_message_with_options([
3488            DhcpOption::DhcpMessageType(MessageType::DHCPINFORM),
3489            DhcpOption::ParameterRequestList(req_order),
3490        ]);
3491
3492        let server_action = server.dispatch(inform);
3493        let ack = assert_matches::assert_matches!(
3494            server_action, Ok(ServerAction::SendResponse(ack,_)) => ack
3495        );
3496        let ack_order: Vec<_> = ack.options.iter().map(|option| option.code()).collect();
3497        // First two options are always MessageType and ServerIdentifier.
3498        // Both of which don't correspond with the ParameterRequestList.
3499        let expected_order: Vec<_> = [OptionCode::DhcpMessageType, OptionCode::ServerIdentifier]
3500            .into_iter()
3501            .chain(expected_order)
3502            .collect();
3503        assert_eq!(ack_order, expected_order)
3504    }
3505
3506    #[test]
3507    fn dispatch_with_decline_for_allocated_addr_returns_ok() {
3508        let (mut server, time_source) = new_test_minimal_server_with_time_source();
3509        let mut decline = new_test_decline(&server);
3510
3511        let declined_ip = random_ipv4_generator();
3512        let client_id = ClientIdentifier::from(&decline);
3513
3514        decline.options.push(DhcpOption::RequestedIpAddress(declined_ip));
3515
3516        assert!(server.pool.allocated.insert(declined_ip));
3517        assert!(server.pool.universe.insert(declined_ip));
3518
3519        assert_matches::assert_matches!(
3520            server.records.insert(
3521                client_id.clone(),
3522                LeaseRecord::new(Some(declined_ip), Vec::new(), time_source.now(), std::u32::MAX)
3523                    .expect("failed to create lease record"),
3524            ),
3525            None
3526        );
3527
3528        assert_eq!(server.dispatch(decline), Ok(ServerAction::AddressDecline(declined_ip)));
3529        assert!(!server.pool.addr_is_available(declined_ip), "addr still marked available");
3530        assert!(server.pool.addr_is_allocated(declined_ip), "addr not marked allocated");
3531        assert!(!server.records.contains_key(&client_id), "client record incorrectly retained");
3532        assert_matches::assert_matches!(
3533            server.store.expect("missing store").actions().as_slice(),
3534            [DataStoreAction::Delete { client_id: id }] if *id == client_id
3535        );
3536    }
3537
3538    #[test]
3539    fn dispatch_with_decline_for_available_addr_returns_ok() {
3540        let (mut server, time_source) = new_test_minimal_server_with_time_source();
3541        let mut decline = new_test_decline(&server);
3542
3543        let declined_ip = random_ipv4_generator();
3544        let client_id = ClientIdentifier::from(&decline);
3545
3546        decline.options.push(DhcpOption::RequestedIpAddress(declined_ip));
3547        assert_matches::assert_matches!(
3548            server.records.insert(
3549                client_id.clone(),
3550                LeaseRecord::new(Some(declined_ip), Vec::new(), time_source.now(), std::u32::MAX)
3551                    .expect("failed to create lease record"),
3552            ),
3553            None
3554        );
3555        assert!(server.pool.universe.insert(declined_ip));
3556
3557        assert_eq!(server.dispatch(decline), Ok(ServerAction::AddressDecline(declined_ip)));
3558        assert!(!server.pool.addr_is_available(declined_ip), "addr still marked available");
3559        assert!(server.pool.addr_is_allocated(declined_ip), "addr not marked allocated");
3560        assert!(!server.records.contains_key(&client_id), "client record incorrectly retained");
3561        assert_matches::assert_matches!(
3562            server.store.expect("missing store").actions().as_slice(),
3563            [DataStoreAction::Delete { client_id: id }] if *id == client_id
3564        );
3565    }
3566
3567    #[test]
3568    fn dispatch_with_decline_for_mismatched_addr_returns_err() {
3569        let (mut server, time_source) = new_test_minimal_server_with_time_source();
3570        let mut decline = new_test_decline(&server);
3571
3572        let declined_ip = random_ipv4_generator();
3573        let client_id = ClientIdentifier::from(&decline);
3574
3575        decline.options.push(DhcpOption::RequestedIpAddress(declined_ip));
3576
3577        let client_ip_according_to_server = random_ipv4_generator();
3578        assert!(server.pool.allocated.insert(client_ip_according_to_server));
3579        assert!(server.pool.universe.insert(declined_ip));
3580
3581        // Server contains client bindings which reflect a different address
3582        // than the one being declined.
3583        assert_matches::assert_matches!(
3584            server.records.insert(
3585                client_id.clone(),
3586                LeaseRecord::new(
3587                    Some(client_ip_according_to_server),
3588                    Vec::new(),
3589                    time_source.now(),
3590                    std::u32::MAX,
3591                )
3592                .expect("failed to create lease record"),
3593            ),
3594            None
3595        );
3596
3597        assert_eq!(
3598            server.dispatch(decline),
3599            Err(ServerError::DeclineIpMismatch {
3600                declined: Some(declined_ip),
3601                client: Some(client_ip_according_to_server)
3602            })
3603        );
3604        assert!(server.pool.addr_is_available(declined_ip), "addr not marked available");
3605        assert!(!server.pool.addr_is_allocated(declined_ip), "addr marked allocated");
3606        assert!(server.records.contains_key(&client_id), "client record deleted from records");
3607    }
3608
3609    #[test]
3610    fn dispatch_with_decline_for_expired_lease_returns_ok() {
3611        let (mut server, time_source) = new_test_minimal_server_with_time_source();
3612        let mut decline = new_test_decline(&server);
3613
3614        let declined_ip = random_ipv4_generator();
3615        let client_id = ClientIdentifier::from(&decline);
3616
3617        decline.options.push(DhcpOption::RequestedIpAddress(declined_ip));
3618
3619        assert!(server.pool.universe.insert(declined_ip));
3620
3621        assert_matches::assert_matches!(
3622            server.records.insert(
3623                client_id.clone(),
3624                LeaseRecord::new(Some(declined_ip), Vec::new(), time_source.now(), std::u32::MIN)
3625                    .expect("failed to create lease record"),
3626            ),
3627            None
3628        );
3629
3630        assert_eq!(server.dispatch(decline), Ok(ServerAction::AddressDecline(declined_ip)));
3631        assert!(!server.pool.addr_is_available(declined_ip), "addr still marked available");
3632        assert!(server.pool.addr_is_allocated(declined_ip), "addr not marked allocated");
3633        assert!(!server.records.contains_key(&client_id), "client record incorrectly retained");
3634        assert_matches::assert_matches!(
3635            server.store.expect("failed to create ").actions().as_slice(),
3636            [DataStoreAction::Delete { client_id: id }] if *id == client_id
3637        );
3638    }
3639
3640    #[test]
3641    fn dispatch_with_decline_for_unknown_client_returns_err() {
3642        let mut server = new_test_minimal_server();
3643        let mut decline = new_test_decline(&server);
3644
3645        let declined_ip = random_ipv4_generator();
3646        let client_id = ClientIdentifier::from(&decline);
3647
3648        decline.options.push(DhcpOption::RequestedIpAddress(declined_ip));
3649
3650        assert!(server.pool.universe.insert(declined_ip));
3651
3652        assert_eq!(
3653            server.dispatch(decline),
3654            Err(ServerError::DeclineFromUnrecognizedClient(client_id))
3655        );
3656        assert!(server.pool.addr_is_available(declined_ip), "addr not marked available");
3657        assert!(!server.pool.addr_is_allocated(declined_ip), "addr marked allocated");
3658    }
3659
3660    #[test]
3661    fn dispatch_with_decline_for_incorrect_server_returns_err() {
3662        let (mut server, time_source) = new_test_minimal_server_with_time_source();
3663        server.params.server_ips = vec![random_ipv4_generator()];
3664
3665        let mut decline = new_client_message(MessageType::DHCPDECLINE);
3666        let server_id = random_ipv4_generator();
3667        decline.options.push(DhcpOption::ServerIdentifier(server_id));
3668
3669        let declined_ip = random_ipv4_generator();
3670        let client_id = ClientIdentifier::from(&decline);
3671
3672        decline.options.push(DhcpOption::RequestedIpAddress(declined_ip));
3673
3674        assert!(server.pool.allocated.insert(declined_ip));
3675        assert_matches::assert_matches!(
3676            server.records.insert(
3677                client_id.clone(),
3678                LeaseRecord::new(Some(declined_ip), Vec::new(), time_source.now(), std::u32::MAX)
3679                    .expect("failed to create lease record"),
3680            ),
3681            None
3682        );
3683
3684        assert_eq!(server.dispatch(decline), Err(ServerError::IncorrectDHCPServer(server_id)));
3685        assert!(!server.pool.addr_is_available(declined_ip), "addr marked available");
3686        assert!(server.pool.addr_is_allocated(declined_ip), "addr not marked allocated");
3687        assert!(server.records.contains_key(&client_id), "client record not retained");
3688    }
3689
3690    #[test]
3691    fn dispatch_with_decline_without_requested_addr_returns_err() {
3692        let mut server = new_test_minimal_server();
3693        let decline = new_test_decline(&server);
3694
3695        assert_eq!(server.dispatch(decline), Err(ServerError::NoRequestedAddrForDecline));
3696    }
3697
3698    #[test]
3699    fn client_requested_lease_time() {
3700        let mut disc = new_test_discover();
3701        let client_id = ClientIdentifier::from(&disc);
3702
3703        let client_requested_time: u32 = 20;
3704
3705        disc.options.push(DhcpOption::IpAddressLeaseTime(client_requested_time));
3706
3707        let mut server = new_test_minimal_server();
3708        assert!(server.pool.universe.insert(random_ipv4_generator()));
3709
3710        let response = server.dispatch(disc).unwrap();
3711        assert_eq!(
3712            extract_message(response)
3713                .options
3714                .iter()
3715                .filter_map(|opt| {
3716                    if let DhcpOption::IpAddressLeaseTime(v) = opt { Some(*v) } else { None }
3717                })
3718                .next()
3719                .unwrap(),
3720            client_requested_time as u32
3721        );
3722
3723        assert_eq!(
3724            server.records.get(&client_id).unwrap().lease_length_seconds,
3725            client_requested_time,
3726        );
3727        assert_matches::assert_matches!(
3728            server.store.expect("missing store").actions().as_slice(),
3729            [DataStoreAction::StoreClientRecord {client_id: id, ..}] if *id == client_id
3730        );
3731    }
3732
3733    #[test]
3734    fn client_requested_lease_time_greater_than_max() {
3735        let mut disc = new_test_discover();
3736        let client_id = ClientIdentifier::from(&disc);
3737
3738        let client_requested_time: u32 = 20;
3739        let server_max_lease_time: u32 = 10;
3740
3741        disc.options.push(DhcpOption::IpAddressLeaseTime(client_requested_time));
3742
3743        let mut server = new_test_minimal_server();
3744        assert!(server.pool.universe.insert(std_ip_v4!("195.168.1.45")));
3745        let ll = LeaseLength { default_seconds: 60 * 60 * 24, max_seconds: server_max_lease_time };
3746        server.params.lease_length = ll;
3747
3748        let response = server.dispatch(disc).unwrap();
3749        assert_eq!(
3750            extract_message(response)
3751                .options
3752                .iter()
3753                .filter_map(|opt| {
3754                    if let DhcpOption::IpAddressLeaseTime(v) = opt { Some(*v) } else { None }
3755                })
3756                .next()
3757                .unwrap(),
3758            server_max_lease_time
3759        );
3760
3761        assert_eq!(
3762            server.records.get(&client_id).unwrap().lease_length_seconds,
3763            server_max_lease_time,
3764        );
3765        assert_matches::assert_matches!(
3766            server.store.expect("missing store").actions().as_slice(),
3767            [DataStoreAction::StoreClientRecord {client_id: id, ..}] if *id == client_id
3768        );
3769    }
3770
3771    #[test]
3772    fn server_dispatcher_get_option_with_unset_option_returns_not_found() {
3773        let server = new_test_minimal_server();
3774        let result = server.dispatch_get_option(fidl_fuchsia_net_dhcp::OptionCode::SubnetMask);
3775        assert_eq!(result, Err(Status::NOT_FOUND));
3776    }
3777
3778    #[test]
3779    fn server_dispatcher_get_option_with_set_option_returns_option() {
3780        let mut server = new_test_minimal_server();
3781        let option = || fidl_fuchsia_net_dhcp::Option_::SubnetMask(fidl_ip_v4!("255.255.255.0"));
3782        assert_matches::assert_matches!(
3783            server.options_repo.insert(
3784                OptionCode::SubnetMask,
3785                DhcpOption::try_from_fidl(option())
3786                    .expect("failed to convert dhcp option from fidl")
3787            ),
3788            None
3789        );
3790        let result = server
3791            .dispatch_get_option(fidl_fuchsia_net_dhcp::OptionCode::SubnetMask)
3792            .expect("failed to get dhcp option");
3793        assert_eq!(result, option());
3794    }
3795
3796    #[test]
3797    fn server_dispatcher_get_parameter_returns_parameter() {
3798        let mut server = new_test_minimal_server();
3799        let addr = random_ipv4_generator();
3800        server.params.server_ips = vec![addr];
3801        let expected = fidl_fuchsia_net_dhcp::Parameter::IpAddrs(vec![addr.into_fidl()]);
3802        let result = server
3803            .dispatch_get_parameter(fidl_fuchsia_net_dhcp::ParameterName::IpAddrs)
3804            .expect("failed to get dhcp option");
3805        assert_eq!(result, expected);
3806    }
3807
3808    #[test]
3809    fn server_dispatcher_set_option_returns_unit() {
3810        let mut server = new_test_minimal_server();
3811        let option = || fidl_fuchsia_net_dhcp::Option_::SubnetMask(fidl_ip_v4!("255.255.255.0"));
3812        let () = server.dispatch_set_option(option()).expect("failed to set dhcp option");
3813        let stored_option: DhcpOption =
3814            DhcpOption::try_from_fidl(option()).expect("failed to convert dhcp option from fidl");
3815        let code = stored_option.code();
3816        let result = server.options_repo.get(&code);
3817        assert_eq!(result, Some(&stored_option));
3818        assert_matches::assert_matches!(
3819            server.store.expect("missing store").actions().as_slice(),
3820            [
3821                DataStoreAction::StoreOptions { opts },
3822            ] if opts.contains(&stored_option)
3823        );
3824    }
3825
3826    #[test]
3827    fn server_dispatcher_set_option_saves_to_stash() {
3828        let prefix_length = DEFAULT_PREFIX_LENGTH;
3829        let fidl_mask =
3830            fidl_fuchsia_net_dhcp::Option_::SubnetMask(prefix_length.get_mask().into_ext());
3831        let params = default_server_params().expect("failed to get default serve parameters");
3832        let mut server: Server = super::Server {
3833            records: HashMap::new(),
3834            pool: AddressPool::new(params.managed_addrs.pool_range()),
3835            params,
3836            store: Some(ActionRecordingDataStore::new()),
3837            options_repo: HashMap::new(),
3838            time_source: TestSystemTime::with_current_time(),
3839        };
3840        let () = server.dispatch_set_option(fidl_mask).expect("failed to set dhcp option");
3841        assert_matches::assert_matches!(
3842            server.store.expect("missing store").actions().as_slice(),
3843            [
3844                DataStoreAction::StoreOptions { opts },
3845            ] if *opts == vec![DhcpOption::SubnetMask(prefix_length)]
3846        );
3847    }
3848
3849    #[test]
3850    fn server_dispatcher_set_parameter_saves_to_stash() {
3851        let (default, max) = (42, 100);
3852        let fidl_lease =
3853            fidl_fuchsia_net_dhcp::Parameter::Lease(fidl_fuchsia_net_dhcp::LeaseLength {
3854                default: Some(default),
3855                max: Some(max),
3856                ..Default::default()
3857            });
3858        let mut server = new_test_minimal_server();
3859        let () = server.dispatch_set_parameter(fidl_lease).expect("failed to set parameter");
3860        assert_matches::assert_matches!(
3861            server.store.expect("missing store").actions().next(),
3862            Some(DataStoreAction::StoreParameters {
3863                params: ServerParameters {
3864                    lease_length: LeaseLength { default_seconds: 42, max_seconds: 100 },
3865                    ..
3866                },
3867            })
3868        );
3869    }
3870
3871    #[test]
3872    fn server_dispatcher_set_parameter() {
3873        let mut server = new_test_minimal_server();
3874        let addr = random_ipv4_generator();
3875        let valid_parameter = || fidl_fuchsia_net_dhcp::Parameter::IpAddrs(vec![addr.into_fidl()]);
3876        let empty_lease_length =
3877            fidl_fuchsia_net_dhcp::Parameter::Lease(fidl_fuchsia_net_dhcp::LeaseLength {
3878                default: None,
3879                max: None,
3880                ..Default::default()
3881            });
3882        let bad_prefix_length =
3883            fidl_fuchsia_net_dhcp::Parameter::AddressPool(fidl_fuchsia_net_dhcp::AddressPool {
3884                prefix_length: Some(33),
3885                range_start: Some(fidl_ip_v4!("192.168.0.2")),
3886                range_stop: Some(fidl_ip_v4!("192.168.0.254")),
3887                ..Default::default()
3888            });
3889        let mac = random_mac_generator().bytes();
3890        let duplicated_static_assignment =
3891            fidl_fuchsia_net_dhcp::Parameter::StaticallyAssignedAddrs(vec![
3892                fidl_fuchsia_net_dhcp::StaticAssignment {
3893                    host: Some(fidl_fuchsia_net::MacAddress { octets: mac.clone() }),
3894                    assigned_addr: Some(random_ipv4_generator().into_fidl()),
3895                    ..Default::default()
3896                },
3897                fidl_fuchsia_net_dhcp::StaticAssignment {
3898                    host: Some(fidl_fuchsia_net::MacAddress { octets: mac.clone() }),
3899                    assigned_addr: Some(random_ipv4_generator().into_fidl()),
3900                    ..Default::default()
3901                },
3902            ]);
3903
3904        let () =
3905            server.dispatch_set_parameter(valid_parameter()).expect("failed to set dhcp parameter");
3906        assert_eq!(
3907            server
3908                .dispatch_get_parameter(fidl_fuchsia_net_dhcp::ParameterName::IpAddrs)
3909                .expect("failed to get dhcp parameter"),
3910            valid_parameter()
3911        );
3912        assert_eq!(
3913            server.dispatch_set_parameter(empty_lease_length),
3914            Err(zx::Status::INVALID_ARGS)
3915        );
3916        assert_eq!(server.dispatch_set_parameter(bad_prefix_length), Err(zx::Status::INVALID_ARGS));
3917        assert_eq!(
3918            server.dispatch_set_parameter(duplicated_static_assignment),
3919            Err(zx::Status::INVALID_ARGS)
3920        );
3921        assert_matches::assert_matches!(
3922            server.store.expect("missing store").actions().as_slice(),
3923            [DataStoreAction::StoreParameters { params }] if *params == server.params
3924        );
3925    }
3926
3927    #[test]
3928    fn server_dispatcher_list_options_returns_set_options() {
3929        let mut server = new_test_minimal_server();
3930        let mask = || {
3931            fidl_fuchsia_net_dhcp::Option_::SubnetMask(DEFAULT_PREFIX_LENGTH.get_mask().into_ext())
3932        };
3933        let hostname = || fidl_fuchsia_net_dhcp::Option_::HostName(String::from("testhostname"));
3934        assert_matches::assert_matches!(
3935            server.options_repo.insert(
3936                OptionCode::SubnetMask,
3937                DhcpOption::try_from_fidl(mask()).expect("failed to convert dhcp option from fidl")
3938            ),
3939            None
3940        );
3941        assert_matches::assert_matches!(
3942            server.options_repo.insert(
3943                OptionCode::HostName,
3944                DhcpOption::try_from_fidl(hostname())
3945                    .expect("failed to convert dhcp option from fidl")
3946            ),
3947            None
3948        );
3949        let result = server.dispatch_list_options().expect("failed to list dhcp options");
3950        assert_eq!(result.len(), server.options_repo.len());
3951        assert!(result.contains(&mask()));
3952        assert!(result.contains(&hostname()));
3953    }
3954
3955    #[test]
3956    fn server_dispatcher_list_parameters_returns_parameters() {
3957        let mut server = new_test_minimal_server();
3958        let addr = random_ipv4_generator();
3959        server.params.server_ips = vec![addr];
3960        let expected = fidl_fuchsia_net_dhcp::Parameter::IpAddrs(vec![addr.into_fidl()]);
3961        let result = server.dispatch_list_parameters().expect("failed to list dhcp options");
3962        let params_fields_ct = 7;
3963        assert_eq!(result.len(), params_fields_ct);
3964        assert!(result.contains(&expected));
3965    }
3966
3967    #[test]
3968    fn server_dispatcher_reset_options() {
3969        let mut server = new_test_minimal_server();
3970        let empty_map = HashMap::new();
3971        assert_ne!(empty_map, server.options_repo);
3972        let () = server.dispatch_reset_options().expect("failed to reset options");
3973        assert_eq!(empty_map, server.options_repo);
3974        let stored_opts = server
3975            .store
3976            .as_mut()
3977            .expect("missing store")
3978            .load_options()
3979            .expect("failed to load options");
3980        assert_eq!(empty_map, stored_opts);
3981        assert_matches::assert_matches!(
3982            server.store.expect("missing store").actions().as_slice(),
3983            [
3984                DataStoreAction::StoreOptions { opts },
3985                DataStoreAction::LoadOptions
3986            ] if opts.is_empty()
3987        );
3988    }
3989
3990    #[test]
3991    fn server_dispatcher_reset_parameters() {
3992        let mut server = new_test_minimal_server();
3993        let default_params = test_server_params(
3994            vec![std_ip_v4!("192.168.0.1")],
3995            LeaseLength { default_seconds: 86400, max_seconds: 86400 },
3996        )
3997        .expect("failed to get test server parameters");
3998        assert_ne!(default_params, server.params);
3999        let () =
4000            server.dispatch_reset_parameters(&default_params).expect("failed to reset parameters");
4001        assert_eq!(default_params, server.params);
4002        assert_matches::assert_matches!(
4003            server.store.expect("missing store").actions().as_slice(),
4004            [DataStoreAction::StoreParameters { params }] if *params == default_params
4005        );
4006    }
4007
4008    #[test]
4009    fn server_dispatcher_clear_leases() {
4010        let mut server = new_test_minimal_server();
4011        server.params.managed_addrs.pool_range_stop = std_ip_v4!("192.168.0.4");
4012        server.pool = AddressPool::new(server.params.managed_addrs.pool_range());
4013        let client = std_ip_v4!("192.168.0.2");
4014        let () = server
4015            .pool
4016            .allocate_addr(client)
4017            .unwrap_or_else(|err| panic!("allocate_addr({}) failed: {:?}", client, err));
4018        let client_id = ClientIdentifier::from(random_mac_generator());
4019        server.records = [(
4020            client_id.clone(),
4021            LeaseRecord {
4022                current: Some(client),
4023                previous: None,
4024                options: Vec::new(),
4025                lease_start_epoch_seconds: 0,
4026                lease_length_seconds: 42,
4027            },
4028        )]
4029        .into();
4030        let () = server.dispatch_clear_leases().expect("dispatch_clear_leases() failed");
4031        let empty_map = HashMap::new();
4032        assert_eq!(empty_map, server.records);
4033        assert!(server.pool.addr_is_available(client));
4034        assert!(!server.pool.addr_is_allocated(client));
4035        let stored_leases = server
4036            .store
4037            .as_mut()
4038            .expect("missing store")
4039            .load_client_records()
4040            .expect("load_client_records() failed");
4041        assert_eq!(empty_map, stored_leases);
4042        assert_matches::assert_matches!(
4043            server.store.expect("missing store").actions().as_slice(),
4044            [
4045                DataStoreAction::Delete { client_id: id },
4046                DataStoreAction::LoadClientRecords
4047            ] if *id == client_id
4048        );
4049    }
4050
4051    #[test]
4052    fn server_dispatcher_validate_params() {
4053        let mut server = new_test_minimal_server();
4054        let () = server.pool.universe.clear();
4055        assert_eq!(server.try_validate_parameters(), Err(Status::INVALID_ARGS));
4056    }
4057
4058    #[test]
4059    fn set_address_pool_fails_if_leases_present() {
4060        let mut server = new_test_minimal_server();
4061        assert_matches::assert_matches!(
4062            server.records.insert(
4063                ClientIdentifier::from(MacAddr::new([1, 2, 3, 4, 5, 6])),
4064                LeaseRecord::default(),
4065            ),
4066            None
4067        );
4068        assert_eq!(
4069            server.dispatch_set_parameter(fidl_fuchsia_net_dhcp::Parameter::AddressPool(
4070                fidl_fuchsia_net_dhcp::AddressPool {
4071                    prefix_length: Some(24),
4072                    range_start: Some(fidl_ip_v4!("192.168.0.2")),
4073                    range_stop: Some(fidl_ip_v4!("192.168.0.254")),
4074                    ..Default::default()
4075                }
4076            )),
4077            Err(Status::BAD_STATE)
4078        );
4079    }
4080
4081    #[test]
4082    fn set_address_pool_updates_internal_pool() {
4083        let mut server = new_test_minimal_server();
4084        let () = server.pool.universe.clear();
4085        let () = server
4086            .dispatch_set_parameter(fidl_fuchsia_net_dhcp::Parameter::AddressPool(
4087                fidl_fuchsia_net_dhcp::AddressPool {
4088                    prefix_length: Some(24),
4089                    range_start: Some(fidl_ip_v4!("192.168.0.2")),
4090                    range_stop: Some(fidl_ip_v4!("192.168.0.5")),
4091                    ..Default::default()
4092                },
4093            ))
4094            .expect("failed to set parameter");
4095        assert_eq!(server.pool.available().count(), 3);
4096        assert_matches::assert_matches!(
4097            server.store.expect("missing store").actions().as_slice(),
4098            [DataStoreAction::StoreParameters { params }] if *params == server.params
4099        );
4100    }
4101
4102    #[test]
4103    fn recovery_from_expired_persistent_record() {
4104        let client_ip = net_declare::std::ip_v4!("192.168.0.1");
4105        let mut time_source = TestSystemTime::with_current_time();
4106        const LEASE_EXPIRATION_SECONDS: u32 = 60;
4107        // The previous server has stored a stale client record.
4108        let mut store = ActionRecordingDataStore::new();
4109        let client_id = ClientIdentifier::from(random_mac_generator());
4110        let client_record = LeaseRecord::new(
4111            Some(client_ip),
4112            Vec::new(),
4113            time_source.now(),
4114            LEASE_EXPIRATION_SECONDS,
4115        )
4116        .expect("failed to create lease record");
4117        let () = store.insert(&client_id, &client_record).expect("failed to insert client record");
4118        // The record should become expired now.
4119        let () = time_source.move_forward(Duration::from_secs(LEASE_EXPIRATION_SECONDS.into()));
4120
4121        // Only 192.168.0.1 is available.
4122        let params = ServerParameters {
4123            server_ips: Vec::new(),
4124            lease_length: LeaseLength {
4125                default_seconds: 60 * 60 * 24,
4126                max_seconds: 60 * 60 * 24 * 7,
4127            },
4128            managed_addrs: ManagedAddresses {
4129                mask: SubnetMask::new(prefix_length_v4!(24)),
4130                pool_range_start: client_ip,
4131                pool_range_stop: net_declare::std::ip_v4!("192.168.0.2"),
4132            },
4133            permitted_macs: PermittedMacs(Vec::new()),
4134            static_assignments: StaticAssignments(HashMap::new()),
4135            arp_probe: false,
4136            bound_device_names: Vec::new(),
4137        };
4138
4139        // The server should recover to a consistent state on the next start.
4140        let records: HashMap<_, _> =
4141            Some((client_id.clone(), client_record.clone())).into_iter().collect();
4142        let server: Server =
4143            Server::new_with_time_source(store, params, HashMap::new(), records, time_source)
4144                .expect("failed to create server");
4145        // Create a temporary because assert_matches! doesn't like turbo-fish type annotation.
4146        let contents: Vec<(&ClientIdentifier, &LeaseRecord)> = server.records.iter().collect();
4147        assert_matches::assert_matches!(
4148            contents.as_slice(),
4149            [(id, LeaseRecord {current: None, previous: Some(ip), ..})] if **id == client_id && *ip == client_ip
4150        );
4151        assert!(server.pool.allocated.is_empty());
4152
4153        assert_eq!(server.pool.available().collect::<Vec<_>>(), vec![client_ip]);
4154
4155        assert_matches::assert_matches!(
4156            server.store.expect("missing store").actions().as_slice(),
4157            [
4158                DataStoreAction::StoreClientRecord{ client_id: id1, record },
4159                DataStoreAction::StoreClientRecord{ client_id: id2, record: LeaseRecord {current: None, previous: Some(ip), ..} },
4160            ] if id1 == id2 && id2 == &client_id && record == &client_record && *ip == client_ip
4161        );
4162    }
4163
4164    #[test]
4165    fn test_validate_discover() {
4166        use std::string::ToString as _;
4167        let mut disc = new_test_discover();
4168        disc.op = OpCode::BOOTREPLY;
4169        assert_eq!(
4170            validate_discover(&disc),
4171            Err(ServerError::ClientMessageError(ProtocolError::InvalidField {
4172                field: String::from("op"),
4173                value: String::from("BOOTREPLY"),
4174                msg_type: MessageType::DHCPDISCOVER
4175            }))
4176        );
4177        disc = new_test_discover();
4178        disc.ciaddr = random_ipv4_generator();
4179        assert_eq!(
4180            validate_discover(&disc),
4181            Err(ServerError::ClientMessageError(ProtocolError::InvalidField {
4182                field: String::from("ciaddr"),
4183                value: disc.ciaddr.to_string(),
4184                msg_type: MessageType::DHCPDISCOVER
4185            }))
4186        );
4187        disc = new_test_discover();
4188        disc.yiaddr = random_ipv4_generator();
4189        assert_eq!(
4190            validate_discover(&disc),
4191            Err(ServerError::ClientMessageError(ProtocolError::InvalidField {
4192                field: String::from("yiaddr"),
4193                value: disc.yiaddr.to_string(),
4194                msg_type: MessageType::DHCPDISCOVER
4195            }))
4196        );
4197        disc = new_test_discover();
4198        disc.siaddr = random_ipv4_generator();
4199        assert_eq!(
4200            validate_discover(&disc),
4201            Err(ServerError::ClientMessageError(ProtocolError::InvalidField {
4202                field: String::from("siaddr"),
4203                value: disc.siaddr.to_string(),
4204                msg_type: MessageType::DHCPDISCOVER
4205            }))
4206        );
4207        disc = new_test_discover();
4208        let server = random_ipv4_generator();
4209        let () = disc.options.push(DhcpOption::ServerIdentifier(server));
4210        assert_eq!(
4211            validate_discover(&disc),
4212            Err(ServerError::ClientMessageError(ProtocolError::InvalidField {
4213                field: String::from("ServerIdentifier"),
4214                value: server.to_string(),
4215                msg_type: MessageType::DHCPDISCOVER
4216            }))
4217        );
4218        disc = new_test_discover();
4219        assert_eq!(validate_discover(&disc), Ok(()));
4220    }
4221
4222    #[test]
4223    fn build_offer_with_custom_t1_t2() {
4224        let mut server = new_test_minimal_server();
4225        let initial_disc = new_test_discover();
4226        let initial_offer_ip = random_ipv4_generator();
4227        assert!(server.pool.universe.insert(initial_offer_ip));
4228        let offer =
4229            server.build_offer(initial_disc, initial_offer_ip).expect("failed to build offer");
4230        let v = offer.options.iter().find_map(|v| match v {
4231            DhcpOption::RenewalTimeValue(v) => Some(*v),
4232            _ => None,
4233        });
4234        assert_eq!(
4235            v,
4236            Some(server.params.lease_length.default_seconds / 2),
4237            "offer options did not contain expected renewal time: {:?}",
4238            offer.options
4239        );
4240        let v = offer.options.iter().find_map(|v| match v {
4241            DhcpOption::RebindingTimeValue(v) => Some(*v),
4242            _ => None,
4243        });
4244        assert_eq!(
4245            v,
4246            Some((server.params.lease_length.default_seconds * 3) / 4),
4247            "offer options did not contain expected rebinding time: {:?}",
4248            offer.options
4249        );
4250        let t1 = rand::random::<u32>();
4251        assert_matches::assert_matches!(
4252            server
4253                .options_repo
4254                .insert(OptionCode::RenewalTimeValue, DhcpOption::RenewalTimeValue(t1)),
4255            None
4256        );
4257        let t2 = rand::random::<u32>();
4258        assert_matches::assert_matches!(
4259            server
4260                .options_repo
4261                .insert(OptionCode::RebindingTimeValue, DhcpOption::RebindingTimeValue(t2)),
4262            None
4263        );
4264        let disc = new_test_discover();
4265        let offer_ip = random_ipv4_generator();
4266        assert!(server.pool.universe.insert(offer_ip));
4267        let offer = server.build_offer(disc, offer_ip).expect("failed to build offer");
4268        let v = offer.options.iter().find_map(|v| match v {
4269            DhcpOption::RenewalTimeValue(v) => Some(*v),
4270            _ => None,
4271        });
4272        assert_eq!(
4273            v,
4274            Some(t1),
4275            "offer options did not contain expected renewal time: {:?}",
4276            offer.options
4277        );
4278        let v = offer.options.iter().find_map(|v| match v {
4279            DhcpOption::RebindingTimeValue(v) => Some(*v),
4280            _ => None,
4281        });
4282        assert_eq!(
4283            v,
4284            Some(t2),
4285            "offer options did not contain expected rebinding time: {:?}",
4286            offer.options
4287        );
4288    }
4289
4290    #[test_case(None; "no requested lease length")]
4291    #[test_case(Some(150); "requested lease length under maximum")]
4292    #[test_case(Some(1000); "requested lease length above maximum")]
4293    fn standalone_build_offer(requested_lease_length: Option<u32>) {
4294        let discover = new_test_discover_with_options(
4295            requested_lease_length.map(DhcpOption::IpAddressLeaseTime).into_iter(),
4296        );
4297        let offered_ip = random_ipv4_generator();
4298        let subnet_mask = DEFAULT_PREFIX_LENGTH;
4299        let server_ip = random_ipv4_generator();
4300        const DEFAULT_LEASE_LENGTH_SECONDS: u32 = 100;
4301        const MAX_LEASE_LENGTH_SECONDS: u32 = 200;
4302        let lease_length_config = LeaseLength {
4303            default_seconds: DEFAULT_LEASE_LENGTH_SECONDS,
4304            max_seconds: MAX_LEASE_LENGTH_SECONDS,
4305        };
4306        let expected_lease_length = match requested_lease_length {
4307            None => DEFAULT_LEASE_LENGTH_SECONDS,
4308            Some(x) => x.min(MAX_LEASE_LENGTH_SECONDS),
4309        };
4310
4311        let chaddr = discover.chaddr;
4312        let xid = discover.xid;
4313        let bdcast_flag = discover.bdcast_flag;
4314
4315        assert_eq!(
4316            build_offer(
4317                discover,
4318                OfferOptions {
4319                    offered_ip,
4320                    server_ip,
4321                    lease_length_config,
4322                    renewal_time_value: None,
4323                    rebinding_time_value: None,
4324                    subnet_mask,
4325                },
4326                &options_repo([]),
4327            )
4328            .expect("build_offer should succeed"),
4329            Message {
4330                op: OpCode::BOOTREPLY,
4331                xid,
4332                secs: 0,
4333                bdcast_flag,
4334                ciaddr: Ipv4Addr::UNSPECIFIED,
4335                yiaddr: offered_ip,
4336                siaddr: Ipv4Addr::UNSPECIFIED,
4337                giaddr: Ipv4Addr::UNSPECIFIED,
4338                chaddr,
4339                sname: String::new(),
4340                file: String::new(),
4341                options: vec![
4342                    DhcpOption::DhcpMessageType(MessageType::DHCPOFFER),
4343                    DhcpOption::ServerIdentifier(server_ip),
4344                    DhcpOption::IpAddressLeaseTime(expected_lease_length),
4345                    DhcpOption::RenewalTimeValue(expected_lease_length / 2),
4346                    DhcpOption::RebindingTimeValue(expected_lease_length * 3 / 4),
4347                    DhcpOption::SubnetMask(subnet_mask),
4348                ],
4349            }
4350        );
4351    }
4352}