umsh_ulcp/
ip.rs

1//! IP connectivity codecs (`PROP_IPV4_*`, `PROP_IPV6_*`, `PROP_IP_*`).
2//!
3//! The structures of `docs/protocol/src/ulcp-ip.md`: family readiness,
4//! the per-family configuration, the addresses in effect, and the
5//! resolvers.
6//!
7//! The usability predicates here are the boundary the whole subsystem is
8//! defined on. [`FamilyState::Ready`] means a usable address, a static
9//! configuration is validated against the same rule, and the two are the
10//! same function so that they cannot drift: a device that accepted a
11//! static address it would then refuse to call ready would be
12//! unreachable and say it was fine.
13
14/// Encoded length of a static [`V4Config`]; the other methods encode as
15/// one octet.
16pub const V4_CONFIG_STATIC_LEN: usize = 10;
17/// Encoded length of a static [`V6Config`].
18pub const V6_CONFIG_STATIC_LEN: usize = 34;
19/// Encoded length of a [`V4Address`]. The property is empty when the
20/// family is not ready.
21pub const V4_ADDRESS_LEN: usize = 9;
22/// Encoded length of a [`V6Item::Address`].
23pub const V6_ADDRESS_ITEM_LEN: usize = 18;
24/// Encoded length of a [`V6Item::Router`].
25pub const V6_ROUTER_ITEM_LEN: usize = 17;
26
27/// Decode or well-formedness failure.
28#[derive(Clone, Copy, Debug, PartialEq, Eq)]
29pub enum IpError {
30    /// The input ended before the structure was complete.
31    Truncated,
32    /// An unknown enumeration code, or a field of impossible length.
33    Malformed,
34    /// The value parses but is not one a device may be configured with:
35    /// a prefix out of range, an address that is not usable, a gateway
36    /// or resolver that is not a unicast host. The
37    /// `STATUS_INVALID_ARGUMENT` set.
38    NotWellFormed,
39    /// The output buffer cannot hold the encoded value.
40    BufferTooSmall,
41}
42
43/// Readiness of one address family, shared by `PROP_IPV4_STATE` and
44/// `PROP_IPV6_STATE`.
45#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
46pub enum FamilyState {
47    /// `IP_DISABLED`: the family is configured off.
48    Disabled = 0,
49    /// `IP_NO_LINK`: the link is down, so there is nothing to address.
50    #[default]
51    NoLink = 1,
52    /// `IP_WAITING`: the link is up and the family has no usable
53    /// address yet.
54    Waiting = 2,
55    /// `IP_READY`: the family holds a usable address. An address, not a
56    /// route: a device on an isolated network with no gateway is ready.
57    Ready = 3,
58    /// `IP_CONFLICT`: the configured static address is held by
59    /// something else, which is a fault in the configuration this
60    /// protocol wrote.
61    Conflict = 4,
62}
63
64impl FamilyState {
65    pub const fn code(self) -> u8 {
66        self as u8
67    }
68
69    pub const fn from_code(code: u8) -> Option<Self> {
70        match code {
71            0 => Some(Self::Disabled),
72            1 => Some(Self::NoLink),
73            2 => Some(Self::Waiting),
74            3 => Some(Self::Ready),
75            4 => Some(Self::Conflict),
76            _ => None,
77        }
78    }
79
80    /// Whether the device can be reached on this family right now.
81    pub const fn is_ready(self) -> bool {
82        matches!(self, Self::Ready)
83    }
84}
85
86/// How a family gets its address.
87#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
88pub enum Method {
89    /// `IP_METHOD_DISABLED`: the family is not used on the link.
90    Disabled = 0,
91    /// `IP_METHOD_AUTO`: DHCP for IPv4; router advertisements, and
92    /// DHCPv6 where the router asks for it, for IPv6.
93    #[default]
94    Auto = 1,
95    /// `IP_METHOD_STATIC`: the address, prefix, and gateway that
96    /// follow.
97    Static = 2,
98}
99
100impl Method {
101    pub const fn code(self) -> u8 {
102        self as u8
103    }
104
105    pub const fn from_code(code: u8) -> Option<Self> {
106        match code {
107            0 => Some(Self::Disabled),
108            1 => Some(Self::Auto),
109            2 => Some(Self::Static),
110            _ => None,
111        }
112    }
113}
114
115/// Whether an IPv4 address is a usable host address: unicast, and not
116/// link-local.
117///
118/// This is the definition `IP_READY` is stated in terms of. A
119/// self-assigned `169.254` address reaches only the link, exactly as
120/// `fe80::` does, and a device that fell back to one is a device whose
121/// DHCP failed.
122pub const fn ipv4_is_usable_host(address: &[u8; 4]) -> bool {
123    let [a, b, _, _] = *address;
124    if a == 0 && b == 0 && address[2] == 0 && address[3] == 0 {
125        return false; // unspecified
126    }
127    if a == 127 {
128        return false; // loopback
129    }
130    if a == 169 && b == 254 {
131        return false; // link-local
132    }
133    if a >= 224 && a <= 239 {
134        return false; // multicast
135    }
136    if a == 255 && b == 255 && address[2] == 255 && address[3] == 255 {
137        return false; // limited broadcast
138    }
139    true
140}
141
142/// Whether an IPv6 address is a usable host address: unicast, and not
143/// link-local.
144pub fn ipv6_is_usable_host(address: &[u8; 16]) -> bool {
145    if address.iter().all(|&b| b == 0) {
146        return false; // unspecified
147    }
148    if address[..15].iter().all(|&b| b == 0) && address[15] == 1 {
149        return false; // loopback
150    }
151    if address[0] == 0xFF {
152        return false; // multicast
153    }
154    if address[0] == 0xFE && (address[1] & 0xC0) == 0x80 {
155        return false; // link-local, fe80::/10
156    }
157    true
158}
159
160/// Whether an IPv6 address is link-local (`fe80::/10`).
161pub const fn ipv6_is_link_local(address: &[u8; 16]) -> bool {
162    address[0] == 0xFE && (address[1] & 0xC0) == 0x80
163}
164
165/// Whether an IPv4 gateway is acceptable: all-zero for "no default
166/// route", or a usable host address.
167pub const fn ipv4_gateway_is_valid(gateway: &[u8; 4]) -> bool {
168    if gateway[0] == 0 && gateway[1] == 0 && gateway[2] == 0 && gateway[3] == 0 {
169        return true;
170    }
171    ipv4_is_usable_host(gateway)
172}
173
174/// Whether an IPv6 gateway is acceptable.
175///
176/// Relaxes the link-local exclusion, unlike the IPv4 form: a router
177/// names itself by its link-local address, and a static IPv6 gateway is
178/// usually exactly that.
179pub fn ipv6_gateway_is_valid(gateway: &[u8; 16]) -> bool {
180    if gateway.iter().all(|&b| b == 0) {
181        return true;
182    }
183    ipv6_is_usable_host(gateway) || ipv6_is_link_local(gateway)
184}
185
186/// Whether one `PROP_IP_DNS` item is acceptable.
187///
188/// Four or sixteen octets, a unicast host either way. An IPv6 resolver
189/// may be link-local, as one advertised by a home router commonly is,
190/// and an IPv4 one may not.
191pub fn resolver_is_valid(item: &[u8]) -> bool {
192    match item.len() {
193        4 => {
194            let mut address = [0u8; 4];
195            address.copy_from_slice(item);
196            ipv4_is_usable_host(&address)
197        }
198        16 => {
199            let mut address = [0u8; 16];
200            address.copy_from_slice(item);
201            ipv6_is_usable_host(&address) || ipv6_is_link_local(&address)
202        }
203        _ => false,
204    }
205}
206
207/// `PROP_IPV4_CONFIG`.
208#[derive(Clone, Copy, Debug, PartialEq, Eq)]
209pub struct V4Config {
210    pub method: Method,
211    /// Meaningful only under [`Method::Static`].
212    pub address: [u8; 4],
213    /// Meaningful only under [`Method::Static`]. At most 32.
214    pub prefix: u8,
215    /// Meaningful only under [`Method::Static`]. All-zero for no
216    /// default route.
217    pub gateway: [u8; 4],
218}
219
220impl Default for V4Config {
221    fn default() -> Self {
222        Self {
223            method: Method::Auto,
224            address: [0; 4],
225            prefix: 0,
226            gateway: [0; 4],
227        }
228    }
229}
230
231impl V4Config {
232    /// Whether this configuration is one a device may be given.
233    pub fn validate(&self) -> Result<(), IpError> {
234        if !matches!(self.method, Method::Static) {
235            return Ok(());
236        }
237        if self.prefix > 32 {
238            return Err(IpError::NotWellFormed);
239        }
240        if !ipv4_is_usable_host(&self.address) {
241            return Err(IpError::NotWellFormed);
242        }
243        if !ipv4_gateway_is_valid(&self.gateway) {
244            return Err(IpError::NotWellFormed);
245        }
246        Ok(())
247    }
248
249    pub fn encode(&self, out: &mut [u8]) -> Result<usize, IpError> {
250        let len = if matches!(self.method, Method::Static) {
251            V4_CONFIG_STATIC_LEN
252        } else {
253            1
254        };
255        if out.len() < len {
256            return Err(IpError::BufferTooSmall);
257        }
258        out[0] = self.method.code();
259        if len > 1 {
260            out[1..5].copy_from_slice(&self.address);
261            out[5] = self.prefix;
262            out[6..10].copy_from_slice(&self.gateway);
263        }
264        Ok(len)
265    }
266
267    pub fn decode(bytes: &[u8]) -> Result<Self, IpError> {
268        let method = Method::from_code(*bytes.first().ok_or(IpError::Truncated)?)
269            .ok_or(IpError::Malformed)?;
270        let mut config = Self {
271            method,
272            ..Self::default()
273        };
274        if matches!(method, Method::Static) {
275            if bytes.len() < V4_CONFIG_STATIC_LEN {
276                return Err(IpError::Truncated);
277            }
278            config.address.copy_from_slice(&bytes[1..5]);
279            config.prefix = bytes[5];
280            config.gateway.copy_from_slice(&bytes[6..10]);
281        }
282        Ok(config)
283    }
284}
285
286/// `PROP_IPV6_CONFIG`: as [`V4Config`] with 16-octet addresses.
287#[derive(Clone, Copy, Debug, PartialEq, Eq)]
288pub struct V6Config {
289    pub method: Method,
290    pub address: [u8; 16],
291    /// At most 128.
292    pub prefix: u8,
293    /// All-zero for no default route. May be link-local.
294    pub gateway: [u8; 16],
295}
296
297impl Default for V6Config {
298    fn default() -> Self {
299        Self {
300            method: Method::Auto,
301            address: [0; 16],
302            prefix: 0,
303            gateway: [0; 16],
304        }
305    }
306}
307
308impl V6Config {
309    /// Whether this configuration is one a device may be given.
310    ///
311    /// A usable static address already held on the link is *not*
312    /// rejected here: the device accepts it and reports
313    /// [`FamilyState::Conflict`], because a duplicate is discovered on
314    /// the wire rather than in the value.
315    pub fn validate(&self) -> Result<(), IpError> {
316        if !matches!(self.method, Method::Static) {
317            return Ok(());
318        }
319        if self.prefix > 128 {
320            return Err(IpError::NotWellFormed);
321        }
322        if !ipv6_is_usable_host(&self.address) {
323            return Err(IpError::NotWellFormed);
324        }
325        if !ipv6_gateway_is_valid(&self.gateway) {
326            return Err(IpError::NotWellFormed);
327        }
328        Ok(())
329    }
330
331    pub fn encode(&self, out: &mut [u8]) -> Result<usize, IpError> {
332        let len = if matches!(self.method, Method::Static) {
333            V6_CONFIG_STATIC_LEN
334        } else {
335            1
336        };
337        if out.len() < len {
338            return Err(IpError::BufferTooSmall);
339        }
340        out[0] = self.method.code();
341        if len > 1 {
342            out[1..17].copy_from_slice(&self.address);
343            out[17] = self.prefix;
344            out[18..34].copy_from_slice(&self.gateway);
345        }
346        Ok(len)
347    }
348
349    pub fn decode(bytes: &[u8]) -> Result<Self, IpError> {
350        let method = Method::from_code(*bytes.first().ok_or(IpError::Truncated)?)
351            .ok_or(IpError::Malformed)?;
352        let mut config = Self {
353            method,
354            ..Self::default()
355        };
356        if matches!(method, Method::Static) {
357            if bytes.len() < V6_CONFIG_STATIC_LEN {
358                return Err(IpError::Truncated);
359            }
360            config.address.copy_from_slice(&bytes[1..17]);
361            config.prefix = bytes[17];
362            config.gateway.copy_from_slice(&bytes[18..34]);
363        }
364        Ok(config)
365    }
366}
367
368/// `PROP_IPV4_ADDRESS`: what the interface holds.
369#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
370pub struct V4Address {
371    pub address: [u8; 4],
372    pub prefix: u8,
373    /// All-zero when there is no default route.
374    pub gateway: [u8; 4],
375}
376
377impl V4Address {
378    /// Whether the family has a way off the link.
379    pub fn has_gateway(&self) -> bool {
380        self.gateway != [0; 4]
381    }
382
383    pub fn encode(&self, out: &mut [u8]) -> Result<usize, IpError> {
384        if out.len() < V4_ADDRESS_LEN {
385            return Err(IpError::BufferTooSmall);
386        }
387        out[0..4].copy_from_slice(&self.address);
388        out[4] = self.prefix;
389        out[5..9].copy_from_slice(&self.gateway);
390        Ok(V4_ADDRESS_LEN)
391    }
392
393    pub fn decode(bytes: &[u8]) -> Result<Self, IpError> {
394        if bytes.len() < V4_ADDRESS_LEN {
395            return Err(IpError::Truncated);
396        }
397        let mut value = Self::default();
398        value.address.copy_from_slice(&bytes[0..4]);
399        value.prefix = bytes[4];
400        value.gateway.copy_from_slice(&bytes[5..9]);
401        Ok(value)
402    }
403}
404
405/// Item kinds of `PROP_IPV6_ADDRESSES`.
406#[derive(Clone, Copy, Debug, PartialEq, Eq)]
407pub enum V6ItemKind {
408    /// `IPV6_ADDRESS`
409    Address = 0,
410    /// `IPV6_ROUTER`
411    Router = 1,
412}
413
414impl V6ItemKind {
415    pub const fn code(self) -> u8 {
416        self as u8
417    }
418
419    pub const fn from_code(code: u8) -> Option<Self> {
420        match code {
421            0 => Some(Self::Address),
422            1 => Some(Self::Router),
423            _ => None,
424        }
425    }
426}
427
428/// One item of `PROP_IPV6_ADDRESSES`.
429#[derive(Clone, Copy, Debug, PartialEq, Eq)]
430pub enum V6Item {
431    /// A stable address the device is reachable at. The link-local
432    /// address is never among them, for the reason it does not make the
433    /// family ready.
434    Address {
435        address: [u8; 16],
436        /// The prefix the assignment carried. An address assigned by
437        /// DHCPv6 reports 128, which means the assignment carried no
438        /// prefix rather than that the link is a `/128`.
439        prefix: u8,
440    },
441    /// One router of the stack's default router list.
442    Router { address: [u8; 16] },
443}
444
445impl V6Item {
446    pub const fn kind(&self) -> V6ItemKind {
447        match self {
448            Self::Address { .. } => V6ItemKind::Address,
449            Self::Router { .. } => V6ItemKind::Router,
450        }
451    }
452
453    pub fn encode(&self, out: &mut [u8]) -> Result<usize, IpError> {
454        let len = match self {
455            Self::Address { .. } => V6_ADDRESS_ITEM_LEN,
456            Self::Router { .. } => V6_ROUTER_ITEM_LEN,
457        };
458        if out.len() < len {
459            return Err(IpError::BufferTooSmall);
460        }
461        out[0] = self.kind().code();
462        match self {
463            Self::Address { address, prefix } => {
464                out[1..17].copy_from_slice(address);
465                out[17] = *prefix;
466            }
467            Self::Router { address } => out[1..17].copy_from_slice(address),
468        }
469        Ok(len)
470    }
471
472    pub fn decode(bytes: &[u8]) -> Result<Self, IpError> {
473        let kind = V6ItemKind::from_code(*bytes.first().ok_or(IpError::Truncated)?)
474            .ok_or(IpError::Malformed)?;
475        let mut address = [0u8; 16];
476        match kind {
477            V6ItemKind::Address => {
478                if bytes.len() < V6_ADDRESS_ITEM_LEN {
479                    return Err(IpError::Truncated);
480                }
481                address.copy_from_slice(&bytes[1..17]);
482                Ok(Self::Address {
483                    address,
484                    prefix: bytes[17],
485                })
486            }
487            V6ItemKind::Router => {
488                if bytes.len() < V6_ROUTER_ITEM_LEN {
489                    return Err(IpError::Truncated);
490                }
491                address.copy_from_slice(&bytes[1..17]);
492                Ok(Self::Router { address })
493            }
494        }
495    }
496}
497
498#[cfg(test)]
499mod tests {
500    use super::*;
501
502    const GLOBAL_V6: [u8; 16] = [0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1];
503    const LINK_LOCAL_V6: [u8; 16] = [0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1];
504
505    #[test]
506    fn family_state_codes_round_trip_strictly() {
507        for code in 0..=4u8 {
508            let state = FamilyState::from_code(code).expect("allocated");
509            assert_eq!(state.code(), code);
510        }
511        assert_eq!(FamilyState::from_code(5), None);
512        assert!(FamilyState::Ready.is_ready());
513        assert!(!FamilyState::Waiting.is_ready());
514        assert!(!FamilyState::Conflict.is_ready());
515    }
516
517    #[test]
518    fn method_codes_round_trip_strictly() {
519        for code in 0..=2u8 {
520            assert_eq!(Method::from_code(code).expect("allocated").code(), code);
521        }
522        assert_eq!(Method::from_code(3), None);
523        // Automatic by default, so a device with nothing configured is
524        // on the network the moment it is associated.
525        assert_eq!(Method::default(), Method::Auto);
526    }
527
528    #[test]
529    fn a_link_local_v4_address_is_not_usable() {
530        // The whole point of the boundary: DHCP failed, and ready would
531        // hide it.
532        assert!(!ipv4_is_usable_host(&[169, 254, 3, 4]));
533        assert!(ipv4_is_usable_host(&[169, 253, 3, 4]));
534        assert!(ipv4_is_usable_host(&[192, 168, 4, 1]));
535        assert!(ipv4_is_usable_host(&[8, 8, 8, 8]));
536        assert!(!ipv4_is_usable_host(&[0, 0, 0, 0]));
537        assert!(!ipv4_is_usable_host(&[127, 0, 0, 1]));
538        assert!(!ipv4_is_usable_host(&[224, 0, 0, 1]));
539        assert!(!ipv4_is_usable_host(&[239, 255, 255, 250]));
540        assert!(!ipv4_is_usable_host(&[255, 255, 255, 255]));
541    }
542
543    #[test]
544    fn a_link_local_v6_address_is_not_usable() {
545        assert!(ipv6_is_usable_host(&GLOBAL_V6));
546        assert!(!ipv6_is_usable_host(&LINK_LOCAL_V6));
547        assert!(ipv6_is_link_local(&LINK_LOCAL_V6));
548        assert!(!ipv6_is_link_local(&GLOBAL_V6));
549        assert!(!ipv6_is_usable_host(&[0; 16]));
550        let mut loopback = [0u8; 16];
551        loopback[15] = 1;
552        assert!(!ipv6_is_usable_host(&loopback));
553        let mut multicast = [0u8; 16];
554        multicast[0] = 0xFF;
555        assert!(!ipv6_is_usable_host(&multicast));
556        // fe80::/10 covers febf:: too.
557        let mut high = LINK_LOCAL_V6;
558        high[1] = 0xBF;
559        assert!(ipv6_is_link_local(&high));
560    }
561
562    #[test]
563    fn only_the_v6_gateway_may_be_link_local() {
564        // A router names itself by its link-local address, so refusing
565        // one would refuse the ordinary case.
566        assert!(ipv6_gateway_is_valid(&LINK_LOCAL_V6));
567        assert!(ipv6_gateway_is_valid(&GLOBAL_V6));
568        assert!(ipv6_gateway_is_valid(&[0; 16]));
569        assert!(!ipv4_gateway_is_valid(&[169, 254, 0, 1]));
570        assert!(ipv4_gateway_is_valid(&[0, 0, 0, 0]));
571        assert!(ipv4_gateway_is_valid(&[192, 168, 4, 254]));
572    }
573
574    #[test]
575    fn a_resolver_is_four_or_sixteen_octets() {
576        assert!(resolver_is_valid(&[8, 8, 8, 8]));
577        assert!(resolver_is_valid(&GLOBAL_V6));
578        // A home router commonly advertises a link-local resolver.
579        assert!(resolver_is_valid(&LINK_LOCAL_V6));
580        assert!(!resolver_is_valid(&[169, 254, 0, 1]));
581        assert!(!resolver_is_valid(&[0, 0, 0, 0]));
582        assert!(!resolver_is_valid(&[127, 0, 0, 1]));
583        assert!(!resolver_is_valid(&[]));
584        assert!(!resolver_is_valid(&[1, 2, 3]));
585        assert!(!resolver_is_valid(&[0; 5]));
586    }
587
588    #[test]
589    fn an_automatic_config_is_one_octet() {
590        let config = V4Config::default();
591        let mut buf = [0u8; V4_CONFIG_STATIC_LEN];
592        assert_eq!(config.encode(&mut buf), Ok(1));
593        assert_eq!(buf[0], Method::Auto.code());
594        assert_eq!(V4Config::decode(&buf[..1]), Ok(config));
595
596        let v6 = V6Config::default();
597        let mut buf = [0u8; V6_CONFIG_STATIC_LEN];
598        assert_eq!(v6.encode(&mut buf), Ok(1));
599        assert_eq!(V6Config::decode(&buf[..1]), Ok(v6));
600    }
601
602    #[test]
603    fn a_static_v4_config_round_trips() {
604        let config = V4Config {
605            method: Method::Static,
606            address: [192, 168, 1, 40],
607            prefix: 24,
608            gateway: [192, 168, 1, 1],
609        };
610        config.validate().expect("well formed");
611        let mut buf = [0u8; V4_CONFIG_STATIC_LEN];
612        assert_eq!(config.encode(&mut buf), Ok(V4_CONFIG_STATIC_LEN));
613        assert_eq!(V4Config::decode(&buf), Ok(config));
614    }
615
616    #[test]
617    fn a_static_v6_config_round_trips() {
618        let config = V6Config {
619            method: Method::Static,
620            address: GLOBAL_V6,
621            prefix: 64,
622            gateway: LINK_LOCAL_V6,
623        };
624        config.validate().expect("well formed");
625        let mut buf = [0u8; V6_CONFIG_STATIC_LEN];
626        assert_eq!(config.encode(&mut buf), Ok(V6_CONFIG_STATIC_LEN));
627        assert_eq!(V6Config::decode(&buf), Ok(config));
628    }
629
630    #[test]
631    fn a_static_config_is_validated_against_the_readiness_boundary() {
632        let base = V4Config {
633            method: Method::Static,
634            address: [192, 168, 1, 40],
635            prefix: 24,
636            gateway: [0; 4],
637        };
638        assert!(base.validate().is_ok());
639        // The same rule IP_READY is stated in terms of, so a device
640        // cannot accept an address it would then refuse to call ready.
641        assert!(
642            V4Config {
643                address: [169, 254, 1, 1],
644                ..base
645            }
646            .validate()
647            .is_err()
648        );
649        assert!(V4Config { prefix: 33, ..base }.validate().is_err());
650        assert!(
651            V4Config {
652                gateway: [224, 0, 0, 1],
653                ..base
654            }
655            .validate()
656            .is_err()
657        );
658        // Nothing is validated when the method does not carry a value.
659        assert!(
660            V4Config {
661                method: Method::Auto,
662                address: [0; 4],
663                prefix: 99,
664                gateway: [0; 4],
665            }
666            .validate()
667            .is_ok()
668        );
669        assert!(
670            V6Config {
671                method: Method::Static,
672                address: GLOBAL_V6,
673                prefix: 129,
674                gateway: [0; 16],
675            }
676            .validate()
677            .is_err()
678        );
679        assert!(
680            V6Config {
681                method: Method::Static,
682                address: LINK_LOCAL_V6,
683                prefix: 64,
684                gateway: [0; 16],
685            }
686            .validate()
687            .is_err()
688        );
689    }
690
691    #[test]
692    fn a_v4_address_round_trips_with_and_without_a_gateway() {
693        let held = V4Address {
694            address: [10, 0, 0, 5],
695            prefix: 8,
696            gateway: [10, 0, 0, 1],
697        };
698        let mut buf = [0u8; V4_ADDRESS_LEN];
699        assert_eq!(held.encode(&mut buf), Ok(V4_ADDRESS_LEN));
700        assert_eq!(V4Address::decode(&buf), Ok(held));
701        assert!(held.has_gateway());
702
703        // An isolated network that hands out addresses and no gateway
704        // is ready, which is the honest reading.
705        let isolated = V4Address {
706            gateway: [0; 4],
707            ..held
708        };
709        assert_eq!(isolated.encode(&mut buf), Ok(V4_ADDRESS_LEN));
710        assert_eq!(V4Address::decode(&buf), Ok(isolated));
711        assert!(!isolated.has_gateway());
712    }
713
714    #[test]
715    fn the_two_v6_item_kinds_round_trip() {
716        let address = V6Item::Address {
717            address: GLOBAL_V6,
718            prefix: 64,
719        };
720        let mut buf = [0u8; V6_ADDRESS_ITEM_LEN];
721        assert_eq!(address.encode(&mut buf), Ok(V6_ADDRESS_ITEM_LEN));
722        assert_eq!(V6Item::decode(&buf), Ok(address));
723        assert_eq!(address.kind(), V6ItemKind::Address);
724
725        let router = V6Item::Router {
726            address: LINK_LOCAL_V6,
727        };
728        assert_eq!(router.encode(&mut buf), Ok(V6_ROUTER_ITEM_LEN));
729        assert_eq!(V6Item::decode(&buf[..V6_ROUTER_ITEM_LEN]), Ok(router));
730        assert_eq!(router.kind(), V6ItemKind::Router);
731
732        // A DHCPv6 assignment carries no prefix and reports 128.
733        let dhcpv6 = V6Item::Address {
734            address: GLOBAL_V6,
735            prefix: 128,
736        };
737        assert_eq!(dhcpv6.encode(&mut buf), Ok(V6_ADDRESS_ITEM_LEN));
738        assert_eq!(V6Item::decode(&buf), Ok(dhcpv6));
739    }
740
741    #[test]
742    fn unknown_codes_and_short_values_are_told_apart() {
743        assert_eq!(V4Config::decode(&[]), Err(IpError::Truncated));
744        assert_eq!(V4Config::decode(&[7]), Err(IpError::Malformed));
745        assert_eq!(
746            V4Config::decode(&[Method::Static.code(), 1, 2]),
747            Err(IpError::Truncated)
748        );
749        assert_eq!(V6Item::decode(&[]), Err(IpError::Truncated));
750        assert_eq!(V6Item::decode(&[9]), Err(IpError::Malformed));
751        assert_eq!(V6Item::decode(&[0, 1, 2]), Err(IpError::Truncated));
752        assert_eq!(V4Address::decode(&[0; 8]), Err(IpError::Truncated));
753    }
754
755    #[test]
756    fn encoders_report_a_buffer_too_small_rather_than_truncating() {
757        let mut small = [0u8; 4];
758        assert_eq!(
759            V4Config {
760                method: Method::Static,
761                ..V4Config::default()
762            }
763            .encode(&mut small),
764            Err(IpError::BufferTooSmall)
765        );
766        assert_eq!(
767            V4Address::default().encode(&mut small),
768            Err(IpError::BufferTooSmall)
769        );
770        assert_eq!(
771            V6Item::Router { address: [0; 16] }.encode(&mut small),
772            Err(IpError::BufferTooSmall)
773        );
774    }
775}