moq_json/window/
encoder.rs1use std::collections::VecDeque;
4use std::marker::PhantomData;
5
6use bytes::Bytes;
7use serde::Serialize;
8use serde_json::Value;
9
10use super::op::{Header, Op};
11use crate::{Error, Result};
12
13pub(super) const MAX_GROUP_FRAMES: usize = 256;
17
18pub(super) const MAX_INDEX: u64 = (1 << 53) - 1;
20
21#[derive(Debug, Clone)]
26#[non_exhaustive]
27pub struct ProducerConfig {
28 pub op_ratio: u32,
41
42 pub compression: bool,
48}
49
50impl Default for ProducerConfig {
51 fn default() -> Self {
52 Self {
53 op_ratio: 8,
54 compression: false,
55 }
56 }
57}
58
59impl ProducerConfig {
60 pub fn with_op_ratio(mut self, op_ratio: u32) -> Self {
62 self.op_ratio = op_ratio;
63 self
64 }
65
66 pub fn with_compression(mut self, compression: bool) -> Self {
68 self.compression = compression;
69 self
70 }
71}
72
73#[derive(Clone, Debug)]
75#[non_exhaustive]
76pub struct Encoded {
77 pub payload: Bytes,
79
80 pub keyframe: bool,
85}
86
87#[must_use = "write the frame, then commit it"]
92pub struct Pending<'a, T> {
93 encoder: &'a mut Encoder<T>,
94 encoded: Encoded,
95 edit: Option<Edit>,
96}
97
98enum Edit {
100 Push(Value),
101 Pop(u64),
102}
103
104impl<T> std::ops::Deref for Pending<'_, T> {
105 type Target = Encoded;
106
107 fn deref(&self) -> &Encoded {
108 &self.encoded
109 }
110}
111
112impl<T> Pending<'_, T> {
113 pub fn commit(mut self) {
117 let edit = self.edit.take().expect("pending edit");
118 self.encoder.commit(edit);
119 }
120}
121
122impl<T> Drop for Pending<'_, T> {
123 fn drop(&mut self) {
124 if self.edit.is_some() {
125 self.encoder.resync();
126 }
127 }
128}
129
130pub struct Encoder<T> {
140 config: ProducerConfig,
141
142 window: VecDeque<Value>,
145
146 offset: u64,
148
149 flate: Option<moq_flate::Encoder>,
151
152 op_bytes: u64,
154
155 header_len: u64,
157
158 group_frames: usize,
160
161 resync: bool,
164
165 _marker: PhantomData<fn(T)>,
166}
167
168impl<T> Encoder<T> {
169 pub fn new(config: ProducerConfig) -> Self {
171 Self {
172 config,
173 window: VecDeque::new(),
174 offset: 0,
175 flate: None,
176 op_bytes: 0,
177 header_len: 0,
178 group_frames: 0,
179 resync: true,
180 _marker: PhantomData,
181 }
182 }
183
184 pub fn window(&self) -> Vec<Value> {
188 self.window.iter().cloned().collect()
189 }
190
191 pub fn range(&self) -> std::ops::Range<u64> {
193 self.offset..self.offset + self.window.len() as u64
194 }
195
196 fn resync(&mut self) {
198 self.flate = None;
199 self.op_bytes = 0;
200 self.header_len = 0;
201 self.group_frames = 0;
202 self.resync = true;
203 }
204
205 fn commit(&mut self, edit: Edit) {
207 match edit {
208 Edit::Push(record) => self.window.push_back(record),
209 Edit::Pop(count) => {
210 self.window.drain(..count as usize);
211 self.offset += count;
212 }
213 }
214 }
215
216 fn op_allowed(&self) -> bool {
218 let ratio = u64::from(self.config.op_ratio);
219 ratio != 0
220 && self.group_frames > 0
221 && self.group_frames < MAX_GROUP_FRAMES
222 && self.op_bytes <= ratio * self.header_len
223 }
224
225 fn validate_plaintext(len: usize, kind: &str) -> Result<()> {
227 if u64::try_from(len).unwrap_or(u64::MAX) > moq_flate::DEFAULT_MAX_FRAME_SIZE {
228 return Err(Error::Json(format!(
229 "window {kind} exceeds the decoder's decompressed size limit"
230 )));
231 }
232 Ok(())
233 }
234
235 fn frame(&mut self, bytes: Vec<u8>) -> Result<Encoded> {
237 Self::validate_plaintext(bytes.len(), "frame")?;
238 let payload = match self.flate.as_mut() {
239 Some(flate) => flate.frame(&bytes),
240 None => Bytes::from(bytes),
241 };
242
243 self.op_bytes += payload.len() as u64;
244 self.group_frames += 1;
245
246 Ok(Encoded {
247 payload,
248 keyframe: false,
249 })
250 }
251
252 fn emit_op(&mut self, bytes: Vec<u8>) -> Result<Option<Encoded>> {
254 let encoded = self.frame(bytes)?;
255 let group_bytes = self.header_len.saturating_add(self.op_bytes);
256 if group_bytes > moq_net::group::MAX_CACHE_BYTES {
257 self.resync();
258 Ok(None)
259 } else {
260 Ok(Some(encoded))
261 }
262 }
263
264 fn header(offset: u64, records: Vec<&Value>) -> Result<Vec<u8>> {
266 Ok(serde_json::to_vec(&Header { offset, records })?)
267 }
268
269 fn emit_header(&mut self, bytes: Vec<u8>) -> Result<Encoded> {
271 Self::validate_plaintext(bytes.len(), "header")?;
272
273 let (payload, flate) = match self.config.compression {
276 true => {
277 let mut flate = moq_flate::Encoder::new();
278 let payload = flate.frame(&bytes);
279 (payload, Some(flate))
280 }
281 false => (Bytes::from(bytes), None),
282 };
283 if payload.len() as u64 > moq_net::group::MAX_CACHE_BYTES {
284 return Err(Error::Json("window header exceeds the group cache limit".into()));
285 }
286
287 self.header_len = payload.len() as u64;
288 self.op_bytes = 0;
289 self.group_frames = 1;
290 self.flate = flate;
291 self.resync = false;
292
293 Ok(Encoded {
294 payload,
295 keyframe: true,
296 })
297 }
298
299 pub fn pop(&mut self, count: u64) -> Result<Option<Pending<'_, T>>> {
304 let count = count.min(self.window.len() as u64);
305 if count == 0 {
306 return Ok(None);
307 }
308
309 let offset = self.offset + count;
310 let encoded = match self.resync || !self.op_allowed() {
311 true => {
312 let bytes = Self::header(offset, self.window.iter().skip(count as usize).collect())?;
313 self.emit_header(bytes)?
314 }
315 false => {
316 let bytes = serde_json::to_vec(&Op::<&Value>::Pop(count))?;
317 match self.emit_op(bytes)? {
318 Some(encoded) => encoded,
319 None => {
320 let bytes = Self::header(offset, self.window.iter().skip(count as usize).collect())?;
321 self.emit_header(bytes)?
322 }
323 }
324 }
325 };
326
327 Ok(Some(self.pending(encoded, Edit::Pop(count))))
328 }
329
330 fn pending(&mut self, encoded: Encoded, edit: Edit) -> Pending<'_, T> {
332 Pending {
333 encoder: self,
334 encoded,
335 edit: Some(edit),
336 }
337 }
338}
339
340impl<T: Serialize> Encoder<T> {
341 pub fn push(&mut self, value: &T) -> Result<Pending<'_, T>> {
346 let bytes = serde_json::to_vec(value)?;
350 let record: Value = serde_json::from_slice(&bytes)?;
351 if self.range().end >= MAX_INDEX {
352 return Err(crate::Error::Json("window index exceeds the safe integer range".into()));
353 }
354
355 let encoded = match self.resync || !self.op_allowed() {
356 true => {
357 let bytes = Self::header(
358 self.offset,
359 self.window.iter().chain(std::iter::once(&record)).collect(),
360 )?;
361 self.emit_header(bytes)?
362 }
363 false => {
364 let bytes = serde_json::to_vec(&Op::Push(&record))?;
365 match self.emit_op(bytes)? {
366 Some(encoded) => encoded,
367 None => {
368 let bytes = Self::header(
369 self.offset,
370 self.window.iter().chain(std::iter::once(&record)).collect(),
371 )?;
372 self.emit_header(bytes)?
373 }
374 }
375 }
376 };
377
378 Ok(self.pending(encoded, Edit::Push(record)))
379 }
380}
381
382#[cfg(test)]
383mod test {
384 use super::*;
385
386 #[test]
387 fn an_op_that_would_evict_the_header_rolls_first() {
388 let mut encoder = Encoder::<String>::new(ProducerConfig::default().with_op_ratio(u32::MAX));
389 let first = "a".repeat(16 * 1024 * 1024);
390 let next = "b".repeat(15 * 1024 * 1024);
391
392 let frame = encoder.push(&first).unwrap();
393 assert!(frame.keyframe);
394 frame.commit();
395
396 let frame = encoder.push(&next).unwrap();
397 assert!(!frame.keyframe);
398 frame.commit();
399
400 let frame = encoder.pop(1).unwrap().unwrap();
401 assert!(!frame.keyframe);
402 frame.commit();
403
404 let frame = encoder.push(&next).unwrap();
405 assert!(frame.keyframe);
406 assert!(frame.payload.len() < moq_net::group::MAX_CACHE_BYTES as usize);
407 frame.commit();
408 }
409
410 #[test]
411 fn an_uncommitted_edit_leaves_the_window_unchanged() {
412 let mut encoder = Encoder::<u64>::new(ProducerConfig::default());
413
414 drop(encoder.push(&1).unwrap());
415 assert!(encoder.window().is_empty());
416
417 let frame = encoder.push(&2).unwrap();
418 assert!(frame.keyframe);
419 frame.commit();
420 assert_eq!(encoder.window(), vec![Value::from(2)]);
421
422 drop(encoder.pop(1).unwrap().unwrap());
423 assert_eq!(encoder.window(), vec![Value::from(2)]);
424 }
425
426 #[test]
427 fn a_header_larger_than_the_group_cache_is_rejected() {
428 let mut encoder = Encoder::<String>::new(ProducerConfig::default());
429 let record = "x".repeat(moq_net::group::MAX_CACHE_BYTES as usize);
430
431 let err = encoder.push(&record).err().expect("oversized header should fail");
432 assert!(err.to_string().contains("group cache limit"));
433 assert!(encoder.window().is_empty());
434
435 let frame = encoder.push(&"ok".to_string()).unwrap();
436 assert!(frame.keyframe);
437 }
438
439 #[test]
440 fn plaintext_is_bounded_by_the_decoder_limit() {
441 let len = usize::try_from(moq_flate::DEFAULT_MAX_FRAME_SIZE + 1).unwrap();
442 assert!(Encoder::<()>::validate_plaintext(len, "frame").is_err());
443 }
444}