1use crate::coding::*;
5
6use super::{Message, Parameters, Version};
7
8const PARAM_PROBE: u64 = 0x1;
10const PARAM_PATH: u64 = 0x2;
12const PARAM_ROLE: u64 = 0x3;
14const PARAM_COST: u64 = 0x4;
16
17pub const DEFAULT_COST: u64 = 1;
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
31pub enum ProbeLevel {
32 #[default]
34 None,
35 Report,
37 Increase,
39}
40
41impl ProbeLevel {
42 fn from_code(code: u64) -> Self {
44 match code {
45 0 => Self::None,
46 1 => Self::Report,
47 _ => Self::Increase,
48 }
49 }
50
51 fn to_code(self) -> u64 {
53 match self {
54 Self::None => 0,
55 Self::Report => 1,
56 Self::Increase => 2,
57 }
58 }
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73#[non_exhaustive]
74pub enum Role {
75 Publisher,
77 Subscriber,
79}
80
81impl Role {
82 fn from_code(code: u64) -> Option<Self> {
87 match code {
88 1 => Some(Role::Publisher),
89 2 => Some(Role::Subscriber),
90 _ => None,
91 }
92 }
93
94 fn to_code(self) -> u64 {
96 match self {
97 Role::Publisher => 1,
98 Role::Subscriber => 2,
99 }
100 }
101
102 pub(crate) fn from_origins(publishes: bool, consumes: bool) -> Option<Self> {
107 match (publishes, consumes) {
108 (true, false) => Some(Role::Publisher),
109 (false, true) => Some(Role::Subscriber),
110 _ => None,
111 }
112 }
113
114 pub fn as_str(self) -> &'static str {
116 match self {
117 Role::Publisher => "publisher",
118 Role::Subscriber => "subscriber",
119 }
120 }
121}
122
123impl std::fmt::Display for Role {
124 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
125 f.write_str(self.as_str())
126 }
127}
128
129#[derive(Debug, Clone, Default, PartialEq, Eq)]
135pub struct Setup {
136 pub probe: ProbeLevel,
138 pub path: Option<String>,
144 pub role: Option<Role>,
148 pub cost: Option<u64>,
153}
154
155impl Message for Setup {
156 fn decode_msg<R: bytes::Buf>(r: &mut R, version: Version) -> Result<Self, DecodeError> {
157 if !version.has_setup_stream() {
158 return Err(DecodeError::Version);
159 }
160
161 let params = Parameters::decode(r, version)?;
162 let probe = params
163 .get_varint(PARAM_PROBE)?
164 .map(ProbeLevel::from_code)
165 .unwrap_or_default();
166 let path = match params.get_bytes(PARAM_PATH) {
167 Some(bytes) => Some(
168 std::str::from_utf8(bytes)
169 .map_err(|_| DecodeError::InvalidValue)?
170 .to_string(),
171 ),
172 None => None,
173 };
174 let role = params.get_varint(PARAM_ROLE)?.and_then(Role::from_code);
175 let cost = params.get_varint(PARAM_COST)?;
176
177 Ok(Self {
178 probe,
179 path,
180 role,
181 cost,
182 })
183 }
184
185 fn encode_msg<W: bytes::BufMut>(&self, w: &mut W, version: Version) -> Result<(), EncodeError> {
186 if !version.has_setup_stream() {
187 return Err(EncodeError::Version);
188 }
189
190 let mut params = Parameters::default();
191 if self.probe != ProbeLevel::None {
193 params.set_varint(PARAM_PROBE, self.probe.to_code());
194 }
195 if let Some(path) = &self.path {
196 params.set_bytes(PARAM_PATH, path.as_bytes().to_vec());
197 }
198 if let Some(role) = self.role {
201 params.set_varint(PARAM_ROLE, role.to_code());
202 }
203 if let Some(cost) = self.cost {
204 params.set_varint(PARAM_COST, cost);
205 }
206
207 params.encode(w, version)
208 }
209}
210
211#[derive(Clone, Default)]
217pub(crate) struct PeerSetup(kio::Shared<Option<Setup>>);
218
219impl PeerSetup {
220 pub fn set(&self, setup: Setup) {
222 *self.0.lock() = Some(setup);
223 }
224
225 pub async fn probe_level(&self) -> ProbeLevel {
227 self.wait(|setup| setup.probe).await
228 }
229
230 pub async fn cost(&self) -> Option<u64> {
233 self.wait(|setup| setup.cost).await
234 }
235
236 async fn wait<T>(&self, f: impl FnOnce(&Setup) -> T) -> T {
242 let slot = self
243 .0
244 .wait(|setup| {
245 if setup.is_some() {
246 std::task::Poll::Ready(())
247 } else {
248 std::task::Poll::Pending
249 }
250 })
251 .await;
252 f(slot.as_ref().expect("waited for Some"))
253 }
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259
260 fn round_trip(msg: &Setup) -> Setup {
261 let mut buf = bytes::BytesMut::new();
262 msg.encode(&mut buf, Version::Lite05).unwrap();
263 let mut slice = &buf[..];
264 let got = Setup::decode(&mut slice, Version::Lite05).unwrap();
265 assert!(bytes::Buf::remaining(&slice) == 0, "trailing bytes after decode");
266 got
267 }
268
269 #[test]
270 fn empty_round_trip() {
271 let msg = Setup::default();
272 assert_eq!(round_trip(&msg), msg);
273 }
274
275 #[test]
276 fn probe_levels_round_trip() {
277 for probe in [ProbeLevel::None, ProbeLevel::Report, ProbeLevel::Increase] {
278 let msg = Setup {
279 probe,
280 ..Default::default()
281 };
282 assert_eq!(round_trip(&msg), msg);
283 }
284 }
285
286 #[test]
287 fn cost_round_trip() {
288 for cost in [None, Some(0), Some(1), Some(7)] {
291 let msg = Setup {
292 cost,
293 ..Default::default()
294 };
295 assert_eq!(round_trip(&msg), msg);
296 }
297 }
298
299 #[test]
300 fn path_round_trip() {
301 let msg = Setup {
302 probe: ProbeLevel::Report,
303 path: Some("/room/123".to_string()),
304 role: None,
305 cost: None,
306 };
307 assert_eq!(round_trip(&msg), msg);
308 }
309
310 #[test]
311 fn empty_path_round_trips() {
312 let msg = Setup {
315 path: Some(String::new()),
316 ..Default::default()
317 };
318 assert_eq!(round_trip(&msg), msg);
319 }
320
321 #[test]
322 fn roles_round_trip() {
323 for role in [Some(Role::Publisher), Some(Role::Subscriber), None] {
324 let msg = Setup {
325 path: Some("/room/123".to_string()),
326 role,
327 ..Default::default()
328 };
329 assert_eq!(round_trip(&msg), msg);
330 }
331 }
332
333 #[test]
334 fn unknown_probe_level_saturates_to_increase() {
335 let mut params = Parameters::default();
338 params.set_varint(PARAM_PROBE, 99);
339 let mut body = Vec::new();
340 params.encode(&mut body, Version::Lite05).unwrap();
341
342 let mut buf = bytes::BytesMut::new();
343 body.len().encode(&mut buf, Version::Lite05).unwrap();
344 buf.extend_from_slice(&body);
345
346 let mut slice = &buf[..];
347 let got = Setup::decode(&mut slice, Version::Lite05).unwrap();
348 assert_eq!(got.probe, ProbeLevel::Increase);
349 }
350
351 #[test]
352 fn role_wire_codes() {
353 for (role, code) in [(Role::Publisher, 1u64), (Role::Subscriber, 2)] {
356 assert_eq!(role.to_code(), code);
357 assert_eq!(Role::from_code(code), Some(role));
358 }
359 }
360
361 #[test]
362 fn unknown_role_decodes_as_bidirectional() {
363 for code in [0u64, 9, 250] {
367 let mut params = Parameters::default();
368 params.set_varint(PARAM_ROLE, code);
369 let mut body = Vec::new();
370 params.encode(&mut body, Version::Lite05).unwrap();
371
372 let mut buf = bytes::BytesMut::new();
373 body.len().encode(&mut buf, Version::Lite05).unwrap();
374 buf.extend_from_slice(&body);
375
376 let mut slice = &buf[..];
377 let got = Setup::decode(&mut slice, Version::Lite05).unwrap();
378 assert_eq!(got.role, None, "role code {code} should decode as bidirectional");
379 }
380 }
381
382 #[test]
383 fn rejects_before_lite05() {
384 let msg = Setup::default();
385 let mut buf = bytes::BytesMut::new();
386 assert!(matches!(
387 msg.encode(&mut buf, Version::Lite04),
388 Err(EncodeError::Version)
389 ));
390 }
391
392 #[test]
393 fn ignores_unknown_parameters() {
394 let mut params = Parameters::default();
396 params.set_bytes(PARAM_PATH, b"/foo".to_vec());
397 params.set_bytes(0xbeef, b"whatever".to_vec());
398
399 let mut body = Vec::new();
400 params.encode(&mut body, Version::Lite05).unwrap();
401
402 let mut buf = bytes::BytesMut::new();
404 body.len().encode(&mut buf, Version::Lite05).unwrap();
405 buf.extend_from_slice(&body);
406
407 let mut slice = &buf[..];
408 let got = Setup::decode(&mut slice, Version::Lite05).unwrap();
409 assert_eq!(got.path.as_deref(), Some("/foo"));
410 }
411}