Skip to main content

moq_e2ee/
generation.rs

1use std::collections::HashSet;
2use std::fmt;
3use std::sync::{Arc, Mutex};
4
5use crate::credential::{Credential, append_bytes};
6use crate::epoch::Epoch;
7use crate::error::{Error, Result};
8use crate::key::TrackKey;
9use crate::limits::{KEY_LABEL, KEY_LEN, NAME_LABEL};
10use crate::name::{Name, encode};
11use crate::track;
12
13/// Grouped-frame versus datagram key domain.
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15pub(crate) enum Domain {
16	Group = 0x00,
17	Datagram = 0x01,
18}
19
20struct Inner {
21	credential: Credential,
22	epoch: Epoch,
23	// Physical names this generation has produced. Never released: a track reopened
24	// under the same epoch would restart its sequences and repeat nonces.
25	claims: Mutex<HashSet<Name>>,
26}
27
28/// One credential under one epoch: the scope of every name, key, and nonce.
29///
30/// Clones share the publisher claims, so a physical track is produced at most once per generation.
31#[derive(Clone)]
32pub struct Generation(Arc<Inner>);
33
34impl fmt::Debug for Generation {
35	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36		f.debug_struct("Generation")
37			.field("credential", &self.0.credential)
38			.field("epoch", &self.0.epoch)
39			.finish()
40	}
41}
42
43impl Generation {
44	pub(crate) fn new(credential: Credential, epoch: Epoch) -> Self {
45		Self(Arc::new(Inner {
46			credential,
47			epoch,
48			claims: Mutex::new(HashSet::new()),
49		}))
50	}
51
52	/// The credential this generation derives from.
53	pub fn credential(&self) -> &Credential {
54		&self.0.credential
55	}
56
57	/// The epoch this generation is scoped to.
58	pub fn epoch(&self) -> &Epoch {
59		&self.0.epoch
60	}
61
62	/// The opaque physical track name for a semantic track name.
63	///
64	/// # Errors
65	///
66	/// [`Error::Identity`] if `semantic` exceeds 65535 bytes.
67	pub fn name(&self, semantic: &str) -> Result<Name> {
68		let material = self.0.credential.expand(&self.name_info(semantic)?)?;
69		Ok(encode(material))
70	}
71
72	/// Protect a net track whose name is a physical name, claiming it for this generation.
73	///
74	/// # Errors
75	///
76	/// [`Error::Identity`] if the track name is not a physical name, [`Error::Reuse`] if
77	/// this generation already produced it.
78	pub fn produce(&self, track: moq_net::track::Producer) -> Result<track::Producer> {
79		let name: Name = track.name().parse()?;
80		if !self.0.claims.lock().expect("claims").insert(name) {
81			return Err(Error::Reuse);
82		}
83		Ok(track::Producer::new(
84			track,
85			self.key(&name, Domain::Group)?,
86			self.key(&name, Domain::Datagram)?,
87		))
88	}
89
90	/// Open a net subscription whose track name is a physical name.
91	///
92	/// # Errors
93	///
94	/// [`Error::Identity`] if the track name is not a physical name.
95	pub fn consume(&self, track: moq_net::track::Subscriber) -> Result<track::Consumer> {
96		let name: Name = track.name().parse()?;
97		Ok(track::Consumer::new(
98			track,
99			self.key(&name, Domain::Group)?,
100			self.key(&name, Domain::Datagram)?,
101		))
102	}
103
104	pub(crate) fn key(&self, name: &Name, domain: Domain) -> Result<TrackKey> {
105		Ok(TrackKey::new(self.key_bytes(name, domain)?))
106	}
107
108	pub(crate) fn key_bytes(&self, name: &Name, domain: Domain) -> Result<[u8; KEY_LEN]> {
109		self.0.credential.expand(&self.key_info(name, domain)?)
110	}
111
112	pub(crate) fn name_info(&self, semantic: &str) -> Result<Vec<u8>> {
113		let mut info = Vec::with_capacity(NAME_LABEL.len() + 64 + semantic.len());
114		info.extend_from_slice(NAME_LABEL);
115		self.scope(&mut info)?;
116		append_bytes(&mut info, semantic.as_bytes())?;
117		Ok(info)
118	}
119
120	pub(crate) fn key_info(&self, name: &Name, domain: Domain) -> Result<Vec<u8>> {
121		let mut info = Vec::with_capacity(KEY_LABEL.len() + 64 + name.as_str().len() + 1);
122		info.extend_from_slice(KEY_LABEL);
123		self.scope(&mut info)?;
124		append_bytes(&mut info, name.as_str().as_bytes())?;
125		info.push(domain as u8);
126		Ok(info)
127	}
128
129	/// The fields every epoch-scoped derivation binds: `bytes(context) || bytes(epoch) || u64(kid)`.
130	fn scope(&self, info: &mut Vec<u8>) -> Result<()> {
131		append_bytes(info, self.0.credential.context())?;
132		append_bytes(info, self.0.epoch.as_str().as_bytes())?;
133		info.extend_from_slice(&self.0.credential.kid().to_be_bytes());
134		Ok(())
135	}
136}