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 = StreamTags (deprecated, but C++ server still sends it)
30    /// Stream tags / metadata (type 6, deprecated).
31    StreamTags,
32    /// Client info (type 7).
33    ClientInfo,
34    /// Error (type 8).
35    Error,
36    /// Application-defined message (type 9+).
37    #[cfg(feature = "custom-protocol")]
38    Custom(u16),
39    /// Unrecognized message type — payload is skipped/ignored.
40    Unknown(u16),
41}
42
43impl MessageType {
44    /// Parse from a raw `u16` value. Never fails — unknown types become `Unknown(n)`.
45    pub fn from_u16(value: u16) -> Self {
46        match value {
47            0 => Self::Base,
48            1 => Self::CodecHeader,
49            2 => Self::WireChunk,
50            3 => Self::ServerSettings,
51            4 => Self::Time,
52            5 => Self::Hello,
53            6 => Self::StreamTags,
54            7 => Self::ClientInfo,
55            8 => Self::Error,
56            #[cfg(feature = "custom-protocol")]
57            9.. => Self::Custom(value),
58            #[cfg(not(feature = "custom-protocol"))]
59            _ => Self::Unknown(value),
60        }
61    }
62}
63
64impl From<MessageType> for u16 {
65    fn from(mt: MessageType) -> Self {
66        match mt {
67            MessageType::Base => 0,
68            MessageType::CodecHeader => 1,
69            MessageType::WireChunk => 2,
70            MessageType::ServerSettings => 3,
71            MessageType::Time => 4,
72            MessageType::Hello => 5,
73            MessageType::StreamTags => 6,
74            MessageType::ClientInfo => 7,
75            MessageType::Error => 8,
76            #[cfg(feature = "custom-protocol")]
77            MessageType::Custom(id) => id,
78            MessageType::Unknown(id) => id,
79        }
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86
87    #[test]
88    fn round_trip_all_message_types() {
89        let types = [
90            MessageType::Base,
91            MessageType::CodecHeader,
92            MessageType::WireChunk,
93            MessageType::ServerSettings,
94            MessageType::Time,
95            MessageType::Hello,
96            MessageType::ClientInfo,
97            MessageType::Error,
98        ];
99        for mt in types {
100            let raw: u16 = mt.into();
101            assert_eq!(MessageType::from_u16(raw), mt);
102        }
103    }
104
105    #[test]
106    fn unknown_message_type_returns_unknown() {
107        assert_eq!(MessageType::from_u16(6), MessageType::StreamTags);
108        #[cfg(not(feature = "custom-protocol"))]
109        {
110            assert_eq!(MessageType::from_u16(9), MessageType::Unknown(9));
111            assert_eq!(
112                MessageType::from_u16(u16::MAX),
113                MessageType::Unknown(u16::MAX)
114            );
115        }
116        #[cfg(feature = "custom-protocol")]
117        {
118            assert_eq!(MessageType::from_u16(9), MessageType::Custom(9));
119            assert_eq!(
120                MessageType::from_u16(u16::MAX),
121                MessageType::Custom(u16::MAX)
122            );
123        }
124    }
125}
126
127/// Custom message for application-defined protocol extensions (type 9+).
128#[cfg(feature = "custom-protocol")]
129#[derive(Debug, Clone)]
130pub struct CustomMessage {
131    /// Message type ID (9+).
132    pub type_id: u16,
133    /// Raw payload bytes.
134    pub payload: Vec<u8>,
135}
136
137#[cfg(feature = "custom-protocol")]
138impl CustomMessage {
139    /// Create a new custom message.
140    pub fn new(type_id: u16, payload: impl Into<Vec<u8>>) -> Self {
141        Self {
142            type_id,
143            payload: payload.into(),
144        }
145    }
146}