Skip to main content

snapcast_proto/message/
mod.rs

1//! Protocol message types.
2
3pub mod base;
4pub mod client_info;
5pub mod codec_header;
6pub mod error;
7pub mod factory;
8pub mod hello;
9pub mod server_settings;
10pub mod time;
11pub mod wire;
12pub mod wire_chunk;
13
14/// Message type identifiers matching the C++ `message_type` enum.
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum MessageType {
17    /// Base message (type 0), header only.
18    Base,
19    /// Codec header (type 1).
20    CodecHeader,
21    /// Encoded audio chunk (type 2).
22    WireChunk,
23    /// Server settings (type 3).
24    ServerSettings,
25    /// Time sync (type 4).
26    Time,
27    /// Client hello (type 5).
28    Hello,
29    // 6 is unused (was StreamTags)
30    /// Client info (type 7).
31    ClientInfo,
32    /// Error (type 8).
33    Error,
34    /// Application-defined message (type 9+).
35    #[cfg(feature = "custom-protocol")]
36    Custom(u16),
37}
38
39impl MessageType {
40    /// Parse from a raw `u16` value.
41    pub fn from_u16(value: u16) -> Option<Self> {
42        match value {
43            0 => Some(Self::Base),
44            1 => Some(Self::CodecHeader),
45            2 => Some(Self::WireChunk),
46            3 => Some(Self::ServerSettings),
47            4 => Some(Self::Time),
48            5 => Some(Self::Hello),
49            7 => Some(Self::ClientInfo),
50            8 => Some(Self::Error),
51            #[cfg(feature = "custom-protocol")]
52            9.. => Some(Self::Custom(value)),
53            _ => None,
54        }
55    }
56}
57
58impl From<MessageType> for u16 {
59    fn from(mt: MessageType) -> Self {
60        match mt {
61            MessageType::Base => 0,
62            MessageType::CodecHeader => 1,
63            MessageType::WireChunk => 2,
64            MessageType::ServerSettings => 3,
65            MessageType::Time => 4,
66            MessageType::Hello => 5,
67            MessageType::ClientInfo => 7,
68            MessageType::Error => 8,
69            #[cfg(feature = "custom-protocol")]
70            MessageType::Custom(id) => id,
71        }
72    }
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78
79    #[test]
80    fn round_trip_all_message_types() {
81        let types = [
82            MessageType::Base,
83            MessageType::CodecHeader,
84            MessageType::WireChunk,
85            MessageType::ServerSettings,
86            MessageType::Time,
87            MessageType::Hello,
88            MessageType::ClientInfo,
89            MessageType::Error,
90        ];
91        for mt in types {
92            let raw: u16 = mt.into();
93            assert_eq!(MessageType::from_u16(raw), Some(mt));
94        }
95    }
96
97    #[test]
98    fn unknown_message_type_returns_none() {
99        assert_eq!(MessageType::from_u16(6), None);
100        #[cfg(not(feature = "custom-protocol"))]
101        {
102            assert_eq!(MessageType::from_u16(9), None);
103            assert_eq!(MessageType::from_u16(u16::MAX), None);
104        }
105        #[cfg(feature = "custom-protocol")]
106        {
107            assert_eq!(MessageType::from_u16(9), Some(MessageType::Custom(9)));
108            assert_eq!(
109                MessageType::from_u16(u16::MAX),
110                Some(MessageType::Custom(u16::MAX))
111            );
112        }
113    }
114}
115
116/// Custom message for application-defined protocol extensions (type 9+).
117#[cfg(feature = "custom-protocol")]
118#[derive(Debug, Clone)]
119pub struct CustomMessage {
120    /// Message type ID (9+).
121    pub type_id: u16,
122    /// Raw payload bytes.
123    pub payload: Vec<u8>,
124}
125
126#[cfg(feature = "custom-protocol")]
127impl CustomMessage {
128    /// Create a new custom message.
129    pub fn new(type_id: u16, payload: impl Into<Vec<u8>>) -> Self {
130        Self {
131            type_id,
132            payload: payload.into(),
133        }
134    }
135}