Skip to main content

moq_json/window/
decoder.rs

1//! The track-free half of window consumption: frame payloads in, window events out.
2
3use std::collections::VecDeque;
4
5use serde::de::DeserializeOwned;
6
7use super::encoder::MAX_INDEX;
8use super::op::{Header, Op};
9use crate::{Error, Result};
10
11/// Configuration for a [`Decoder`], and so for the [`Consumer`](super::Consumer) wrapping one.
12#[derive(Debug, Clone, Default)]
13#[non_exhaustive]
14pub struct ConsumerConfig {
15	/// Read frames written with
16	/// [`ProducerConfig::compression`](super::ProducerConfig::compression) on.
17	pub compression: bool,
18}
19
20impl ConsumerConfig {
21	/// Set [`compression`](Self::compression) (a builder, since the struct is `#[non_exhaustive]`).
22	pub fn with_compression(mut self, compression: bool) -> Self {
23		self.compression = compression;
24		self
25	}
26}
27
28/// One change to the window, as the consumer sees it.
29///
30/// A record is `Push`ed when it first reaches this consumer. Contiguous ranges are `Pop`ped when
31/// they leave the window or `Skip`ped when they were dropped before this consumer saw them.
32#[derive(Debug, Clone, PartialEq)]
33#[non_exhaustive]
34pub enum Event<T> {
35	/// This record joined the window, at this absolute index.
36	Push {
37		/// Absolute index assigned to the record.
38		index: u64,
39		/// The decoded record.
40		value: T,
41	},
42
43	/// These records left the window.
44	Pop(std::ops::Range<u64>),
45
46	/// These records existed but will never be delivered: they were pushed and dropped while this
47	/// consumer was behind.
48	Skip(std::ops::Range<u64>),
49}
50
51/// An event ready to return, or a header's unseen records waiting to become push events.
52enum Queued<T> {
53	Event(Event<T>),
54	Push { index: u64, records: std::vec::IntoIter<T> },
55}
56
57/// Decodes one MoQ group's frames while borrowing the continuous window state.
58pub struct Group<'a, T> {
59	decoder: &'a mut Decoder<T>,
60	codec: Codec,
61}
62
63/// Group-local decoding state used by both [`Group`] and [`Consumer`](super::Consumer).
64pub(super) struct Codec {
65	/// The group's DEFLATE decoder, `Some` while reading compressed frames.
66	flate: Option<moq_flate::Decoder>,
67
68	/// Whether the required frame-zero header has been decoded.
69	positioned: bool,
70}
71
72impl Codec {
73	pub(super) fn new() -> Self {
74		Self {
75			flate: None,
76			positioned: false,
77		}
78	}
79}
80
81/// Reconstructs window events from frame payloads.
82///
83/// The track-free core of [`Consumer`](super::Consumer). It tracks indices, not contents: it knows
84/// where the window starts and how far it has delivered, which is all it needs to turn a header into
85/// the pushes, pops, and skips the reader has not already been told about.
86///
87/// Group rolls are invisible here on purpose. A header restates the window, and this decoder emits
88/// only what is new, so a reader sees one continuous stream of edits no matter how often the
89/// publisher rolled for compression's sake.
90pub struct Decoder<T> {
91	config: ConsumerConfig,
92
93	/// Absolute index of the window's front, once a group header has positioned us.
94	front: u64,
95
96	/// Records currently in the window.
97	len: u64,
98
99	/// Next index to deliver, or `None` before the first header. A fresh consumer adopts the first
100	/// header's offset rather than skipping everything that came before it.
101	delivered: Option<u64>,
102
103	/// Events produced by the frames decoded so far, oldest first.
104	events: VecDeque<Queued<T>>,
105}
106
107impl<T> Decoder<T> {
108	/// Create a decoder that has not yet been positioned by a group header.
109	pub fn new(config: ConsumerConfig) -> Self {
110		Self {
111			config,
112			front: 0,
113			len: 0,
114			delivered: None,
115			events: VecDeque::new(),
116		}
117	}
118
119	/// Borrow this decoder for one MoQ group.
120	pub fn group(&mut self) -> Group<'_, T> {
121		Group {
122			decoder: self,
123			codec: Codec::new(),
124		}
125	}
126
127	/// Take the next event produced by the frames decoded so far.
128	///
129	/// Returns `None` once the queue is drained, which is a request for more frames rather than the
130	/// end of anything: [`Group::decode`] refills it. Deliberately not [`Iterator`], whose
131	/// `None` a caller would reasonably read as exhausted.
132	pub fn next_event(&mut self) -> Option<Event<T>> {
133		match self.events.pop_front()? {
134			Queued::Event(event) => Some(event),
135			Queued::Push { index, mut records } => {
136				let value = records.next().expect("queued push batch is not empty");
137				if !records.as_slice().is_empty() {
138					self.events.push_front(Queued::Push {
139						index: index + 1,
140						records,
141					});
142				}
143				Some(Event::Push { index, value })
144			}
145		}
146	}
147
148	/// Absolute index of the oldest record in the window, and of the next to arrive.
149	pub fn range(&self) -> std::ops::Range<u64> {
150		self.front..self.front + self.len
151	}
152}
153
154impl<T: DeserializeOwned> Decoder<T> {
155	/// Decode one frame, queueing the events it implies.
156	pub(super) fn decode(&mut self, group: &mut Codec, payload: &[u8]) -> Result<()> {
157		let inflated = match self.config.compression {
158			true => Some(group.flate.get_or_insert_with(moq_flate::Decoder::new).frame(payload)?),
159			false => None,
160		};
161		let bytes = inflated.as_deref().unwrap_or(payload);
162
163		if !group.positioned {
164			let header: Header<T> = serde_path_to_error::deserialize(&mut serde_json::Deserializer::from_slice(bytes))
165				.map_err(|err| Error::Json(err.to_string()))?;
166			self.apply_header(header.offset, header.records)?;
167			group.positioned = true;
168			return Ok(());
169		}
170
171		match serde_path_to_error::deserialize(&mut serde_json::Deserializer::from_slice(bytes))
172			.map_err(|err| Error::Json(err.to_string()))?
173		{
174			Op::Push(record) => self.apply_push(record),
175			Op::Pop(count) => self.apply_pop(count),
176		}
177	}
178
179	/// The window is exactly these records. Report what this reader missed, then what is new.
180	fn apply_header(&mut self, offset: u64, records: Vec<T>) -> Result<()> {
181		if offset > MAX_INDEX {
182			return Err(Error::Json("window offset exceeds the safe integer range".into()));
183		}
184		let len = u64::try_from(records.len()).map_err(|_| Error::Json("window length exceeds u64".into()))?;
185		let end = offset
186			.checked_add(len)
187			.filter(|end| *end <= MAX_INDEX)
188			.ok_or_else(|| Error::Json("window range exceeds the safe integer range".into()))?;
189
190		let delivered = match self.delivered {
191			// First position: adopt the publisher's offset rather than skipping all of history.
192			None => offset,
193			Some(delivered) => {
194				if offset < self.front || end < delivered {
195					return Err(Error::Json("window header moved backwards".into()));
196				}
197
198				// Records that left the window while we were away. Those we had delivered are pops; those
199				// we never saw are skips. Keep each gap compact: the offset is untrusted and may jump by
200				// far more indices than a consumer could materialize individually.
201				let popped = self.front..delivered.min(offset);
202				if !popped.is_empty() {
203					self.events.push_back(Queued::Event(Event::Pop(popped)));
204				}
205				let skipped = delivered..offset;
206				if !skipped.is_empty() {
207					self.events.push_back(Queued::Event(Event::Skip(skipped)));
208				}
209				delivered
210			}
211		};
212
213		// Keep the unseen tail as one batch and materialize each push only when the caller asks for it.
214		let skip = usize::try_from(delivered.saturating_sub(offset))
215			.map_err(|_| Error::Json("window length exceeds usize".into()))?;
216		let mut records = records.into_iter();
217		if skip > 0 {
218			records.nth(skip - 1);
219		}
220		if !records.as_slice().is_empty() {
221			self.events.push_back(Queued::Push {
222				index: offset + skip as u64,
223				records,
224			});
225		}
226
227		self.front = offset;
228		self.len = end - offset;
229		self.delivered = Some(delivered.max(end));
230		Ok(())
231	}
232
233	/// One record joined the back.
234	fn apply_push(&mut self, record: T) -> Result<()> {
235		let delivered = self.delivered.expect("group header positioned the decoder");
236
237		let index = self
238			.front
239			.checked_add(self.len)
240			.ok_or_else(|| Error::Json("window range exceeds u64".into()))?;
241		let end = index
242			.checked_add(1)
243			.filter(|end| *end <= MAX_INDEX)
244			.ok_or_else(|| Error::Json("window range exceeds the safe integer range".into()))?;
245		self.len = end - self.front;
246
247		if index >= delivered {
248			self.events
249				.push_back(Queued::Event(Event::Push { index, value: record }));
250			self.delivered = Some(end);
251		}
252
253		Ok(())
254	}
255
256	/// Records left the front.
257	fn apply_pop(&mut self, count: u64) -> Result<()> {
258		let delivered = self.delivered.expect("group header positioned the decoder");
259		if count > self.len {
260			return Err(Error::Json(format!(
261				"pop of {count} exceeds the {} record(s) in the window",
262				self.len
263			)));
264		}
265
266		let end = self
267			.front
268			.checked_add(count)
269			.ok_or_else(|| Error::Json("window range exceeds u64".into()))?;
270		let popped = self.front..delivered.min(end);
271		if !popped.is_empty() {
272			self.events.push_back(Queued::Event(Event::Pop(popped)));
273		}
274		let skipped = delivered.max(self.front)..end;
275		if !skipped.is_empty() {
276			self.events.push_back(Queued::Event(Event::Skip(skipped)));
277		}
278
279		self.front = end;
280		self.len -= count;
281		self.delivered = Some(delivered.max(self.front));
282
283		Ok(())
284	}
285}
286
287impl<T> Group<'_, T> {
288	/// Take the next event produced by this group's frames so far.
289	pub fn next_event(&mut self) -> Option<Event<T>> {
290		self.decoder.next_event()
291	}
292
293	/// Absolute index of the oldest record in the window, and of the next to arrive.
294	pub fn range(&self) -> std::ops::Range<u64> {
295		self.decoder.range()
296	}
297}
298
299impl<T: DeserializeOwned> Group<'_, T> {
300	/// Decode the next frame in this group.
301	pub fn decode(&mut self, payload: &[u8]) -> Result<()> {
302		self.decoder.decode(&mut self.codec, payload)
303	}
304}