1use umsh_ulcp::{ip, wifi};
17
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20pub struct WifiConfig {
21 pub station: bool,
29 pub default_enabled: bool,
35 pub supported_modes: u16,
37 pub max_sae_password: usize,
39 pub raw_keys: bool,
41 pub binary_ssids: bool,
43}
44
45impl WifiConfig {
46 pub fn validate_network(
47 &self,
48 entry: &wifi::NetworkEntry<'_>,
49 ) -> Result<(), umsh_ulcp::Status> {
50 entry
51 .validate()
52 .map_err(|_| umsh_ulcp::Status::INVALID_ARGUMENT)?;
53 if self.supported_modes & entry.security.bit() == 0
54 || (entry.raw_key && !self.raw_keys)
55 || (!self.binary_ssids
56 && (entry.ssid.contains(&0) || core::str::from_utf8(entry.ssid).is_err()))
57 || (entry.security == wifi::SecurityMode::Wpa3
58 && entry.credential.len() > self.max_sae_password)
59 {
60 return Err(umsh_ulcp::Status::UNIMPLEMENTED);
61 }
62 Ok(())
63 }
64
65 pub const STATION: Self = Self {
67 station: true,
68 default_enabled: false,
69 supported_modes: 0x1f,
70 max_sae_password: 128,
71 raw_keys: true,
72 binary_ssids: true,
73 };
74
75 pub const ALWAYS_ON: Self = Self {
77 station: true,
78 default_enabled: true,
79 supported_modes: 0x1f,
80 max_sae_password: 128,
81 raw_keys: true,
82 binary_ssids: true,
83 };
84
85 pub const SCAN_ONLY: Self = Self {
87 station: false,
88 default_enabled: false,
89 supported_modes: 0x1f,
90 max_sae_password: 128,
91 raw_keys: true,
92 binary_ssids: true,
93 };
94}
95
96#[derive(Clone, Copy, Debug, PartialEq, Eq)]
102pub struct IpConfig {
103 pub v4: bool,
104 pub v6: bool,
105 pub manual_dns: bool,
107}
108
109impl IpConfig {
110 pub const DUAL: Self = Self {
112 v4: true,
113 v6: true,
114 manual_dns: true,
115 };
116 pub const V4_ONLY: Self = Self {
117 v4: true,
118 v6: false,
119 manual_dns: true,
120 };
121}
122
123pub const MAX_RESOLVERS: usize = 3;
130
131#[derive(Clone, Copy, Debug, PartialEq, Eq)]
137pub struct Resolvers {
138 entries: [[u8; 16]; MAX_RESOLVERS],
139 widths: [u8; MAX_RESOLVERS],
140 len: usize,
141}
142
143impl Default for Resolvers {
144 fn default() -> Self {
145 Self {
146 entries: [[0; 16]; MAX_RESOLVERS],
147 widths: [0; MAX_RESOLVERS],
148 len: 0,
149 }
150 }
151}
152
153impl Resolvers {
154 pub fn is_empty(&self) -> bool {
155 self.len == 0
156 }
157
158 pub fn iter(&self) -> impl Iterator<Item = &[u8]> {
160 (0..self.len).map(|index| &self.entries[index][..self.widths[index] as usize])
161 }
162
163 pub fn push(&mut self, resolver: &[u8]) -> Result<(), ResolverError> {
168 if !ip::resolver_is_valid(resolver) {
169 return Err(ResolverError::NotWellFormed);
170 }
171 if self.len == MAX_RESOLVERS {
172 return Err(ResolverError::TooMany);
173 }
174 self.entries[self.len][..resolver.len()].copy_from_slice(resolver);
175 self.widths[self.len] = resolver.len() as u8;
176 self.len += 1;
177 Ok(())
178 }
179
180 pub fn replace(&mut self, items: &[u8]) -> Result<(), ResolverError> {
188 let mut next = Self::default();
189 for item in umsh_ulcp::items::prefixed_items(items) {
190 let item = item.map_err(|_| ResolverError::Malformed)?;
191 next.push(item)?;
192 }
193 *self = next;
194 Ok(())
195 }
196
197 pub fn encode(&self, out: &mut [u8]) -> Option<usize> {
199 let mut len = 0;
200 for resolver in self.iter() {
201 len += umsh_ulcp::items::encode_prefixed_item(resolver, out.get_mut(len..)?).ok()?;
202 }
203 Some(len)
204 }
205}
206
207#[derive(Clone, Copy, Debug, PartialEq, Eq)]
209pub enum ResolverError {
210 Malformed,
212 NotWellFormed,
215 TooMany,
217}
218
219#[derive(Clone, Copy, Debug, PartialEq, Eq)]
225pub struct SelectedNetwork {
226 ssid: [u8; wifi::SSID_MAX_LEN],
227 len: usize,
228}
229
230impl Default for SelectedNetwork {
231 fn default() -> Self {
232 Self {
233 ssid: [0; wifi::SSID_MAX_LEN],
234 len: 0,
235 }
236 }
237}
238
239impl SelectedNetwork {
240 pub fn as_bytes(&self) -> &[u8] {
241 &self.ssid[..self.len]
242 }
243
244 pub fn is_empty(&self) -> bool {
245 self.len == 0
246 }
247
248 pub fn set(&mut self, ssid: &[u8]) -> Result<(), ()> {
250 if ssid.len() > wifi::SSID_MAX_LEN {
251 return Err(());
252 }
253 self.ssid[..ssid.len()].copy_from_slice(ssid);
254 self.len = ssid.len();
255 Ok(())
256 }
257}
258
259#[cfg(test)]
260mod tests {
261 use super::*;
262
263 #[test]
264 fn limited_driver_rejects_unsupported_profiles_without_rejecting_unicode() {
265 let config = WifiConfig {
266 raw_keys: false,
267 binary_ssids: false,
268 max_sae_password: 63,
269 ..WifiConfig::STATION
270 };
271 let mut entry = wifi::NetworkEntry {
272 hidden: false,
273 raw_key: false,
274 security: wifi::SecurityMode::Wpa2,
275 ssid: "Café".as_bytes(),
276 credential: b"password",
277 };
278 assert_eq!(config.validate_network(&entry), Ok(()));
279 for ssid in [b"\xff".as_slice(), b"a\0b"] {
280 entry.ssid = ssid;
281 assert_eq!(
282 config.validate_network(&entry),
283 Err(umsh_ulcp::Status::UNIMPLEMENTED)
284 );
285 }
286 entry.ssid = b"network";
287 entry.raw_key = true;
288 entry.credential = &[0x42; 32];
289 assert_eq!(
290 config.validate_network(&entry),
291 Err(umsh_ulcp::Status::UNIMPLEMENTED)
292 );
293 entry.raw_key = false;
294 entry.security = wifi::SecurityMode::Wpa3;
295 entry.credential = &[b'x'; 64];
296 assert_eq!(
297 config.validate_network(&entry),
298 Err(umsh_ulcp::Status::UNIMPLEMENTED)
299 );
300 }
301
302 fn item(bytes: &[u8]) -> Vec<u8> {
303 let mut out = Vec::new();
304 out.push(bytes.len() as u8);
305 out.extend_from_slice(bytes);
306 out
307 }
308
309 #[test]
310 fn a_resolver_list_takes_both_families_and_refuses_a_fourth() {
311 let mut resolvers = Resolvers::default();
312 let mut items = Vec::new();
313 items.extend_from_slice(&item(&[192, 168, 1, 1]));
314 let mut v6 = [0u8; 16];
315 v6[0] = 0x20;
316 v6[1] = 0x01;
317 v6[15] = 1;
318 items.extend_from_slice(&item(&v6));
319 assert_eq!(resolvers.replace(&items), Ok(()));
320 assert_eq!(resolvers.iter().count(), 2);
321
322 let mut too_many = items.clone();
323 too_many.extend_from_slice(&item(&[9, 9, 9, 9]));
324 too_many.extend_from_slice(&item(&[1, 1, 1, 1]));
325 assert_eq!(resolvers.replace(&too_many), Err(ResolverError::TooMany));
326 assert_eq!(resolvers.iter().count(), 2);
328 }
329
330 #[test]
331 fn a_resolver_nothing_answers_on_is_refused() {
332 let mut resolvers = Resolvers::default();
333 assert_eq!(
334 resolvers.replace(&item(&[0, 0, 0, 0])),
335 Err(ResolverError::NotWellFormed)
336 );
337 assert_eq!(
340 resolvers.replace(&item(&[169, 254, 1, 1])),
341 Err(ResolverError::NotWellFormed)
342 );
343 assert_eq!(
344 resolvers.replace(&item(&[1, 2, 3])),
345 Err(ResolverError::NotWellFormed)
346 );
347 }
348
349 #[test]
350 fn an_empty_list_clears_and_round_trips() {
351 let mut resolvers = Resolvers::default();
352 assert_eq!(resolvers.replace(&item(&[9, 9, 9, 9])), Ok(()));
353 assert_eq!(resolvers.replace(&[]), Ok(()));
354 assert!(resolvers.is_empty());
355 let mut out = [0u8; 64];
356 assert_eq!(resolvers.encode(&mut out), Some(0));
357 }
358
359 #[test]
360 fn the_selected_network_holds_arbitrary_octets_and_the_empty_choice() {
361 let mut selected = SelectedNetwork::default();
362 assert!(selected.is_empty());
363 assert_eq!(selected.set(&[0xFF, 0x00, 0x80]), Ok(()));
364 assert_eq!(selected.as_bytes(), [0xFF, 0x00, 0x80]);
365 assert_eq!(selected.set(&[]), Ok(()));
366 assert!(selected.is_empty());
367 assert_eq!(selected.set(&[0x41; 33]), Err(()));
368 }
369}