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