1use core::net::Ipv4Addr;
2
3use num_enum::{FromPrimitive, IntoPrimitive};
4
5use super::option_fsm::{Protocol, Verdict};
6use crate::wire::ProtocolType;
7
8#[derive(FromPrimitive, IntoPrimitive, Copy, Clone, Eq, PartialEq, Debug)]
9#[cfg_attr(feature = "defmt", derive(defmt::Format))]
10#[repr(u8)]
11enum OptionCode {
12 #[num_enum(default)]
13 Unknown = 0,
14 IpAddress = 3,
15 Dns1 = 129,
16 Dns2 = 131,
17}
18
19struct IpOption {
20 address: Ipv4Addr,
21 is_rejected: bool,
22}
23
24impl IpOption {
25 fn new() -> Self {
26 Self {
27 address: Ipv4Addr::UNSPECIFIED,
28 is_rejected: false,
29 }
30 }
31
32 fn get(&self) -> Option<Ipv4Addr> {
33 if self.is_rejected || self.address.is_unspecified() {
34 None
35 } else {
36 Some(self.address)
37 }
38 }
39
40 fn nacked(&mut self, data: &[u8], is_rej: bool) {
41 if is_rej {
42 self.is_rejected = true
43 } else {
44 match <[u8; 4]>::try_from(data) {
45 Ok(data) => self.address = Ipv4Addr::from(data),
47 Err(_) => self.is_rejected = true,
51 }
52 }
53 }
54}
55
56#[derive(Debug)]
58#[cfg_attr(feature = "defmt", derive(defmt::Format))]
59pub struct Ipv4Status {
60 pub address: Option<Ipv4Addr>,
62 pub peer_address: Option<Ipv4Addr>,
64 pub dns_servers: [Option<Ipv4Addr>; 2],
66}
67
68pub(crate) struct IPv4CP {
69 peer_address: Ipv4Addr,
70
71 address: IpOption,
72 dns_server_1: IpOption,
73 dns_server_2: IpOption,
74}
75
76impl IPv4CP {
77 pub fn new() -> Self {
78 Self {
79 peer_address: Ipv4Addr::UNSPECIFIED,
80
81 address: IpOption::new(),
82 dns_server_1: IpOption::new(),
83 dns_server_2: IpOption::new(),
84 }
85 }
86
87 pub fn status(&self) -> Ipv4Status {
88 let peer_address = if self.peer_address.is_unspecified() {
89 None
90 } else {
91 Some(self.peer_address)
92 };
93
94 Ipv4Status {
95 address: self.address.get(),
96 peer_address,
97 dns_servers: [self.dns_server_1.get(), self.dns_server_2.get()],
98 }
99 }
100}
101
102impl Protocol for IPv4CP {
103 fn protocol(&self) -> ProtocolType {
104 ProtocolType::IPv4CP
105 }
106
107 fn peer_options_start(&mut self) {}
108
109 fn peer_option_received(&mut self, code: u8, data: &[u8]) -> Verdict {
110 let opt = OptionCode::from(code);
111 trace!("IPv4CP: rx option {:?} {:?} {:?}", code, opt, data);
112 match opt {
113 OptionCode::IpAddress => match <[u8; 4]>::try_from(data) {
114 Ok(data) => {
115 self.peer_address = Ipv4Addr::from(data);
116 Verdict::Ack
117 }
118 Err(_) => Verdict::Rej,
119 },
120 _ => Verdict::Rej,
121 }
122 }
123
124 fn own_options(&mut self, mut f: impl FnMut(u8, &[u8])) {
125 if !self.address.is_rejected {
126 f(OptionCode::IpAddress.into(), &self.address.address.octets());
127 }
128 if !self.dns_server_1.is_rejected {
129 f(OptionCode::Dns1.into(), &self.dns_server_1.address.octets());
130 }
131 if !self.dns_server_2.is_rejected {
132 f(OptionCode::Dns2.into(), &self.dns_server_2.address.octets());
133 }
134 }
135
136 fn own_option_nacked(&mut self, code: u8, data: &[u8], is_rej: bool) {
137 let opt = OptionCode::from(code);
138 trace!("IPv4CP nak {:?} {:?} {:?} {:?}", code, opt, data, is_rej);
139 match opt {
140 OptionCode::Unknown => {}
141 OptionCode::IpAddress => self.address.nacked(data, is_rej),
142 OptionCode::Dns1 => self.dns_server_1.nacked(data, is_rej),
143 OptionCode::Dns2 => self.dns_server_2.nacked(data, is_rej),
144 }
145 }
146}