1use core::fmt;
8
9use crate::{
10 Status,
11 frame::{Cmd, Frame, MultiEntries, MultiGetKeys, PropPayload, StreamPayload},
12 ids::{cap, prop},
13 pui,
14};
15
16pub const fn property_name(key: u32) -> Option<&'static str> {
18 Some(match key {
19 prop::LAST_STATUS => "PROP_LAST_STATUS",
20 prop::PROTOCOL_VERSION => "PROP_PROTOCOL_VERSION",
21 prop::DEV_VERSION => "PROP_DEV_VERSION",
22 prop::DEV_MODEL => "PROP_DEV_MODEL",
23 prop::INTERFACE_TYPE => "PROP_INTERFACE_TYPE",
24 prop::CAPS => "PROP_CAPS",
25 prop::UPTIME => "PROP_UPTIME",
26 prop::PHY_ENABLED => "PROP_PHY_ENABLED",
27 prop::PHY_FREQ => "PROP_PHY_FREQ",
28 prop::PHY_TX_POWER => "PROP_PHY_TX_POWER",
29 prop::PHY_RSSI => "PROP_PHY_RSSI",
30 prop::PHY_LORA_BW => "PROP_PHY_LORA_BW",
31 prop::PHY_LORA_SF => "PROP_PHY_LORA_SF",
32 prop::PHY_LORA_CR => "PROP_PHY_LORA_CR",
33 prop::PHY_MTU => "PROP_PHY_MTU",
34 prop::PHY_LORA_SW => "PROP_PHY_LORA_SW",
35 prop::MAC_PROMISCUOUS => "PROP_MAC_PROMISCUOUS",
36 prop::MAC_BACKHAUL => "PROP_MAC_BACKHAUL",
37 prop::SAVED => "PROP_SAVED",
38 prop::DEV_KEY => "PROP_DEV_KEY",
39 prop::DEV_PRIVATE_KEY => "PROP_DEV_PRIVATE_KEY",
40 prop::DEV_CHANNEL_KEYS => "PROP_DEV_CHANNEL_KEYS",
41 prop::DEV_PEERS => "PROP_DEV_PEERS",
42 prop::DEV_NAME => "PROP_DEV_NAME",
43 prop::BATTERY => "PROP_BATTERY",
44 prop::MAC_REPEATER_ENABLED => "PROP_MAC_REPEATER_ENABLED",
45 prop::IDENT => "PROP_IDENT",
46 prop::IDENT_ROLE => "PROP_IDENT_ROLE",
47 prop::IDENT_MOBILE => "PROP_IDENT_MOBILE",
48 prop::MAC_REPEATER_REGIONS => "PROP_MAC_REPEATER_REGIONS",
49 prop::MAC_REPEATER_DEFAULT_REGION => "PROP_MAC_REPEATER_DEFAULT_REGION",
50 prop::MAC_REPEATER_MIN_RSSI => "PROP_MAC_REPEATER_MIN_RSSI",
51 prop::MAC_REPEATER_MIN_SNR => "PROP_MAC_REPEATER_MIN_SNR",
52 prop::DEV_DISCOVERABLE => "PROP_DEV_DISCOVERABLE",
53 prop::ALERT => "PROP_ALERT",
54 prop::ADVERT_INTERVAL => "PROP_ADVERT_INTERVAL",
55 prop::BEACON_INTERVAL => "PROP_BEACON_INTERVAL",
56 prop::STARTUP_BEACON => "PROP_STARTUP_BEACON",
57 prop::IDENT_LOCATION => "PROP_IDENT_LOCATION",
58 prop::IDENT_ALTITUDE => "PROP_IDENT_ALTITUDE",
59 prop::GNSS_ENABLED => "PROP_GNSS_ENABLED",
60 prop::GNSS_LOCATION => "PROP_GNSS_LOCATION",
61 prop::GNSS_ALTITUDE => "PROP_GNSS_ALTITUDE",
62 prop::GNSS_FIX => "PROP_GNSS_FIX",
63 prop::GNSS_PRECISION => "PROP_GNSS_PRECISION",
64 prop::GNSS_SATELLITES => "PROP_GNSS_SATELLITES",
65 prop::ILLUMINANCE => "PROP_ILLUMINANCE",
66 prop::HOST_KEY => "PROP_HOST_KEY",
67 prop::HOST_CHANNEL_KEYS => "PROP_HOST_CHANNEL_KEYS",
68 prop::HOST_PEER_KEYS => "PROP_HOST_PEER_KEYS",
69 prop::HOST_RX_FILTERS => "PROP_HOST_RX_FILTERS",
70 prop::HOST_AUTO_ACK => "PROP_HOST_AUTO_ACK",
71 prop::HOST_RX_QUEUE_COUNT => "PROP_HOST_RX_QUEUE_COUNT",
72 prop::HOST_RX_QUEUE_CAPACITY => "PROP_HOST_RX_QUEUE_CAPACITY",
73 prop::HOST_RX_QUEUE_DROPPED => "PROP_HOST_RX_QUEUE_DROPPED",
74 prop::PHY_DUTY_NOW => "PROP_PHY_DUTY_NOW",
75 prop::PHY_DUTY_LIMIT => "PROP_PHY_DUTY_LIMIT",
76 prop::STAT_TX_PACKETS => "PROP_STAT_TX_PACKETS",
77 prop::STAT_TX_CHANNEL_BUSY => "PROP_STAT_TX_CHANNEL_BUSY",
78 prop::STAT_RX_PACKETS => "PROP_STAT_RX_PACKETS",
79 prop::STAT_RX_BAD_CRC => "PROP_STAT_RX_BAD_CRC",
80 prop::STAT_RX_NON_UMSH => "PROP_STAT_RX_NON_UMSH",
81 prop::STAT_RX_ACCEPTED => "PROP_STAT_RX_ACCEPTED",
82 prop::STAT_FORWARDED => "PROP_STAT_FORWARDED",
83 prop::STAT_FORWARD_DROPPED => "PROP_STAT_FORWARD_DROPPED",
84 prop::STAT_FORWARD_CANCELLED => "PROP_STAT_FORWARD_CANCELLED",
85 prop::BLE_PAIRING_PIN => "PROP_BLE_PAIRING_PIN",
86 prop::DEV_ADMINS => "PROP_DEV_ADMINS",
87 prop::TIME => "PROP_TIME",
88 prop::TZ_OFFSET => "PROP_TZ_OFFSET",
89 prop::GNSS_IDENT_UPDATE => "PROP_GNSS_IDENT_UPDATE",
90 prop::GNSS_IDENT_PRECISION => "PROP_GNSS_IDENT_PRECISION",
91 prop::GNSS_TIME_TRUST => "PROP_GNSS_TIME_TRUST",
92 prop::BLE_ENABLED => "PROP_BLE_ENABLED",
93 prop::BLE_BOND_COUNT => "PROP_BLE_BOND_COUNT",
94 prop::BLE_LINK => "PROP_BLE_LINK",
95 prop::BLE_PAIRING => "PROP_BLE_PAIRING",
96 _ => return None,
97 })
98}
99
100pub const PROPERTIES: &[u32] = &[
106 prop::LAST_STATUS,
107 prop::PROTOCOL_VERSION,
108 prop::DEV_VERSION,
109 prop::DEV_MODEL,
110 prop::INTERFACE_TYPE,
111 prop::CAPS,
112 prop::UPTIME,
113 prop::PHY_ENABLED,
114 prop::PHY_FREQ,
115 prop::PHY_TX_POWER,
116 prop::PHY_RSSI,
117 prop::PHY_LORA_BW,
118 prop::PHY_LORA_SF,
119 prop::PHY_LORA_CR,
120 prop::PHY_MTU,
121 prop::PHY_LORA_SW,
122 prop::MAC_PROMISCUOUS,
123 prop::MAC_BACKHAUL,
124 prop::SAVED,
125 prop::DEV_KEY,
126 prop::DEV_PRIVATE_KEY,
127 prop::DEV_CHANNEL_KEYS,
128 prop::DEV_PEERS,
129 prop::DEV_NAME,
130 prop::BATTERY,
131 prop::MAC_REPEATER_ENABLED,
132 prop::IDENT,
133 prop::IDENT_ROLE,
134 prop::IDENT_MOBILE,
135 prop::MAC_REPEATER_REGIONS,
136 prop::MAC_REPEATER_DEFAULT_REGION,
137 prop::MAC_REPEATER_MIN_RSSI,
138 prop::MAC_REPEATER_MIN_SNR,
139 prop::DEV_DISCOVERABLE,
140 prop::ALERT,
141 prop::ADVERT_INTERVAL,
142 prop::BEACON_INTERVAL,
143 prop::STARTUP_BEACON,
144 prop::IDENT_LOCATION,
145 prop::IDENT_ALTITUDE,
146 prop::GNSS_ENABLED,
147 prop::GNSS_LOCATION,
148 prop::GNSS_ALTITUDE,
149 prop::GNSS_FIX,
150 prop::GNSS_PRECISION,
151 prop::GNSS_SATELLITES,
152 prop::ILLUMINANCE,
153 prop::HOST_KEY,
154 prop::HOST_CHANNEL_KEYS,
155 prop::HOST_PEER_KEYS,
156 prop::HOST_RX_FILTERS,
157 prop::HOST_AUTO_ACK,
158 prop::HOST_RX_QUEUE_COUNT,
159 prop::HOST_RX_QUEUE_CAPACITY,
160 prop::HOST_RX_QUEUE_DROPPED,
161 prop::PHY_DUTY_NOW,
162 prop::PHY_DUTY_LIMIT,
163 prop::STAT_TX_PACKETS,
164 prop::STAT_TX_CHANNEL_BUSY,
165 prop::STAT_RX_PACKETS,
166 prop::STAT_RX_BAD_CRC,
167 prop::STAT_RX_NON_UMSH,
168 prop::STAT_RX_ACCEPTED,
169 prop::STAT_FORWARDED,
170 prop::STAT_FORWARD_DROPPED,
171 prop::STAT_FORWARD_CANCELLED,
172 prop::BLE_PAIRING_PIN,
173 prop::DEV_ADMINS,
174 prop::TIME,
175 prop::TZ_OFFSET,
176 prop::GNSS_IDENT_UPDATE,
177 prop::GNSS_IDENT_PRECISION,
178 prop::GNSS_TIME_TRUST,
179 prop::BLE_ENABLED,
180 prop::BLE_BOND_COUNT,
181 prop::BLE_LINK,
182 prop::BLE_PAIRING,
183];
184
185#[derive(Clone, Copy, Debug, PartialEq, Eq)]
195pub enum PropertyType {
196 Bool,
198 U8,
199 U16,
200 U32,
201 I8,
202 I16,
203 I32,
204 Text,
206 Key32,
208 Status,
210 Bytes,
212}
213
214pub const fn property_type(key: u32) -> Option<PropertyType> {
221 use PropertyType::{Bool, Bytes, I8, I16, I32, Key32, Status, Text, U8, U16, U32};
222 Some(match key {
223 prop::LAST_STATUS => Status,
224 prop::PROTOCOL_VERSION => Bytes,
226 prop::DEV_VERSION | prop::DEV_MODEL | prop::DEV_NAME => Text,
227 prop::INTERFACE_TYPE => U32,
228 prop::CAPS => Bytes,
230 prop::UPTIME => U32,
231 prop::PHY_ENABLED => Bool,
232 prop::PHY_FREQ => U32,
233 prop::PHY_TX_POWER | prop::PHY_RSSI => I8,
234 prop::PHY_LORA_BW => U32,
235 prop::PHY_LORA_SF | prop::PHY_LORA_CR => U8,
236 prop::PHY_MTU | prop::PHY_LORA_SW => U16,
237 prop::MAC_PROMISCUOUS | prop::MAC_BACKHAUL => Bool,
238 prop::SAVED => U8,
239 prop::DEV_KEY | prop::DEV_PRIVATE_KEY | prop::HOST_KEY => Key32,
240 prop::DEV_CHANNEL_KEYS
242 | prop::DEV_PEERS
243 | prop::DEV_ADMINS
244 | prop::HOST_CHANNEL_KEYS
245 | prop::HOST_PEER_KEYS
246 | prop::HOST_RX_FILTERS => Bytes,
247 prop::BATTERY => Bytes,
249 prop::MAC_REPEATER_ENABLED => Bool,
250 prop::IDENT => Bytes,
252 prop::IDENT_ROLE => U8,
253 prop::IDENT_MOBILE => Bool,
254 prop::MAC_REPEATER_REGIONS => Text,
256 prop::MAC_REPEATER_DEFAULT_REGION => Bytes,
258 prop::MAC_REPEATER_MIN_RSSI => I16,
259 prop::MAC_REPEATER_MIN_SNR => I8,
260 prop::DEV_DISCOVERABLE => Bool,
261 prop::ALERT => U8,
262 prop::ADVERT_INTERVAL | prop::BEACON_INTERVAL => U32,
263 prop::STARTUP_BEACON => Bool,
264 prop::IDENT_LOCATION | prop::GNSS_LOCATION => Bytes,
266 prop::IDENT_ALTITUDE => Bytes,
268 prop::GNSS_ENABLED => Bool,
269 prop::GNSS_ALTITUDE => I32,
270 prop::GNSS_FIX => U8,
271 prop::GNSS_PRECISION => U16,
272 prop::GNSS_SATELLITES => Bytes,
274 prop::ILLUMINANCE => U32,
275 prop::HOST_AUTO_ACK => Bool,
276 prop::HOST_RX_QUEUE_COUNT | prop::HOST_RX_QUEUE_CAPACITY => U16,
277 prop::HOST_RX_QUEUE_DROPPED => U32,
278 prop::PHY_DUTY_NOW | prop::PHY_DUTY_LIMIT => U16,
279 prop::STAT_TX_PACKETS
280 | prop::STAT_TX_CHANNEL_BUSY
281 | prop::STAT_RX_PACKETS
282 | prop::STAT_RX_BAD_CRC
283 | prop::STAT_RX_NON_UMSH
284 | prop::STAT_RX_ACCEPTED
285 | prop::STAT_FORWARDED
286 | prop::STAT_FORWARD_DROPPED
287 | prop::STAT_FORWARD_CANCELLED => U32,
288 prop::BLE_PAIRING_PIN => U32,
289 prop::TIME => U32,
290 prop::TZ_OFFSET => I16,
291 prop::GNSS_IDENT_UPDATE => Bool,
292 prop::GNSS_IDENT_PRECISION => U8,
293 prop::GNSS_TIME_TRUST | prop::BLE_ENABLED | prop::BLE_PAIRING => Bool,
294 prop::BLE_BOND_COUNT | prop::BLE_LINK => U8,
295 _ => return None,
296 })
297}
298
299pub const fn capability_name(code: u32) -> Option<&'static str> {
301 Some(match code {
302 cap::WRITABLE_RAW_STREAM => "WRITABLE_RAW_STREAM",
303 cap::PHY_DUTY_LIMIT => "PHY_DUTY_LIMIT",
304 cap::PHY_LORA => "PHY_LORA",
305 cap::HOST_FILTER => "HOST_FILTER",
306 cap::HOST_RX_QUEUE => "HOST_RX_QUEUE",
307 cap::HOST_KEYS => "HOST_KEYS",
308 cap::HOST_AUTO_ACK => "HOST_AUTO_ACK",
309 cap::SAVE => "SAVE",
310 cap::DEV_IDENTITY => "DEV_IDENTITY",
311 cap::DEV_NAME => "DEV_NAME",
312 cap::BATTERY => "BATTERY",
313 cap::REPEATER => "REPEATER",
314 cap::IDENT => "IDENT",
315 cap::ALERT => "ALERT",
316 cap::TIME => "TIME",
317 cap::GNSS => "GNSS",
318 cap::ADVERT => "ADVERT",
319 cap::ILLUMINANCE => "ILLUMINANCE",
320 cap::MAC_BACKHAUL => "MAC_BACKHAUL",
321 cap::ADMIN => "ADMIN",
322 cap::CMD_MULTI => "CMD_MULTI",
323 cap::BLE => "BLE",
324 cap::REBOOT => "REBOOT",
325 cap::STATS => "STATS",
326 _ => return None,
327 })
328}
329
330pub struct FrameDescription<'a>(pub &'a [u8]);
335
336impl fmt::Display for FrameDescription<'_> {
337 fn fmt(&self, out: &mut fmt::Formatter<'_>) -> fmt::Result {
338 let bytes = self.0;
339 let Ok(frame) = Frame::parse(bytes) else {
340 return write!(out, "malformed frame ({} bytes)", bytes.len());
341 };
342 let tid = frame.header.tid();
343 let Some(command) = frame.command() else {
344 return write!(out, "unknown command tid={tid} ({} bytes)", bytes.len());
345 };
346 match command {
347 Cmd::Nop
348 | Cmd::Reset
349 | Cmd::QueueDrain
350 | Cmd::Save
351 | Cmd::Clear
352 | Cmd::Restore
353 | Cmd::FactoryReset
354 | Cmd::Reboot
355 | Cmd::BleClearBonds => {
356 write!(out, "{command:?} tid={tid}")
357 }
358 Cmd::PropGet
359 | Cmd::PropSet
360 | Cmd::PropIs
361 | Cmd::PropInsert
362 | Cmd::PropRemove
363 | Cmd::PropInserted
364 | Cmd::PropRemoved => {
365 let Ok(payload) = PropPayload::parse(frame.payload) else {
366 return write!(out, "{command:?} tid={tid} (malformed payload)");
367 };
368 let name = property_name(payload.key);
369 if payload.key == prop::LAST_STATUS && command == Cmd::PropIs {
370 let status = pui::decode(payload.value)
371 .map(|(code, _)| Status(code))
372 .unwrap_or(Status::FAILURE);
373 if let Some(name) = name {
374 write!(out, "{command:?} tid={tid} {name} = {status:?}")
375 } else {
376 write!(
377 out,
378 "{command:?} tid={tid} prop {} = {status:?}",
379 payload.key
380 )
381 }
382 } else if let Some(name) = name {
383 write!(
384 out,
385 "{command:?} tid={tid} {name} ({} value bytes)",
386 payload.value.len()
387 )
388 } else {
389 write!(
390 out,
391 "{command:?} tid={tid} prop {} ({} value bytes)",
392 payload.key,
393 payload.value.len()
394 )
395 }
396 }
397 Cmd::PropMultiGet => {
398 let mut count = 0usize;
399 for key in MultiGetKeys::new(frame.payload) {
400 if key.is_err() {
401 return write!(out, "{command:?} tid={tid} (malformed payload)");
402 }
403 count += 1;
404 }
405 write!(out, "{command:?} tid={tid} ({count} properties)")
406 }
407 Cmd::PropMultiSet | Cmd::PropAre => {
408 let mut count = 0usize;
409 let mut value_bytes = 0usize;
410 for entry in MultiEntries::new(frame.payload) {
411 let Ok(entry) = entry else {
412 return write!(out, "{command:?} tid={tid} (malformed payload)");
413 };
414 count += 1;
415 value_bytes += entry.value.len();
416 }
417 write!(
418 out,
419 "{command:?} tid={tid} ({count} entries, {value_bytes} value bytes)"
420 )
421 }
422 Cmd::StrSend | Cmd::StrRecv => match StreamPayload::parse(frame.payload) {
423 Ok(payload) => write!(
424 out,
425 "{command:?} tid={tid} stream={} ({} data bytes, {} meta bytes)",
426 payload.stream,
427 payload.data.len(),
428 payload.metadata.len()
429 ),
430 Err(_) => write!(out, "{command:?} tid={tid} (malformed payload)"),
431 },
432 }
433 }
434}
435
436#[cfg(test)]
437mod tests {
438 use super::*;
439 use crate::frame;
440
441 #[test]
442 fn describes_known_and_unknown_properties_without_values() {
443 let mut buf = [0u8; 16];
444 let len = frame::prop_set(&mut buf, 2, prop::PHY_FREQ, &[0x7e, 0x42]).unwrap();
445 assert_eq!(
446 FrameDescription(&buf[..len]).to_string(),
447 "PropSet tid=2 PROP_PHY_FREQ (2 value bytes)"
448 );
449
450 let len = frame::prop_get(&mut buf, 3, 60_000).unwrap();
451 assert_eq!(
452 FrameDescription(&buf[..len]).to_string(),
453 "PropGet tid=3 prop 60000 (0 value bytes)"
454 );
455 }
456
457 #[test]
458 fn describes_status_and_malformed_frames() {
459 let mut buf = [0u8; 8];
460 let len = frame::last_status(&mut buf, 4, Status::OK).unwrap();
461 assert_eq!(
462 FrameDescription(&buf[..len]).to_string(),
463 "PropIs tid=4 PROP_LAST_STATUS = Status::OK"
464 );
465 assert_eq!(
466 FrameDescription(&[0x80]).to_string(),
467 "malformed frame (1 bytes)"
468 );
469 }
470
471 #[test]
472 fn describes_multi_property_frames() {
473 let mut buf = [0u8; 64];
474 let len = frame::prop_multi_get(&mut buf, 1, &[prop::CAPS, prop::DEV_ADMINS]).unwrap();
475 assert_eq!(
476 FrameDescription(&buf[..len]).to_string(),
477 "PropMultiGet tid=1 (2 properties)"
478 );
479
480 let mut writer = frame::prop_are(&mut buf, 2).unwrap();
481 writer.write_entry(prop::PHY_TX_POWER, &[14]).unwrap();
482 writer.write_status_entry(Status::PROP_NOT_FOUND).unwrap();
483 let len = writer.finish();
484 assert_eq!(
485 FrameDescription(&buf[..len]).to_string(),
486 "PropAre tid=2 (2 entries, 2 value bytes)"
487 );
488
489 let malformed = [0x82, 0x17, 0x09, 0x71, 0xAA];
491 assert_eq!(
492 FrameDescription(&malformed).to_string(),
493 "PropAre tid=2 (malformed payload)"
494 );
495 }
496
497 #[test]
498 fn names_capabilities() {
499 assert_eq!(capability_name(cap::HOST_RX_QUEUE), Some("HOST_RX_QUEUE"));
500 assert_eq!(capability_name(cap::BATTERY), Some("BATTERY"));
501 assert_eq!(capability_name(cap::REPEATER), Some("REPEATER"));
502 assert_eq!(capability_name(cap::IDENT), Some("IDENT"));
503 assert_eq!(capability_name(cap::TIME), Some("TIME"));
504 assert_eq!(capability_name(cap::GNSS), Some("GNSS"));
505 assert_eq!(capability_name(60_000), None);
506 }
507
508 #[test]
509 fn names_time_and_positioning_properties() {
510 assert_eq!(property_name(prop::TIME), Some("PROP_TIME"));
511 assert_eq!(property_name(prop::TZ_OFFSET), Some("PROP_TZ_OFFSET"));
512 assert_eq!(property_name(prop::GNSS_ENABLED), Some("PROP_GNSS_ENABLED"));
513 assert_eq!(
514 property_name(prop::GNSS_LOCATION),
515 Some("PROP_GNSS_LOCATION")
516 );
517 assert_eq!(
518 property_name(prop::GNSS_ALTITUDE),
519 Some("PROP_GNSS_ALTITUDE")
520 );
521 assert_eq!(property_name(prop::GNSS_FIX), Some("PROP_GNSS_FIX"));
522 assert_eq!(
523 property_name(prop::GNSS_PRECISION),
524 Some("PROP_GNSS_PRECISION")
525 );
526 assert_eq!(
527 property_name(prop::GNSS_SATELLITES),
528 Some("PROP_GNSS_SATELLITES")
529 );
530 assert_eq!(
531 property_name(prop::GNSS_IDENT_UPDATE),
532 Some("PROP_GNSS_IDENT_UPDATE")
533 );
534 assert_eq!(
535 property_name(prop::GNSS_IDENT_PRECISION),
536 Some("PROP_GNSS_IDENT_PRECISION")
537 );
538 assert_eq!(
539 property_name(prop::GNSS_TIME_TRUST),
540 Some("PROP_GNSS_TIME_TRUST")
541 );
542 }
543
544 #[test]
545 fn names_repeater_policy_properties() {
546 assert_eq!(
547 property_name(prop::MAC_REPEATER_REGIONS),
548 Some("PROP_MAC_REPEATER_REGIONS")
549 );
550 assert_eq!(
551 property_name(prop::MAC_REPEATER_DEFAULT_REGION),
552 Some("PROP_MAC_REPEATER_DEFAULT_REGION")
553 );
554 assert_eq!(
555 property_name(prop::MAC_REPEATER_MIN_RSSI),
556 Some("PROP_MAC_REPEATER_MIN_RSSI")
557 );
558 assert_eq!(
559 property_name(prop::MAC_REPEATER_MIN_SNR),
560 Some("PROP_MAC_REPEATER_MIN_SNR")
561 );
562 assert_eq!(property_name(prop::IDENT_ROLE), Some("PROP_IDENT_ROLE"));
563 }
564
565 #[test]
566 fn names_the_bluetooth_toggle() {
567 assert_eq!(property_name(prop::BLE_ENABLED), Some("PROP_BLE_ENABLED"));
568 assert_eq!(property_type(prop::BLE_ENABLED), Some(PropertyType::Bool));
569 }
570
571 #[test]
575 fn names_the_bond_count_and_the_link() {
576 assert_eq!(
577 property_name(prop::BLE_BOND_COUNT),
578 Some("PROP_BLE_BOND_COUNT")
579 );
580 assert_eq!(property_type(prop::BLE_BOND_COUNT), Some(PropertyType::U8));
581 assert_eq!(property_name(prop::BLE_LINK), Some("PROP_BLE_LINK"));
582 assert_eq!(property_type(prop::BLE_LINK), Some(PropertyType::U8));
583 assert_eq!(property_name(prop::BLE_PAIRING), Some("PROP_BLE_PAIRING"));
584 assert_eq!(
585 property_type(prop::BLE_PAIRING),
586 Some(PropertyType::Bool),
587 "the window is a state you set, not an act you request"
588 );
589 assert_eq!(capability_name(cap::BLE), Some("BLE"));
590 assert_eq!(capability_name(cap::STATS + 1), None);
593 }
594
595 #[test]
600 fn every_named_property_is_listed_and_typed() {
601 for &key in PROPERTIES {
602 assert!(
603 property_name(key).is_some(),
604 "prop {key} is listed but unnamed"
605 );
606 assert!(
607 property_type(key).is_some(),
608 "prop {key} is listed but untyped"
609 );
610 }
611 for (index, &key) in PROPERTIES.iter().enumerate() {
613 assert!(
614 !PROPERTIES[..index].contains(&key),
615 "prop {key} is listed twice"
616 );
617 }
618 }
619
620 #[test]
621 fn property_types_follow_the_wire() {
622 assert_eq!(property_type(prop::PHY_FREQ), Some(PropertyType::U32));
623 assert_eq!(property_type(prop::PHY_TX_POWER), Some(PropertyType::I8));
624 assert_eq!(property_type(prop::DEV_NAME), Some(PropertyType::Text));
625 assert_eq!(property_type(prop::DEV_KEY), Some(PropertyType::Key32));
626 assert_eq!(property_type(prop::TZ_OFFSET), Some(PropertyType::I16));
627 assert_eq!(
628 property_type(prop::HOST_RX_QUEUE_DROPPED),
629 Some(PropertyType::U32)
630 );
631 assert_eq!(property_type(prop::BATTERY), Some(PropertyType::Bytes));
633 assert_eq!(property_type(60_000), None);
634 }
635
636 #[test]
637 fn describes_battery_snapshots_and_the_empty_form() {
638 let mut buf = [0u8; 16];
639 let len = frame::prop_is(&mut buf, 5, prop::BATTERY, &[0b101, 0x74, 0x0E, 0]).unwrap();
640 assert_eq!(
641 FrameDescription(&buf[..len]).to_string(),
642 "PropIs tid=5 PROP_BATTERY (4 value bytes)"
643 );
644
645 let len = frame::prop_is(&mut buf, 6, prop::BATTERY, &[]).unwrap();
646 assert_eq!(
647 FrameDescription(&buf[..len]).to_string(),
648 "PropIs tid=6 PROP_BATTERY (0 value bytes)"
649 );
650 }
651}