dhcpv4/
server.rs

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