Skip to main content

snapcast_proto/message/
base.rs

1//! Base message header — the first 26 bytes of every Snapcast protocol message.
2
3use std::io::{self, Read, Write};
4
5use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
6use thiserror::Error;
7
8use super::MessageType;
9use crate::types::Timeval;
10
11/// Errors that can occur during message parsing.
12#[derive(Debug, Error)]
13pub enum ProtoError {
14    /// An I/O error occurred during read or write.
15    #[error("I/O error: {0}")]
16    Io(#[from] io::Error),
17    /// Received an unrecognized message type value.
18    #[error("unknown message type: {0}")]
19    UnknownMessageType(u16),
20}
21
22/// Base message header (26 bytes).
23///
24/// Every Snapcast protocol message starts with this header, followed by
25/// `size` bytes of typed payload.
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct BaseMessage {
28    /// Type of the message payload.
29    pub msg_type: MessageType,
30    /// Sequence identifier.
31    pub id: u16,
32    /// ID of the message this is a response to.
33    pub refers_to: u16,
34    /// Timestamp when the message was sent.
35    pub sent: Timeval,
36    /// Timestamp when the message was received.
37    pub received: Timeval,
38    /// Size of the payload in bytes.
39    pub size: u32,
40}
41
42impl BaseMessage {
43    /// Size of the serialized header in bytes.
44    pub const HEADER_SIZE: usize = 26;
45
46    /// Deserialize a base message header from a reader.
47    pub fn read_from<R: Read>(r: &mut R) -> Result<Self, ProtoError> {
48        let raw_type = r.read_u16::<LittleEndian>()?;
49        let msg_type = MessageType::from_u16(raw_type);
50        let id = r.read_u16::<LittleEndian>()?;
51        let refers_to = r.read_u16::<LittleEndian>()?;
52        let sent = Timeval::read_from(r)?;
53        let received = Timeval::read_from(r)?;
54        let size = r.read_u32::<LittleEndian>()?;
55        Ok(Self {
56            msg_type,
57            id,
58            refers_to,
59            sent,
60            received,
61            size,
62        })
63    }
64
65    /// Serialize a base message header to a writer.
66    pub fn write_to<W: Write>(&self, w: &mut W) -> Result<(), ProtoError> {
67        w.write_u16::<LittleEndian>(self.msg_type.into())?;
68        w.write_u16::<LittleEndian>(self.id)?;
69        w.write_u16::<LittleEndian>(self.refers_to)?;
70        self.sent.write_to(w)?;
71        self.received.write_to(w)?;
72        w.write_u32::<LittleEndian>(self.size)?;
73        Ok(())
74    }
75
76    /// Serialize to a new `Vec<u8>`.
77    pub fn to_bytes(&self) -> Result<Vec<u8>, ProtoError> {
78        let mut buf = Vec::with_capacity(Self::HEADER_SIZE);
79        self.write_to(&mut buf)?;
80        Ok(buf)
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    /// Test vector: a Hello message header as the C++ code would serialize it.
89    ///
90    /// Fields (all little-endian):
91    ///   type=5 (Hello), id=1, refersTo=0,
92    ///   sent.sec=1000, sent.usec=500000,
93    ///   received.sec=0, received.usec=0,
94    ///   size=42
95    const HELLO_HEADER_BYTES: [u8; 26] = [
96        0x05, 0x00, // type = 5 (Hello)
97        0x01, 0x00, // id = 1
98        0x00, 0x00, // refersTo = 0
99        0xE8, 0x03, 0x00, 0x00, // sent.sec = 1000
100        0x20, 0xA1, 0x07, 0x00, // sent.usec = 500000
101        0x00, 0x00, 0x00, 0x00, // received.sec = 0
102        0x00, 0x00, 0x00, 0x00, // received.usec = 0
103        0x2A, 0x00, 0x00, 0x00, // size = 42
104    ];
105
106    fn hello_header() -> BaseMessage {
107        BaseMessage {
108            msg_type: MessageType::Hello,
109            id: 1,
110            refers_to: 0,
111            sent: Timeval {
112                sec: 1000,
113                usec: 500_000,
114            },
115            received: Timeval { sec: 0, usec: 0 },
116            size: 42,
117        }
118    }
119
120    #[test]
121    fn serialize_hello_header() {
122        let msg = hello_header();
123        let bytes = msg.to_bytes().unwrap();
124        assert_eq!(bytes.as_slice(), &HELLO_HEADER_BYTES);
125    }
126
127    #[test]
128    fn deserialize_hello_header() {
129        let mut cursor = io::Cursor::new(&HELLO_HEADER_BYTES);
130        let msg = BaseMessage::read_from(&mut cursor).unwrap();
131        assert_eq!(msg, hello_header());
132    }
133
134    #[test]
135    fn round_trip() {
136        let original = hello_header();
137        let bytes = original.to_bytes().unwrap();
138        let mut cursor = io::Cursor::new(&bytes);
139        let decoded = BaseMessage::read_from(&mut cursor).unwrap();
140        assert_eq!(original, decoded);
141    }
142
143    #[test]
144    fn header_size_is_26_bytes() {
145        let msg = hello_header();
146        let bytes = msg.to_bytes().unwrap();
147        assert_eq!(bytes.len(), BaseMessage::HEADER_SIZE);
148    }
149
150    #[test]
151    fn unknown_type_returns_unknown_variant() {
152        let mut bad_bytes = HELLO_HEADER_BYTES;
153        bad_bytes[0] = 0xFF;
154        bad_bytes[1] = 0xFF;
155        let mut cursor = io::Cursor::new(&bad_bytes);
156        let msg = BaseMessage::read_from(&mut cursor).unwrap();
157        #[cfg(not(feature = "custom-protocol"))]
158        assert_eq!(msg.msg_type, MessageType::Unknown(0xFFFF));
159        #[cfg(feature = "custom-protocol")]
160        assert_eq!(msg.msg_type, MessageType::Custom(0xFFFF));
161    }
162}