Skip to main content

moq_e2ee/
name.rs

1use std::fmt;
2use std::str::FromStr;
3
4use base64::Engine;
5use base64::engine::general_purpose::URL_SAFE_NO_PAD;
6
7use crate::error::{Error, Result};
8use crate::limits::{NAME_LEN, NAME_TEXT_LEN};
9
10/// An opaque physical track name: 22 unpadded base64url characters derived from a semantic name.
11#[derive(Clone, Copy, PartialEq, Eq, Hash)]
12pub struct Name([u8; NAME_TEXT_LEN]);
13
14impl Name {
15	/// The 22 ASCII characters, a valid moq-lite track name.
16	pub fn as_str(&self) -> &str {
17		std::str::from_utf8(&self.0).expect("base64url is ASCII")
18	}
19}
20
21impl FromStr for Name {
22	type Err = Error;
23
24	/// Accept a name learned from a track or a decrypted catalog.
25	fn from_str(name: &str) -> Result<Self> {
26		if name.len() != NAME_TEXT_LEN {
27			return Err(Error::Identity);
28		}
29		let mut material = [0u8; NAME_LEN];
30		let n = URL_SAFE_NO_PAD
31			.decode_slice(name.as_bytes(), &mut material)
32			.map_err(|_| Error::Identity)?;
33		if n != NAME_LEN {
34			return Err(Error::Identity);
35		}
36		let mut out = [0u8; NAME_TEXT_LEN];
37		out.copy_from_slice(name.as_bytes());
38		Ok(Self(out))
39	}
40}
41
42impl fmt::Debug for Name {
43	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44		f.debug_tuple("Name").field(&self.as_str()).finish()
45	}
46}
47
48impl fmt::Display for Name {
49	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50		f.write_str(self.as_str())
51	}
52}
53
54impl AsRef<str> for Name {
55	fn as_ref(&self) -> &str {
56		self.as_str()
57	}
58}
59
60/// Encode 16 bytes of HKDF output as a name.
61pub(crate) fn encode(material: [u8; NAME_LEN]) -> Name {
62	let mut out = [0u8; NAME_TEXT_LEN];
63	let n = URL_SAFE_NO_PAD
64		.encode_slice(material, &mut out)
65		.expect("16 bytes encode to 22 base64url characters");
66	debug_assert_eq!(n, NAME_TEXT_LEN);
67	Name(out)
68}