mumcord_protocol/
query.rs1use prost::Message;
16
17use crate::udp_proto;
18use crate::version::MumbleVersion;
19
20const PROTO_PING: u8 = 1;
22pub const LEGACY_QUERY_LEN: usize = 12;
24pub const LEGACY_ANSWER_LEN: usize = 24;
26
27pub fn encode_query(echo: u64) -> Vec<u8> {
30 let message = udp_proto::Ping {
31 timestamp: echo,
32 request_extended_information: true,
33 ..Default::default()
34 };
35 let mut out = vec![PROTO_PING];
36 out.extend_from_slice(&message.encode_to_vec());
37 out
38}
39
40pub fn encode_legacy_query(echo: u64) -> Vec<u8> {
43 let mut out = Vec::with_capacity(LEGACY_QUERY_LEN);
44 out.extend_from_slice(&[0, 0, 0, 0]);
45 out.extend_from_slice(&echo.to_be_bytes());
46 out
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub struct QueryAnswer {
52 pub version: MumbleVersion,
53 pub users: u32,
54 pub max_users: u32,
55 pub echo: u64,
56}
57
58pub fn decode_answer(data: &[u8]) -> Option<QueryAnswer> {
61 if data.len() == LEGACY_ANSWER_LEN {
62 let word = |i: usize| u32::from_be_bytes([data[i], data[i + 1], data[i + 2], data[i + 3]]);
63 let version = MumbleVersion::from_v1(word(0));
65 if version == MumbleVersion::new(0, 0, 0) {
66 return None;
67 }
68 return Some(QueryAnswer {
69 version,
70 users: word(12),
71 max_users: word(16),
72 echo: u64::from_be_bytes(data[4..12].try_into().ok()?),
73 });
74 }
75
76 let (&header, body) = data.split_first()?;
77 if header != PROTO_PING {
78 return None;
79 }
80 let message = udp_proto::Ping::decode(body).ok()?;
81 if message.server_version_v2 == 0 {
83 return None;
84 }
85 Some(QueryAnswer {
86 version: MumbleVersion::from_v2(message.server_version_v2),
87 users: message.user_count,
88 max_users: message.max_user_count,
89 echo: message.timestamp,
90 })
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96
97 fn legacy_answer(version: MumbleVersion, users: u32, max: u32, echo: u64) -> Vec<u8> {
98 let mut data = Vec::new();
99 data.extend_from_slice(&version.to_v1().to_be_bytes());
100 data.extend_from_slice(&echo.to_be_bytes());
101 for value in [users, max, 72_000] {
102 data.extend_from_slice(&value.to_be_bytes());
103 }
104 data
105 }
106
107 #[test]
108 fn legacy_query_is_the_extended_ping() {
109 let query = encode_legacy_query(0x1122_3344_5566_7788);
110 assert_eq!(query.len(), LEGACY_QUERY_LEN);
111 assert_eq!(&query[..4], &[0, 0, 0, 0]);
112 assert_eq!(
113 &query[4..],
114 &[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]
115 );
116 }
117
118 #[test]
119 fn protobuf_query_round_trips() {
120 let query = encode_query(0xDEAD_BEEF);
121 assert_eq!(query[0], PROTO_PING);
122 assert_eq!(
123 decode_answer(&query),
124 None,
125 "the query itself is not an answer"
126 );
127 let mut answer = udp_proto::Ping {
129 timestamp: 0xDEAD_BEEF,
130 request_extended_information: true,
131 server_version_v2: MumbleVersion::new(1, 5, 735).to_v2(),
132 user_count: 7,
133 max_user_count: 50,
134 max_bandwidth_per_user: 72_000,
135 };
136 answer.request_extended_information = false;
137 let mut data = vec![PROTO_PING];
138 data.extend_from_slice(&answer.encode_to_vec());
139 assert_eq!(
140 decode_answer(&data),
141 Some(QueryAnswer {
142 version: MumbleVersion::new(1, 5, 735),
143 users: 7,
144 max_users: 50,
145 echo: 0xDEAD_BEEF
146 })
147 );
148 }
149
150 #[test]
151 fn reads_the_legacy_answer() {
152 let v = MumbleVersion::new(1, 4, 230);
153 assert_eq!(
154 decode_answer(&legacy_answer(v, 3, 100, 0xDEAD_BEEF)),
155 Some(QueryAnswer {
156 version: v,
157 users: 3,
158 max_users: 100,
159 echo: 0xDEAD_BEEF
160 })
161 );
162 }
163
164 #[test]
165 fn ignores_answers_that_are_not_figures() {
166 assert_eq!(decode_answer(&[0x20, 0x05]), None);
167 assert_eq!(decode_answer(&[]), None);
168 assert_eq!(decode_answer(&[0; LEGACY_ANSWER_LEN]), None);
169 }
170}