Skip to main content

moq_net/
setup.rs

1//! The SETUP exchange, and the credential a peer may present in it.
2//!
3//! Only [`Token`] is public; the SETUP messages themselves are wire internals.
4
5use bytes::Bytes;
6
7use crate::{
8	Version,
9	coding::{self, Decode, DecodeError, Encode, EncodeError, Sizer},
10	ietf, lite,
11};
12
13// SETUP only carries negotiation parameters. Bound it independently of control messages.
14pub(crate) const MAX_SETUP_SIZE: usize = 64 * 1024;
15
16const CLIENT_SETUP: u8 = 0x20;
17const SERVER_SETUP: u8 = 0x21;
18
19/// Draft-17 unified SETUP message type (varint 0x2F00)
20pub(crate) const SETUP_V17: u64 = 0x2F00;
21
22/// A credential a moq-transport peer presented in its SETUP's `AUTHORIZATION TOKEN` option.
23///
24/// The transport never reads the bytes; verifying them is the application's job.
25#[derive(Clone, PartialEq, Eq)]
26pub struct Token {
27	/// The wire Token Type, naming how [`value`](Self::value) is encoded.
28	pub kind: u64,
29	/// The token itself.
30	pub value: Vec<u8>,
31}
32
33impl std::fmt::Debug for Token {
34	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35		write!(f, "Token(kind={}, <{} bytes>)", self.kind, self.value.len())
36	}
37}
38
39impl Token {
40	/// Token Type 0: a format the endpoints agreed on out of band, such as a JWT.
41	pub const OUT_OF_BAND: u64 = 0x0;
42	/// Token Type 1: a Common Access Token (draft-ietf-moq-c4m).
43	pub const CAT: u64 = 0x1;
44}
45
46/// Draft-17+ unified SETUP message, with the same encoding for both client and server.
47#[derive(Clone)]
48pub(crate) struct Setup {
49	pub parameters: Bytes,
50}
51
52impl std::fmt::Debug for Setup {
53	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54		// Opaque parameters can contain credentials, even before we recognize an option.
55		f.debug_struct("Setup")
56			.field("parameters_len", &self.parameters.len())
57			.finish()
58	}
59}
60
61impl Setup {
62	fn check_version(v: Version) {
63		match v {
64			Version::Ietf(ietf::Version::Draft14 | ietf::Version::Draft15 | ietf::Version::Draft16)
65			| Version::Lite(_) => unreachable!("Setup is draft-17+ only"),
66			_ => {}
67		}
68	}
69}
70
71impl Encode<Version> for Setup {
72	fn control(&self, _: Version) -> Option<crate::stats::Control> {
73		Some(crate::stats::Control::Session)
74	}
75
76	fn encode<W: bytes::BufMut>(&self, w: &mut W, v: Version) -> Result<(), EncodeError> {
77		Self::check_version(v);
78		SETUP_V17.encode(w, v)?;
79		u16::try_from(self.parameters.len())
80			.map_err(|_| EncodeError::TooLarge)?
81			.encode(w, v)?;
82		if w.remaining_mut() < self.parameters.len() {
83			return Err(EncodeError::Short);
84		}
85		w.put_slice(&self.parameters);
86		Ok(())
87	}
88}
89
90impl Decode<Version> for Setup {
91	fn control(&self, _: Version) -> Option<crate::stats::Control> {
92		Some(crate::stats::Control::Session)
93	}
94
95	fn decode<R: bytes::Buf>(r: &mut R, v: Version) -> Result<Self, DecodeError> {
96		Self::check_version(v);
97		let kind = u64::decode(r, v)?;
98		if kind != SETUP_V17 {
99			return Err(DecodeError::InvalidValue);
100		}
101		// The fixed u16 length is already below MAX_SETUP_SIZE.
102		let size = u16::decode(r, v)? as usize;
103		if r.remaining() < size {
104			return Err(DecodeError::Short);
105		}
106		let msg = r.copy_to_bytes(size);
107		Ok(Self { parameters: msg })
108	}
109}
110
111#[derive(Clone, Copy, Debug, PartialEq, Eq)]
112enum SetupVersion {
113	Draft14,
114	Draft15Plus,
115	/// Draft17+ uses ALPN-only negotiation with no legacy SETUP message.
116	Modern,
117	LiteLegacy,
118	Unsupported,
119}
120
121impl SetupVersion {
122	fn from_version(v: Version) -> Self {
123		match v {
124			Version::Ietf(ietf::Version::Draft14) => Self::Draft14,
125			Version::Ietf(ietf::Version::Draft15) | Version::Ietf(ietf::Version::Draft16) => Self::Draft15Plus,
126			Version::Ietf(ietf::Version::Draft17)
127			| Version::Ietf(ietf::Version::Draft18)
128			| Version::Ietf(ietf::Version::Draft19)
129			| Version::Ietf(ietf::Version::Draft20)
130			| Version::Ietf(ietf::Version::Draft21)
131			| Version::Ietf(ietf::Version::Draft22) => Self::Modern,
132			Version::Lite(lite::Version::Lite01) | Version::Lite(lite::Version::Lite02) => Self::LiteLegacy,
133			Version::Lite(_) => Self::Unsupported,
134		}
135	}
136}
137
138/// A version-agnostic setup message sent by the client.
139#[derive(Clone)]
140pub(crate) struct Client {
141	/// The list of supported versions in preferred order.
142	pub versions: coding::Versions,
143
144	/// Parameters, unparsed because the IETF draft changed the encoding.
145	pub parameters: Bytes,
146}
147
148impl std::fmt::Debug for Client {
149	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150		f.debug_struct("Client")
151			.field("versions", &self.versions)
152			.field("parameters_len", &self.parameters.len())
153			.finish()
154	}
155}
156
157impl Client {
158	fn encode_inner<W: bytes::BufMut>(&self, w: &mut W, v: Version) -> Result<(), EncodeError> {
159		match SetupVersion::from_version(v) {
160			SetupVersion::Draft15Plus => {
161				// Draft15+: no versions list, parameters only.
162			}
163			SetupVersion::Draft14 | SetupVersion::LiteLegacy => self.versions.encode(w, v)?,
164			SetupVersion::Modern | SetupVersion::Unsupported => return Err(EncodeError::Version),
165		};
166		if w.remaining_mut() < self.parameters.len() {
167			return Err(EncodeError::Short);
168		}
169		w.put_slice(&self.parameters);
170		Ok(())
171	}
172}
173
174impl Decode<Version> for Client {
175	fn control(&self, _: Version) -> Option<crate::stats::Control> {
176		Some(crate::stats::Control::Session)
177	}
178
179	/// Decode a client setup message (draft-14 through draft-16 only).
180	fn decode<R: bytes::Buf>(r: &mut R, v: Version) -> Result<Self, DecodeError> {
181		let kind = u8::decode(r, v)?;
182		if kind != CLIENT_SETUP {
183			return Err(DecodeError::InvalidValue);
184		}
185
186		let size = match SetupVersion::from_version(v) {
187			SetupVersion::Draft14 | SetupVersion::Draft15Plus => u16::decode(r, v)? as usize,
188			SetupVersion::LiteLegacy => usize::decode(r, v)?,
189			SetupVersion::Modern | SetupVersion::Unsupported => return Err(DecodeError::Version),
190		};
191
192		if size > MAX_SETUP_SIZE {
193			return Err(DecodeError::MessageTooLarge {
194				size,
195				max: MAX_SETUP_SIZE,
196			});
197		}
198
199		if r.remaining() < size {
200			return Err(DecodeError::Short);
201		}
202
203		let mut msg = r.copy_to_bytes(size);
204
205		let versions = match SetupVersion::from_version(v) {
206			SetupVersion::Draft15Plus => {
207				// Draft15+: no versions list, parameters only.
208				coding::Versions::from([v.into()])
209			}
210			SetupVersion::Draft14 | SetupVersion::LiteLegacy => {
211				coding::Versions::decode(&mut msg, v).map_err(DecodeError::complete)?
212			}
213			SetupVersion::Modern | SetupVersion::Unsupported => return Err(DecodeError::Version),
214		};
215
216		Ok(Self {
217			versions,
218			parameters: msg,
219		})
220	}
221}
222
223impl Encode<Version> for Client {
224	fn control(&self, _: Version) -> Option<crate::stats::Control> {
225		Some(crate::stats::Control::Session)
226	}
227
228	/// Encode a client setup message (draft-14 through draft-16 only).
229	fn encode<W: bytes::BufMut>(&self, w: &mut W, v: Version) -> Result<(), EncodeError> {
230		CLIENT_SETUP.encode(w, v)?;
231
232		let mut sizer = Sizer::default();
233		self.encode_inner(&mut sizer, v)?;
234		let size = sizer.size;
235		if size > MAX_SETUP_SIZE {
236			return Err(EncodeError::TooLarge);
237		}
238
239		match SetupVersion::from_version(v) {
240			SetupVersion::Draft14 | SetupVersion::Draft15Plus => {
241				u16::try_from(size).map_err(|_| EncodeError::TooLarge)?.encode(w, v)?;
242			}
243			SetupVersion::LiteLegacy => (size as u64).encode(w, v)?,
244			SetupVersion::Modern | SetupVersion::Unsupported => return Err(EncodeError::Version),
245		}
246		self.encode_inner(w, v)
247	}
248}
249
250/// Sent by the server in response to a client setup.
251#[derive(Clone)]
252pub(crate) struct Server {
253	/// The list of supported versions in preferred order.
254	pub version: coding::Version,
255
256	/// Supported extensions.
257	pub parameters: Bytes,
258}
259
260impl std::fmt::Debug for Server {
261	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
262		f.debug_struct("Server")
263			.field("version", &self.version)
264			.field("parameters_len", &self.parameters.len())
265			.finish()
266	}
267}
268
269impl Server {
270	fn encode_inner<W: bytes::BufMut>(&self, w: &mut W, v: Version) -> Result<(), EncodeError> {
271		match SetupVersion::from_version(v) {
272			SetupVersion::Draft15Plus => {
273				// Draft15+: No version field, parameters only.
274			}
275			SetupVersion::Draft14 | SetupVersion::LiteLegacy => self.version.encode(w, v)?,
276			SetupVersion::Modern | SetupVersion::Unsupported => return Err(EncodeError::Version),
277		};
278		if w.remaining_mut() < self.parameters.len() {
279			return Err(EncodeError::Short);
280		}
281		w.put_slice(&self.parameters);
282		Ok(())
283	}
284}
285
286impl Encode<Version> for Server {
287	fn control(&self, _: Version) -> Option<crate::stats::Control> {
288		Some(crate::stats::Control::Session)
289	}
290
291	/// Encode a server setup message (draft-14 through draft-16 only).
292	fn encode<W: bytes::BufMut>(&self, w: &mut W, v: Version) -> Result<(), EncodeError> {
293		SERVER_SETUP.encode(w, v)?;
294
295		let mut sizer = Sizer::default();
296		self.encode_inner(&mut sizer, v)?;
297		let size = sizer.size;
298		if size > MAX_SETUP_SIZE {
299			return Err(EncodeError::TooLarge);
300		}
301
302		match SetupVersion::from_version(v) {
303			SetupVersion::Draft14 | SetupVersion::Draft15Plus => {
304				u16::try_from(size).map_err(|_| EncodeError::TooLarge)?.encode(w, v)?;
305			}
306			SetupVersion::LiteLegacy => (size as u64).encode(w, v)?,
307			SetupVersion::Modern | SetupVersion::Unsupported => return Err(EncodeError::Version),
308		}
309
310		self.encode_inner(w, v)
311	}
312}
313
314impl Decode<Version> for Server {
315	fn control(&self, _: Version) -> Option<crate::stats::Control> {
316		Some(crate::stats::Control::Session)
317	}
318
319	/// Decode a server setup message (draft-14 through draft-16 only).
320	fn decode<R: bytes::Buf>(r: &mut R, v: Version) -> Result<Self, DecodeError> {
321		let kind = u8::decode(r, v)?;
322		if kind != SERVER_SETUP {
323			return Err(DecodeError::InvalidValue);
324		}
325
326		let size = match SetupVersion::from_version(v) {
327			SetupVersion::Draft14 | SetupVersion::Draft15Plus => u16::decode(r, v)? as usize,
328			SetupVersion::LiteLegacy => usize::decode(r, v)?,
329			SetupVersion::Modern | SetupVersion::Unsupported => return Err(DecodeError::Version),
330		};
331
332		if size > MAX_SETUP_SIZE {
333			return Err(DecodeError::MessageTooLarge {
334				size,
335				max: MAX_SETUP_SIZE,
336			});
337		}
338
339		if r.remaining() < size {
340			return Err(DecodeError::Short);
341		}
342
343		let mut msg = r.copy_to_bytes(size);
344		let version = match SetupVersion::from_version(v) {
345			SetupVersion::Draft15Plus => v.into(),
346			SetupVersion::Draft14 | SetupVersion::LiteLegacy => {
347				coding::Version::decode(&mut msg, v).map_err(DecodeError::complete)?
348			}
349			SetupVersion::Modern | SetupVersion::Unsupported => return Err(DecodeError::Version),
350		};
351
352		Ok(Self {
353			version,
354			parameters: msg,
355		})
356	}
357}
358
359#[cfg(test)]
360mod tests {
361	use super::*;
362
363	#[test]
364	#[tracing_test::traced_test]
365	fn credentials_stay_out_of_traces() {
366		let secret = "credential-that-must-not-be-logged";
367		let token = Token {
368			kind: Token::OUT_OF_BAND,
369			value: secret.as_bytes().to_vec(),
370		};
371		let setup = lite::Setup {
372			path: Some(format!("/room?jwt={secret}")),
373			..Default::default()
374		};
375		let wire = setup.encode_bytes(lite::Version::Lite06).unwrap();
376		let decoded = lite::Setup::decode(&mut wire.as_ref(), lite::Version::Lite06).unwrap();
377		assert_eq!(decoded, setup);
378
379		let version = ietf::Version::Draft22;
380		let mut params = ietf::Parameters::default();
381		ietf::token::into_setup(&mut params, &token, version).unwrap();
382		params.set_bytes(ietf::ParameterBytes::Path, format!("/room?jwt={secret}").into_bytes());
383		let setup = Setup {
384			parameters: params.encode_bytes(version).unwrap(),
385		};
386		let wire = setup.encode_bytes(Version::Ietf(version)).unwrap();
387		let decoded = Setup::decode(&mut wire.as_ref(), Version::Ietf(version)).unwrap();
388		let params = ietf::Parameters::decode(&mut decoded.parameters.as_ref(), version).unwrap();
389		assert_eq!(ietf::token::from_setup(&params, version).unwrap(), Some(token.clone()));
390		let client = Client {
391			versions: coding::Versions::from([Version::Ietf(version).into()]),
392			parameters: setup.parameters.clone(),
393		};
394		let server = Server {
395			version: Version::Ietf(version).into(),
396			parameters: setup.parameters.clone(),
397		};
398		let mut lite_params = lite::Parameters::default();
399		lite_params.set_bytes(999, secret.as_bytes().to_vec());
400		tracing::trace!(
401			?token,
402			?setup,
403			?decoded,
404			?params,
405			?client,
406			?server,
407			?lite_params,
408			"SETUP diagnostics"
409		);
410
411		let uri = format!("https://relay.example/room?jwt={secret}");
412		let goaway = lite::Goaway {
413			uri: uri.as_str().into(),
414		};
415		let wire = goaway.encode_bytes(lite::Version::Lite06).unwrap();
416		assert_eq!(
417			lite::Goaway::decode(&mut wire.as_ref(), lite::Version::Lite06)
418				.unwrap()
419				.uri,
420			uri
421		);
422		let goaway = ietf::GoAway {
423			new_session_uri: uri.as_str().into(),
424			timeout: 0,
425		};
426		let wire = goaway.encode_bytes(version).unwrap();
427		assert_eq!(
428			ietf::GoAway::decode(&mut wire.as_ref(), version)
429				.unwrap()
430				.new_session_uri,
431			uri
432		);
433		tracing::trace!(goaway = ?crate::goaway::Goaway::redirect(uri), "GOAWAY diagnostics");
434
435		// A malformed message can still contain a valid credential before the error.
436		let mut body = Vec::new();
437		(secret.len() + 1).encode(&mut body, lite::Version::Lite06).unwrap();
438		body.extend_from_slice(secret.as_bytes());
439		let mut malformed = Vec::new();
440		body.len().encode(&mut malformed, lite::Version::Lite06).unwrap();
441		malformed.extend_from_slice(&body);
442		assert!(lite::Goaway::decode(&mut malformed.as_slice(), lite::Version::Lite06).is_err());
443		let mut body = Vec::new();
444		// The required timeout is absent.
445		format!("https://relay.example/?jwt={secret}")
446			.encode(&mut body, version)
447			.unwrap();
448		let mut malformed = Vec::new();
449		(body.len() as u16).encode(&mut malformed, version).unwrap();
450		malformed.extend_from_slice(&body);
451		assert!(ietf::GoAway::decode(&mut malformed.as_slice(), version).is_err());
452
453		assert!(logs_contain("encoding"));
454		assert!(logs_contain("decoded"));
455		assert!(logs_contain("SETUP diagnostics"));
456		assert!(logs_contain("GOAWAY diagnostics"));
457		assert!(!logs_contain(secret));
458		let decimal = format!("{:?}", secret.as_bytes());
459		assert!(!logs_contain(&decimal[1..decimal.len() - 1]));
460	}
461
462	/// Never emit a legacy SETUP our own receiver would refuse.
463	#[test]
464	fn encode_enforces_the_setup_limit() {
465		let v = Version::Lite(lite::Version::Lite01);
466		let parameters = Bytes::from(vec![0; MAX_SETUP_SIZE]);
467
468		let client = Client {
469			versions: coding::Versions::from([v.into()]),
470			parameters: parameters.clone(),
471		};
472		let mut buf = Vec::new();
473		assert!(matches!(client.encode(&mut buf, v), Err(EncodeError::TooLarge)));
474
475		let server = Server {
476			version: v.into(),
477			parameters,
478		};
479		let mut buf = Vec::new();
480		assert!(matches!(server.encode(&mut buf, v), Err(EncodeError::TooLarge)));
481	}
482}