1use crate::frame::{Cmd, Frame, MultiEntries, ParseError, PropPayload};
14use crate::ids::prop;
15use crate::pui;
16use crate::status::Status;
17
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20pub enum Answer<'a> {
21 Value(&'a [u8]),
23 Refused(Status),
25}
26
27impl<'a> Answer<'a> {
28 pub const fn value(self) -> Option<&'a [u8]> {
30 match self {
31 Self::Value(value) => Some(value),
32 Self::Refused(_) => None,
33 }
34 }
35
36 pub const fn status(self) -> Option<Status> {
38 match self {
39 Self::Value(_) => None,
40 Self::Refused(status) => Some(status),
41 }
42 }
43
44 fn read(requested: u32, key: u32, value: &'a [u8]) -> Self {
47 if key == prop::LAST_STATUS && requested != prop::LAST_STATUS {
48 let status = pui::decode(value)
51 .map(|(code, _)| Status(code))
52 .unwrap_or(Status::FAILURE);
53 Self::Refused(status)
54 } else {
55 Self::Value(value)
56 }
57 }
58}
59
60pub fn status_of(reply: &[u8]) -> Option<Status> {
63 let parsed = Frame::parse(reply).ok()?;
64 if parsed.command() != Some(Cmd::PropIs) {
65 return None;
66 }
67 let payload = PropPayload::parse(parsed.payload).ok()?;
68 if payload.key != prop::LAST_STATUS {
69 return None;
70 }
71 Some(Status(pui::decode(payload.value).ok()?.0))
72}
73
74pub fn property(requested: u32, reply: &[u8]) -> Result<Answer<'_>, ParseError> {
76 let parsed = Frame::parse(reply)?;
77 let payload = PropPayload::parse(parsed.payload)?;
78 Ok(Answer::read(requested, payload.key, payload.value))
79}
80
81#[derive(Clone, Copy, Debug, PartialEq, Eq)]
83pub enum EntriesError {
84 NotEntries,
88 Unreadable(ParseError),
90}
91
92impl From<ParseError> for EntriesError {
93 fn from(error: ParseError) -> Self {
94 Self::Unreadable(error)
95 }
96}
97
98pub fn entries<'a>(
105 requested: &'a [u32],
106 reply: &'a [u8],
107) -> Result<impl Iterator<Item = Result<(u32, Answer<'a>), ParseError>> + 'a, EntriesError> {
108 let parsed = Frame::parse(reply)?;
109 if parsed.command() != Some(Cmd::PropAre) {
110 return Err(EntriesError::NotEntries);
111 }
112 Ok(MultiEntries::new(parsed.payload)
113 .enumerate()
114 .map(move |(position, entry)| {
115 let entry = entry?;
116 let key = requested.get(position).copied().unwrap_or(entry.key);
118 Ok((key, Answer::read(key, entry.key, entry.value)))
119 }))
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125 use crate::frame;
126
127 fn is(key: u32, value: &[u8]) -> Vec<u8> {
128 let mut buf = [0u8; 128];
129 let len = frame::prop_is(&mut buf, 3, key, value).unwrap();
130 buf[..len].to_vec()
131 }
132
133 #[test]
134 fn a_status_in_place_of_a_value_reads_as_a_refusal() {
135 let reply = is(prop::LAST_STATUS, &[Status::PROP_NOT_FOUND.0 as u8]);
136 assert_eq!(
137 property(prop::DEV_NAME, &reply).unwrap(),
138 Answer::Refused(Status::PROP_NOT_FOUND)
139 );
140 assert_eq!(
142 property(prop::LAST_STATUS, &reply).unwrap(),
143 Answer::Value(&[Status::PROP_NOT_FOUND.0 as u8])
144 );
145 assert_eq!(status_of(&reply), Some(Status::PROP_NOT_FOUND));
146 }
147
148 #[test]
149 fn each_position_is_read_against_the_key_it_answers() {
150 let mut buf = [0u8; 128];
151 let mut writer = frame::prop_are(&mut buf, 3).unwrap();
152 writer.write_entry(prop::DEV_NAME, b"Ridge").unwrap();
153 writer.write_status_entry(Status::PROP_NOT_FOUND).unwrap();
154 let len = writer.finish();
155 let reply = &buf[..len];
156
157 let requested = [prop::DEV_NAME, prop::HOST_KEY];
158 let read: Vec<_> = entries(&requested, reply)
159 .unwrap()
160 .map(Result::unwrap)
161 .collect();
162 assert_eq!(
163 read,
164 vec![
165 (prop::DEV_NAME, Answer::Value(b"Ridge".as_slice())),
166 (prop::HOST_KEY, Answer::Refused(Status::PROP_NOT_FOUND)),
167 ]
168 );
169 }
170
171 #[test]
172 fn a_device_that_declined_the_command_is_not_an_entry_list() {
173 let reply = is(prop::LAST_STATUS, &[Status::UNIMPLEMENTED.0 as u8]);
174 assert!(entries(&[prop::DEV_NAME], &reply).is_err());
175 assert_eq!(status_of(&reply), Some(Status::UNIMPLEMENTED));
176 }
177}