use std::collections::BTreeMap;
use super::extended::{extended_message, ExtendedMessage, ExtendedMessageSubtype};
const SUBTYPE_ZONE_NAME: ExtendedMessageSubtype = 0xff13;
extended_message!(SUBTYPE_ZONE_NAME,
pub struct ZoneNameRequest {
pub zone_index: Option<u8>,
}
pub struct ZoneNameResponse {
pub zones: BTreeMap<u8, String>,
}
{
fn impl_frame_data_len(&self) -> usize {
if self.zone_index.is_none() { 0 } else { size_of::<u8>() }
}
fn impl_frame_data<W: std::io::Write>(&self, dst: &mut W) -> Result<(), super::MessageError> {
if let Some(idx) = self.zone_index {
dst.write_all(&idx.to_be_bytes())?;
}
Ok(())
}
fn from_frame_data(message_id: u8, data: Vec<u8>) -> Result<Self, super::MessageError> {
match data[..] {
[] => { Ok(Self { message_id, zone_index: None }) },
[zone_index] => { Ok(Self { message_id, zone_index: Some(zone_index) }) },
_ => Err(MessageError::InvalidData),
}
}
}
{
fn impl_frame_data_len(&self) -> usize {
self.zones.values().fold(self.zones.len() * size_of::<u8>() * 2, |a, z| a + z.len())
}
fn impl_frame_data<W: std::io::Write>(&self, dst: &mut W) -> Result<(), super::MessageError> {
for (idx, name) in self.zones.iter() {
dst.write_all(&idx.to_be_bytes())?;
dst.write_all(&(name.len() as u8).to_be_bytes())?;
dst.write_all(name.as_bytes())?;
}
Ok(())
}
fn from_frame_data(message_id: u8, data: Vec<u8>) -> Result<Self, super::MessageError> {
let mut i: usize = 0;
let mut zones = BTreeMap::new();
while i < data.len() {
if data.len() < i + 2 || data.len() < i + 2 + data[i+1] as usize {
return Err(MessageError::InvalidData);
}
let l = data[i+1] as usize;
zones.insert(data[i], match core::str::from_utf8(&data[i+2..i+2+l]) {
Ok(valid) => valid,
Err(err) =>
unsafe { core::str::from_utf8_unchecked(&data[i+2..i+2+err.valid_up_to()]) }
}.to_string());
i += 2 + l;
}
Ok(Self { message_id, zones })
}
});
impl ZoneNameRequest {
pub fn new(zone_index: Option<u8>) -> Self {
Self {
message_id: super::next_msg_id(),
zone_index,
}
}
}
impl ZoneNameResponse {
pub fn new<K: Into<u8>, V: Into<String>, T: IntoIterator<Item = (K, V)>>(zones: T) -> Self {
Self::with_message_id(super::next_msg_id(), zones)
}
pub fn with_message_id<K: Into<u8>, V: Into<String>, T: IntoIterator<Item = (K, V)>>(
message_id: u8,
zones: T,
) -> Self {
Self {
message_id,
zones: zones
.into_iter()
.map(|(k, v)| (k.into(), v.into()))
.collect(),
}
}
pub fn by_index(&self) -> impl Iterator<Item = (u8, &str)> {
self.zones.iter().map(|(k, v)| (*k, v.as_str()))
}
pub fn by_name(&self) -> impl Iterator<Item = (u8, &str)> {
let mut s: Vec<_> = self.zones.iter().map(|(k, v)| (*k, v.as_str())).collect();
s.sort_by_key(|(_, n)| *n);
Iter::new(&self.zones, s.iter().map(|(k, _)| *k).collect::<Vec<_>>())
}
}
struct Iter<'a, I: IntoIterator<Item = u8>> {
zones: &'a BTreeMap<u8, String>,
order: <I as IntoIterator>::IntoIter,
}
impl<'a, I: IntoIterator<Item = u8>> Iter<'a, I> {
fn new(zones: &'a BTreeMap<u8, String>, order: I) -> Self {
Self {
zones,
order: order.into_iter(),
}
}
}
impl<'a, I: IntoIterator<Item = u8>> Iterator for Iter<'a, I> {
type Item = (u8, &'a str);
fn next(&mut self) -> Option<Self::Item> {
self.order
.next()
.and_then(|i| Some((i, self.zones.get(&i)?.as_str())))
}
}
#[cfg(test)]
mod tests {
use super::*;
use super::super::extended::ExtendedMessageSubtype;
use crate::conn::tests::data::*;
const ZONE_NAMES: [(u8, &str); 3] = [(0, "Living"), (2, "Bedroom"), (1, "Kitchen")];
#[test]
fn test_by_name() {
let z = ZoneNameResponse::new(ZONE_NAMES);
let mut i = z.by_name();
assert_eq!(i.next(), Some((2, "Bedroom")));
assert_eq!(i.next(), Some((1, "Kitchen")));
assert_eq!(i.next(), Some((0, "Living")));
assert_eq!(i.next(), None);
}
#[test]
fn test_zone_name_request_all() {
let orig = ZoneNameRequest::new(None);
let frame: Frame = orig.clone().try_into().expect("into frame failed");
assert_eq!(
frame.data.len(),
size_of::<super::super::extended::ExtendedMessageSubtype>()
);
let req: ZoneNameRequest = frame.try_into().expect("from frame failed");
assert_eq!(req, orig);
}
#[test]
fn test_zone_name_request_one() {
let orig = ZoneNameRequest::new(Some(7));
let frame: Frame = orig.clone().try_into().expect("into frame failed");
assert_eq!(
frame.data.len(),
size_of::<ExtendedMessageSubtype>() + size_of::<u8>()
);
let req: ZoneNameRequest = frame.try_into().expect("from frame failed");
assert_eq!(req, orig);
assert_eq!(req.zone_index, Some(7));
}
#[test]
fn test_zone_name_response_one() {
let orig = ZoneNameResponse::new([ZONE_NAMES[1]]);
let key = ZONE_NAMES[1].0;
let frame: Frame = orig.clone().try_into().expect("into frame failed");
assert_eq!(
frame.data.len(),
size_of::<ExtendedMessageSubtype>() + 2 * size_of::<u8>() + orig.zones[&key].len()
);
let resp: ZoneNameResponse = frame.try_into().expect("from frame failed");
assert_eq!(resp, orig);
assert_eq!(resp.zones.len(), 1);
assert_matches!(resp.zones.first_key_value(),
Some((k, v)) => {
assert_eq!(*k, key);
assert_eq!(*v, "Bedroom");
}
);
}
#[test]
fn test_zone_name_response_all() {
let orig = ZoneNameResponse::new(ZONE_NAMES);
let frame: Frame = orig.clone().try_into().expect("into frame failed");
assert_eq!(
frame.data.len(),
ZONE_NAMES
.iter()
.fold(size_of::<ExtendedMessageSubtype>(), |a, (_, name)| a
+ name.len()
+ 2 * size_of::<u8>())
);
let resp: ZoneNameResponse = frame.try_into().expect("from frame failed");
assert_eq!(resp, orig);
assert_eq!(resp.zones.len(), ZONE_NAMES.len());
for (idx, name) in ZONE_NAMES {
assert_matches!(resp.zones.get(&idx), Some(n) => {
assert_eq!(n, name);
})
}
}
#[test]
fn test_zone_name_req_from_data_one() {
let req: ZoneNameRequest = frame(MSG_REQ_ZONE_NAME_ONE)
.try_into()
.expect("from frame failed");
assert_matches!(req.zone_index, Some(idx) => {
assert_eq!(idx, 0);
});
let f: Frame = req.try_into().expect("into frame failed");
assert_eq!(f, frame(MSG_REQ_ZONE_NAME_ONE));
}
#[test]
fn test_zone_name_req_from_data_all() {
let req: ZoneNameRequest = frame(MSG_REQ_ZONE_NAME_ALL)
.try_into()
.expect("from frame failed");
assert_matches!(req.zone_index, None);
let f: Frame = req.try_into().expect("into frame failed");
assert_eq!(f, frame(MSG_REQ_ZONE_NAME_ALL));
}
#[test]
fn test_zone_name_resp_from_data_one() {
let resp: ZoneNameResponse = frame(MSG_RESP_ZONE_NAME_ONE)
.try_into()
.expect("from frame failed");
let mut iter = resp.by_index();
assert_matches!(iter.next(), Some((idx, name)) => {
assert_eq!(idx, 0);
assert_eq!(name, "Living")
});
assert_matches!(iter.next(), None);
drop(iter);
let f: Frame = resp.try_into().expect("into frame failed");
assert_eq!(f, frame(MSG_RESP_ZONE_NAME_ONE));
}
#[test]
fn test_zone_name_resp_from_data_all() {
let resp: ZoneNameResponse = frame(MSG_RESP_ZONE_NAME_ALL)
.try_into()
.expect("from frame failed");
let mut iter = resp.by_index();
assert_matches!(iter.next(), Some((idx, name)) => {
assert_eq!(idx, 0);
assert_eq!(name, "Living")
});
assert_matches!(iter.next(), Some((idx, name)) => {
assert_eq!(idx, 1);
assert_eq!(name, "Kitchen")
});
assert_matches!(iter.next(), Some((idx, name)) => {
assert_eq!(idx, 2);
assert_eq!(name, "Bedroom")
});
assert_matches!(iter.next(), None);
drop(iter);
let f: Frame = resp.try_into().expect("into frame failed");
assert_eq!(f, frame(MSG_RESP_ZONE_NAME_ALL));
}
#[test]
fn test_zone_name_resp_truncated_mid() {
let data = [
&MSG_RESP_ZONE_NAME_ONE[..14], &"Ƚǐving".as_bytes()[..6], &[0x29, 0x7d], ]
.concat();
let resp: ZoneNameResponse = frame(&data).try_into().expect("from frame failed");
let mut iter = resp.by_index();
assert_matches!(iter.next(), Some((idx, name)) => {
assert_eq!(idx, 0);
assert_eq!(name, "Ƚǐvi") });
assert_matches!(iter.next(), None);
}
#[test]
fn test_zone_name_resp_truncated_end() {
let data = [
&MSG_RESP_ZONE_NAME_ONE[..14], &"Livinǵ".as_bytes()[..6], &[0xce, 0x2f], ]
.concat();
let resp: ZoneNameResponse = frame(&data).try_into().expect("from frame failed");
let mut iter = resp.by_index();
assert_matches!(iter.next(), Some((idx, name)) => {
assert_eq!(idx, 0);
assert_eq!(name, "Livin") });
assert_matches!(iter.next(), None);
}
}