Skip to main content

moq_e2ee/
group.rs

1//! Grouped-frame producer and consumer for one group identity.
2
3use std::fmt;
4use std::sync::atomic::{AtomicBool, Ordering};
5use std::sync::{Arc, Mutex};
6use std::task::{Poll, ready};
7
8use bytes::Bytes;
9
10use crate::error::{Error, Result};
11use crate::key::TrackKey;
12use crate::limits::MAX_GROUPED_PAYLOAD;
13
14/// A decrypted grouped frame.
15#[derive(Clone, Debug, PartialEq, Eq)]
16pub struct Frame {
17	/// Presentation timestamp.
18	pub timestamp: moq_net::Timestamp,
19	/// Decrypted application bytes.
20	pub plaintext: Bytes,
21}
22
23/// Exclusive writer for one group; frames are numbered from 0 in write order.
24///
25/// Dropping without [`Self::finish`] or [`Self::abort`] lets the net group close uncleanly.
26pub struct Producer {
27	inner: moq_net::group::Producer,
28	key: Arc<Mutex<TrackKey>>,
29	next_frame: u32,
30}
31
32impl Producer {
33	pub(crate) fn new(inner: moq_net::group::Producer, key: Arc<Mutex<TrackKey>>) -> Self {
34		Self {
35			inner,
36			key,
37			next_frame: 0,
38		}
39	}
40
41	/// The group's sequence number.
42	pub fn sequence(&self) -> u64 {
43		self.inner.sequence
44	}
45
46	/// Next frame index that will be written.
47	pub fn next_frame(&self) -> u32 {
48		self.next_frame
49	}
50
51	/// Encrypt `plaintext` at the next frame index and write the ciphertext.
52	///
53	/// The identity is spent before the net write, so a failed write never repeats a
54	/// nonce with different bytes. Predictable failures, an oversize plaintext or a
55	/// timestamp the track cannot represent, are refused before encryption.
56	///
57	/// # Errors
58	///
59	/// [`Error::Identity`] if the next frame exceeds 32 bits, [`Error::Exhausted`],
60	/// [`Error::Oversize`], or a net write error.
61	pub fn write_frame(&mut self, timestamp: moq_net::Timestamp, plaintext: &[u8]) -> Result<()> {
62		let frame = self.next_frame;
63		if frame == u32::MAX {
64			return Err(Error::Identity);
65		}
66		timestamp
67			.convert(self.inner.timescale())
68			.map_err(|_| Error::Net(moq_net::Error::TimestampMismatch))?;
69		let payload = self.key.lock().expect("track key").protect(
70			self.inner.sequence,
71			u64::from(frame),
72			plaintext,
73			MAX_GROUPED_PAYLOAD,
74		)?;
75		self.next_frame = frame + 1;
76		self.inner.write_frame(timestamp, payload)?;
77		Ok(())
78	}
79
80	/// Finish the group. No more frames will be written.
81	///
82	/// # Errors
83	///
84	/// A net error if the group is already closed.
85	pub fn finish(self) -> Result<()> {
86		self.inner.finish()?;
87		Ok(())
88	}
89
90	/// Abort the group with a cancel, consuming the handle.
91	///
92	/// # Errors
93	///
94	/// A net error if the group is already closed.
95	pub fn abort(self) -> Result<()> {
96		self.inner.abort(moq_net::Error::Cancel)?;
97		Ok(())
98	}
99
100	#[cfg(test)]
101	pub(crate) fn invocations(&self) -> u64 {
102		self.key.lock().expect("track key").invocations()
103	}
104}
105
106impl fmt::Debug for Producer {
107	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108		f.debug_struct("group::Producer")
109			.field("sequence", &self.sequence())
110			.field("next_frame", &self.next_frame)
111			.finish()
112	}
113}
114
115/// Reader for one protected group.
116///
117/// Authentication failure is sticky and ends the grouped track.
118pub struct Consumer {
119	inner: moq_net::group::Consumer,
120	key: Arc<Mutex<TrackKey>>,
121	auth_failed: Arc<AtomicBool>,
122}
123
124impl Consumer {
125	pub(crate) fn new(
126		inner: moq_net::group::Consumer,
127		key: Arc<Mutex<TrackKey>>,
128		auth_failed: Arc<AtomicBool>,
129	) -> Self {
130		Self {
131			inner,
132			key,
133			auth_failed,
134		}
135	}
136
137	/// The group's sequence number.
138	pub fn sequence(&self) -> u64 {
139		self.inner.sequence
140	}
141
142	/// Read the next decrypted frame, without blocking.
143	///
144	/// # Errors
145	///
146	/// [`Error::Authentication`] ends this track. [`Error::Oversize`] or
147	/// [`Error::Exhausted`] as for open, and net errors from the underlying group.
148	pub fn poll_read_frame(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<Frame>>> {
149		if self.auth_failed.load(Ordering::Acquire) {
150			return Poll::Ready(Err(Error::Authentication));
151		}
152		let Some(frame) = ready!(self.inner.poll_read_frame(waiter)?) else {
153			return Poll::Ready(Ok(None));
154		};
155		// The transport cursor already advanced past the frame just read; its index is
156		// the nonce half, so a group resumed above frame 0 still authenticates.
157		let index = self.inner.index().checked_sub(1).ok_or(Error::Identity)?;
158		let result =
159			self.key
160				.lock()
161				.expect("track key")
162				.open(self.inner.sequence, index, &frame.payload, MAX_GROUPED_PAYLOAD);
163		match result {
164			Ok(plaintext) => Poll::Ready(Ok(Some(Frame {
165				timestamp: frame.timestamp,
166				plaintext,
167			}))),
168			Err(err) => {
169				if matches!(err, Error::Authentication | Error::Identity) {
170					self.auth_failed.store(true, Ordering::Release);
171				}
172				Poll::Ready(Err(err))
173			}
174		}
175	}
176
177	/// Read the next decrypted frame.
178	///
179	/// # Errors
180	///
181	/// Same as [`Self::poll_read_frame`].
182	pub async fn read_frame(&mut self) -> Result<Option<Frame>> {
183		kio::wait(|waiter| self.poll_read_frame(waiter)).await
184	}
185}
186
187impl fmt::Debug for Consumer {
188	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189		f.debug_struct("group::Consumer")
190			.field("sequence", &self.sequence())
191			.field("index", &self.inner.index())
192			.finish()
193	}
194}