snapcast_proto/message/
base.rs1use std::io::{self, Read, Write};
4
5use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
6use thiserror::Error;
7
8use super::MessageType;
9use crate::types::Timeval;
10
11#[derive(Debug, Error)]
13pub enum ProtoError {
14 #[error("I/O error: {0}")]
16 Io(#[from] io::Error),
17 #[error("unknown message type: {0}")]
19 UnknownMessageType(u16),
20}
21
22#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct BaseMessage {
28 pub msg_type: MessageType,
30 pub id: u16,
32 pub refers_to: u16,
34 pub sent: Timeval,
36 pub received: Timeval,
38 pub size: u32,
40}
41
42impl BaseMessage {
43 pub const HEADER_SIZE: usize = 26;
45
46 pub fn read_from<R: Read>(r: &mut R) -> Result<Self, ProtoError> {
48 let raw_type = r.read_u16::<LittleEndian>()?;
49 let msg_type =
50 MessageType::from_u16(raw_type).ok_or(ProtoError::UnknownMessageType(raw_type))?;
51 let id = r.read_u16::<LittleEndian>()?;
52 let refers_to = r.read_u16::<LittleEndian>()?;
53 let sent = Timeval::read_from(r)?;
54 let received = Timeval::read_from(r)?;
55 let size = r.read_u32::<LittleEndian>()?;
56 Ok(Self {
57 msg_type,
58 id,
59 refers_to,
60 sent,
61 received,
62 size,
63 })
64 }
65
66 pub fn write_to<W: Write>(&self, w: &mut W) -> Result<(), ProtoError> {
68 w.write_u16::<LittleEndian>(self.msg_type.into())?;
69 w.write_u16::<LittleEndian>(self.id)?;
70 w.write_u16::<LittleEndian>(self.refers_to)?;
71 self.sent.write_to(w)?;
72 self.received.write_to(w)?;
73 w.write_u32::<LittleEndian>(self.size)?;
74 Ok(())
75 }
76
77 pub fn to_bytes(&self) -> Result<Vec<u8>, ProtoError> {
79 let mut buf = Vec::with_capacity(Self::HEADER_SIZE);
80 self.write_to(&mut buf)?;
81 Ok(buf)
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88
89 const HELLO_HEADER_BYTES: [u8; 26] = [
97 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0xE8, 0x03, 0x00, 0x00, 0x20, 0xA1, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x00, 0x00, ];
106
107 fn hello_header() -> BaseMessage {
108 BaseMessage {
109 msg_type: MessageType::Hello,
110 id: 1,
111 refers_to: 0,
112 sent: Timeval {
113 sec: 1000,
114 usec: 500_000,
115 },
116 received: Timeval { sec: 0, usec: 0 },
117 size: 42,
118 }
119 }
120
121 #[test]
122 fn serialize_hello_header() {
123 let msg = hello_header();
124 let bytes = msg.to_bytes().unwrap();
125 assert_eq!(bytes.as_slice(), &HELLO_HEADER_BYTES);
126 }
127
128 #[test]
129 fn deserialize_hello_header() {
130 let mut cursor = io::Cursor::new(&HELLO_HEADER_BYTES);
131 let msg = BaseMessage::read_from(&mut cursor).unwrap();
132 assert_eq!(msg, hello_header());
133 }
134
135 #[test]
136 fn round_trip() {
137 let original = hello_header();
138 let bytes = original.to_bytes().unwrap();
139 let mut cursor = io::Cursor::new(&bytes);
140 let decoded = BaseMessage::read_from(&mut cursor).unwrap();
141 assert_eq!(original, decoded);
142 }
143
144 #[test]
145 fn header_size_is_26_bytes() {
146 let msg = hello_header();
147 let bytes = msg.to_bytes().unwrap();
148 assert_eq!(bytes.len(), BaseMessage::HEADER_SIZE);
149 }
150
151 #[test]
152 fn unknown_type_returns_error() {
153 let mut bad_bytes = HELLO_HEADER_BYTES;
154 bad_bytes[0] = 0xFF;
155 bad_bytes[1] = 0xFF;
156 let mut cursor = io::Cursor::new(&bad_bytes);
157 #[cfg(not(feature = "custom-protocol"))]
158 {
159 let err = BaseMessage::read_from(&mut cursor).unwrap_err();
160 assert!(matches!(err, ProtoError::UnknownMessageType(0xFFFF)));
161 }
162 #[cfg(feature = "custom-protocol")]
163 {
164 let msg = BaseMessage::read_from(&mut cursor).unwrap();
165 assert_eq!(msg.msg_type, MessageType::Custom(0xFFFF));
166 }
167 }
168}