1use crate::frame::{self, Cmd, Frame, PropPayload};
8
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
11pub struct TidAllocator {
12 next: u8,
13}
14
15impl TidAllocator {
16 pub const fn new() -> Self {
18 Self { next: 1 }
19 }
20
21 pub fn allocate(&mut self) -> u8 {
23 let tid = self.next;
24 self.next = if tid >= frame::TID_MAX { 1 } else { tid + 1 };
25 tid
26 }
27}
28
29impl Default for TidAllocator {
30 fn default() -> Self {
31 Self::new()
32 }
33}
34
35#[derive(Clone, Copy, Debug, PartialEq, Eq)]
37pub enum PropertyNotificationKind {
38 Is,
39 Inserted,
40 Removed,
41 Are,
46}
47
48impl PropertyNotificationKind {
49 pub const fn from_command(command: Cmd) -> Option<Self> {
50 match command {
51 Cmd::PropIs => Some(Self::Is),
52 Cmd::PropInserted => Some(Self::Inserted),
53 Cmd::PropRemoved => Some(Self::Removed),
54 Cmd::PropAre => Some(Self::Are),
55 _ => None,
56 }
57 }
58
59 pub const fn command(self) -> Cmd {
61 match self {
62 Self::Is => Cmd::PropIs,
63 Self::Inserted => Cmd::PropInserted,
64 Self::Removed => Cmd::PropRemoved,
65 Self::Are => Cmd::PropAre,
66 }
67 }
68
69 pub const fn is_single_property(self) -> bool {
71 !matches!(self, Self::Are)
72 }
73}
74
75#[derive(Clone, Copy, Debug, PartialEq, Eq)]
77pub struct PropertyNotification<'a> {
78 pub tid: u8,
79 pub kind: PropertyNotificationKind,
80 pub key: u32,
81 pub value: &'a [u8],
82}
83
84impl<'a> PropertyNotification<'a> {
85 pub fn parse(bytes: &'a [u8]) -> Result<Self, PropertyNotificationError> {
88 let frame = Frame::parse(bytes).map_err(|_| PropertyNotificationError::MalformedFrame)?;
89 Self::from_frame(&frame)
90 }
91
92 pub fn from_frame(frame: &Frame<'a>) -> Result<Self, PropertyNotificationError> {
93 let kind = frame
94 .command()
95 .and_then(PropertyNotificationKind::from_command)
96 .filter(|kind| kind.is_single_property())
97 .ok_or(PropertyNotificationError::UnexpectedCommand)?;
98 let payload = PropPayload::parse(frame.payload)
99 .map_err(|_| PropertyNotificationError::MalformedPayload)?;
100 Ok(Self {
101 tid: frame.header.tid(),
102 kind,
103 key: payload.key,
104 value: payload.value,
105 })
106 }
107}
108
109#[derive(Clone, Copy, Debug, PartialEq, Eq)]
110pub enum PropertyNotificationError {
111 MalformedFrame,
112 UnexpectedCommand,
113 MalformedPayload,
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
121 fn transaction_ids_never_allocate_zero() {
122 let mut allocator = TidAllocator::new();
123 let ids: [u8; 10] = core::array::from_fn(|_| allocator.allocate());
124 assert_eq!(ids, [1, 2, 3, 4, 5, 6, 7, 1, 2, 3]);
125 }
126
127 #[test]
128 fn parses_property_notification() {
129 let mut bytes = [0; 16];
130 let len = frame::prop_is(&mut bytes, 3, 0x1234, &[5, 6]).unwrap();
131 assert_eq!(
132 PropertyNotification::parse(&bytes[..len]).unwrap(),
133 PropertyNotification {
134 tid: 3,
135 kind: PropertyNotificationKind::Is,
136 key: 0x1234,
137 value: &[5, 6],
138 }
139 );
140 }
141
142 #[test]
143 fn multi_property_notifications_have_no_single_property_form() {
144 let mut bytes = [0; 32];
145 let mut writer = frame::prop_are(&mut bytes, 2).unwrap();
146 writer.write_entry(0x1234, &[7]).unwrap();
147 let len = writer.finish();
148 assert_eq!(
149 PropertyNotification::parse(&bytes[..len]),
150 Err(PropertyNotificationError::UnexpectedCommand)
151 );
152 assert_eq!(
153 PropertyNotificationKind::from_command(Cmd::PropAre),
154 Some(PropertyNotificationKind::Are)
155 );
156 assert!(!PropertyNotificationKind::Are.is_single_property());
157 }
158
159 #[test]
160 fn rejects_non_property_command() {
161 let mut bytes = [0; 8];
162 let len = frame::save(&mut bytes, 1).unwrap();
163 assert_eq!(
164 PropertyNotification::parse(&bytes[..len]),
165 Err(PropertyNotificationError::UnexpectedCommand)
166 );
167 }
168}