use std::cell::RefCell;
use std::marker::PhantomData;
use std::sync::OnceLock;
use bytes::Bytes;
use serde::Serialize;
use serde_json::Value;
use crate::{Compression, Result};
pub(super) const MAX_DELTA_FRAMES: usize = 256;
enum Baseline {
Parsed(Value),
Encoded {
bytes: Bytes,
parsed: OnceLock<Option<Value>>,
},
}
impl Baseline {
fn value(&self) -> Option<&Value> {
match self {
Self::Parsed(value) => Some(value),
Self::Encoded { bytes, parsed } => parsed.get_or_init(|| serde_json::from_slice(bytes).ok()).as_ref(),
}
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct Config {
pub delta_ratio: u32,
pub compression: Compression,
}
impl Config {
pub fn with_delta_ratio(mut self, delta_ratio: u32) -> Self {
self.delta_ratio = delta_ratio;
self
}
}
impl Default for Config {
fn default() -> Self {
Self {
delta_ratio: 8,
compression: Compression::None,
}
}
}
#[derive(Clone, Debug)]
pub struct Encoded {
pub payload: Bytes,
pub keyframe: bool,
}
#[must_use = "the frame must be written and committed, or dropped to resynchronize the encoder"]
pub struct Pending<'a, T> {
encoder: &'a mut Encoder<T>,
encoded: Encoded,
committed: bool,
}
impl<T> Pending<'_, T> {
pub fn commit(mut self) {
self.committed = true;
}
}
impl<T> std::ops::Deref for Pending<'_, T> {
type Target = Encoded;
fn deref(&self) -> &Encoded {
&self.encoded
}
}
impl<T> Drop for Pending<'_, T> {
fn drop(&mut self) {
if !self.committed {
self.encoder.reset();
}
}
}
pub struct Encoder<T> {
config: Config,
last: Option<Baseline>,
scratch: RefCell<crate::diff::Scratch>,
flate: Option<moq_flate::Encoder>,
delta_bytes: u64,
snapshot_len: u64,
group_frames: usize,
resync: bool,
_marker: PhantomData<fn(T)>,
}
impl<T> Encoder<T> {
pub fn new(config: Config) -> Self {
Self {
config,
last: None,
scratch: RefCell::new(crate::diff::Scratch::default()),
flate: None,
delta_bytes: 0,
snapshot_len: 0,
group_frames: 0,
resync: false,
_marker: PhantomData,
}
}
pub fn value(&self) -> Option<&Value> {
self.last.as_ref()?.value()
}
pub fn reset(&mut self) {
self.flate = None;
self.delta_bytes = 0;
self.snapshot_len = 0;
self.group_frames = 0;
self.resync = true;
}
}
impl<T: Serialize> Encoder<T> {
pub fn update(&mut self, value: &T) -> Result<Option<Pending<'_, T>>> {
Ok(self.encode(value)?.map(|encoded| Pending {
encoder: self,
encoded,
committed: false,
}))
}
fn encode(&mut self, value: &T) -> Result<Option<Encoded>> {
if self.resync {
return self.snapshot(value).map(Some);
}
if let Some(Baseline::Encoded { bytes, .. }) = self.last.as_ref() {
let bytes = bytes.clone();
let next = serde_json::to_vec(value)?;
if next.as_slice() == bytes.as_ref() {
return Ok(None);
}
return self.snapshot_encoded(next).map(Some);
}
let Some(Baseline::Parsed(last)) = self.last.as_ref() else {
return self.snapshot(value).map(Some);
};
let crate::diff::PatchBytes { patch, forced_snapshot } =
crate::diff::bytes(last, value, &self.scratch).map_err(crate::Error::Json)?;
if !forced_snapshot && patch.is_empty() {
return Ok(None);
}
if forced_snapshot || !self.delta_allowed() {
return self.snapshot(value).map(Some);
}
let bytes = Bytes::from(patch);
if self.config.compression.is_deflate() && bytes.len() as u64 > moq_flate::DEFAULT_MAX_FRAME_SIZE {
return Err(moq_flate::Error::TooLarge(moq_flate::DEFAULT_MAX_FRAME_SIZE).into());
}
let payload = match self.flate.as_mut() {
Some(flate) => flate.frame(&bytes),
None => bytes.clone(),
};
if self.snapshot_len + self.delta_bytes + payload.len() as u64 > moq_net::group::MAX_CACHE_BYTES {
return self.snapshot(value).map(Some);
}
self.delta_bytes += payload.len() as u64;
self.group_frames += 1;
let Some(Baseline::Parsed(last)) = self.last.as_mut() else {
unreachable!("a parsed snapshot precedes any delta")
};
crate::merge::apply_generated_bytes(last, &bytes)?;
Ok(Some(Encoded {
payload,
keyframe: false,
}))
}
fn delta_allowed(&self) -> bool {
let ratio = u64::from(self.config.delta_ratio);
ratio != 0
&& self.group_frames > 0
&& self.group_frames < MAX_DELTA_FRAMES
&& self.delta_bytes <= ratio * self.snapshot_len
}
fn snapshot(&mut self, value: &T) -> Result<Encoded> {
let snapshot = serde_json::to_vec(value)?;
self.snapshot_encoded(snapshot)
}
fn snapshot_encoded(&mut self, snapshot: Vec<u8>) -> Result<Encoded> {
if self.config.compression.is_deflate() && snapshot.len() as u64 > moq_flate::DEFAULT_MAX_FRAME_SIZE {
return Err(moq_flate::Error::TooLarge(moq_flate::DEFAULT_MAX_FRAME_SIZE).into());
}
let snapshot = Bytes::from(snapshot);
let last = if self.config.delta_ratio == 0 {
Baseline::Encoded {
bytes: snapshot.clone(),
parsed: OnceLock::new(),
}
} else {
Baseline::Parsed(serde_json::from_slice(&snapshot)?)
};
let (payload, flate) = match self.config.compression {
Compression::Deflate => {
let mut flate = moq_flate::Encoder::new();
let payload = flate.frame(&snapshot);
(payload, Some(flate))
}
Compression::None => (snapshot, None),
};
self.snapshot_len = payload.len() as u64;
self.delta_bytes = 0;
self.group_frames = 1;
self.flate = flate;
self.last = Some(last);
self.resync = false;
Ok(Encoded {
payload,
keyframe: true,
})
}
}
#[cfg(test)]
mod test {
use super::*;
use serde_json::json;
#[test]
fn duplicate_serialized_keys_are_refused() {
use serde::ser::SerializeMap;
struct Duplicate {
duplicate: bool,
}
impl Serialize for Duplicate {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
let mut map = serializer.serialize_map(Some(2 + usize::from(self.duplicate)))?;
map.serialize_entry("a", &1)?;
map.serialize_entry("b", &2)?;
if self.duplicate {
map.serialize_entry("a", &3)?;
}
map.end()
}
}
let mut encoder = Encoder::<Duplicate>::new(Config::default());
encoder
.update(&Duplicate { duplicate: false })
.unwrap()
.unwrap()
.commit();
let err = encoder.encode(&Duplicate { duplicate: true }).unwrap_err();
assert!(err.to_string().contains("duplicate JSON object key"));
}
fn encode(config: Config, values: &[Value]) -> Vec<(bool, usize)> {
let mut encoder = Encoder::<Value>::new(config);
let mut out = Vec::new();
for value in values {
if let Some(frame) = encoder.update(value).unwrap() {
out.push((frame.keyframe, frame.payload.len()));
frame.commit();
}
}
out
}
fn commit(encoder: &mut Encoder<Value>, value: &Value) -> Option<Encoded> {
let frame = encoder.update(value).unwrap()?;
let encoded = Encoded {
payload: frame.payload.clone(),
keyframe: frame.keyframe,
};
frame.commit();
Some(encoded)
}
#[test]
fn first_update_is_a_keyframe() {
let frames = encode(Config::default(), &[json!({ "a": 1 })]);
assert_eq!(frames.len(), 1);
assert!(frames[0].0);
}
#[test]
fn unchanged_value_encodes_nothing() {
let frames = encode(Config::default(), &[json!({ "a": 1 }), json!({ "a": 1 })]);
assert_eq!(frames.len(), 1);
}
#[test]
fn changes_ride_as_deltas() {
let frames = encode(
Config::default().with_delta_ratio(100),
&[
json!({ "a": 1, "b": 1 }),
json!({ "a": 1, "b": 2 }),
json!({ "a": 1, "b": 3 }),
],
);
assert_eq!(frames.iter().map(|f| f.0).collect::<Vec<_>>(), vec![true, false, false]);
}
#[test]
fn deltas_off_forces_a_keyframe_per_change() {
let frames = encode(
Config::default().with_delta_ratio(0),
&[json!({ "a": 1 }), json!({ "a": 2 })],
);
assert_eq!(frames.iter().map(|f| f.0).collect::<Vec<_>>(), vec![true, true]);
}
#[test]
fn deltas_off_still_skips_an_unchanged_value() {
let frames = encode(
Config::default().with_delta_ratio(0),
&[json!({ "a": 1 }), json!({ "a": 1 }), json!({ "a": 1 })],
);
assert_eq!(frames.len(), 1);
}
#[test]
fn deltas_off_detects_a_change_under_the_same_keys() {
let frames = encode(
Config::default().with_delta_ratio(0),
&[json!({ "a": 1, "b": 2 }), json!({ "a": 1, "b": 3 })],
);
assert_eq!(frames.len(), 2);
}
#[test]
fn deltas_off_still_exposes_the_value() {
let mut encoder = Encoder::<Value>::new(Config::default().with_delta_ratio(0));
assert_eq!(encoder.value(), None);
commit(&mut encoder, &json!({ "a": 1, "b": 2 })).unwrap();
assert_eq!(encoder.value(), Some(&json!({ "a": 1, "b": 2 })));
commit(&mut encoder, &json!({ "a": 1, "b": 3 })).unwrap();
assert_eq!(encoder.value(), Some(&json!({ "a": 1, "b": 3 })));
}
#[test]
fn deltas_off_while_compressing_keeps_the_plaintext_baseline() {
let mut config = Config::default().with_delta_ratio(0);
config.compression = Compression::Deflate;
let mut encoder = Encoder::<Value>::new(config);
commit(&mut encoder, &json!({ "a": 1 })).unwrap();
assert_eq!(encoder.value(), Some(&json!({ "a": 1 })));
assert!(commit(&mut encoder, &json!({ "a": 1 })).is_none());
}
#[test]
fn a_null_field_forces_a_keyframe() {
let frames = encode(
Config::default().with_delta_ratio(100),
&[json!({ "a": 1, "b": 1 }), json!({ "a": 1, "b": null })],
);
assert_eq!(frames.iter().map(|f| f.0).collect::<Vec<_>>(), vec![true, true]);
}
#[test]
fn a_non_object_root_forces_a_keyframe() {
let frames = encode(
Config::default().with_delta_ratio(100),
&[json!({ "a": 1 }), json!([1, 2, 3])],
);
assert_eq!(frames.iter().map(|f| f.0).collect::<Vec<_>>(), vec![true, true]);
}
#[test]
fn frame_cap_forces_a_keyframe() {
let values: Vec<Value> = (0..=MAX_DELTA_FRAMES).map(|n| json!({ "n": n })).collect();
let frames = encode(Config::default().with_delta_ratio(1_000_000), &values);
assert_eq!(frames.len(), MAX_DELTA_FRAMES + 1);
assert_eq!(frames.iter().filter(|f| f.0).count(), 2);
assert!(frames[MAX_DELTA_FRAMES].0);
}
#[test]
fn reset_forces_the_next_update_to_be_a_keyframe() {
let mut encoder = Encoder::<Value>::new(Config::default().with_delta_ratio(100));
assert!(commit(&mut encoder, &json!({ "a": 1 })).unwrap().keyframe);
assert!(!commit(&mut encoder, &json!({ "a": 2 })).unwrap().keyframe);
encoder.reset();
assert!(commit(&mut encoder, &json!({ "a": 3 })).unwrap().keyframe);
}
#[test]
fn an_uncommitted_frame_resynchronizes_the_encoder() {
let mut encoder = Encoder::<Value>::new(Config::default().with_delta_ratio(100));
commit(&mut encoder, &json!({ "a": 1 })).unwrap();
commit(&mut encoder, &json!({ "a": 2 })).unwrap();
drop(encoder.update(&json!({ "a": 3 })).unwrap().expect("a delta"));
let recovered = commit(&mut encoder, &json!({ "a": 4 })).expect("a resynchronizing snapshot");
assert!(recovered.keyframe);
assert_eq!(
serde_json::from_slice::<Value>(&recovered.payload).unwrap(),
json!({ "a": 4 }),
"the snapshot carries the whole value, not a patch"
);
}
#[test]
fn an_uncommitted_first_frame_is_reencoded() {
let mut encoder = Encoder::<Value>::new(Config::default());
drop(encoder.update(&json!({ "a": 1 })).unwrap().expect("a snapshot"));
let retried = commit(&mut encoder, &json!({ "a": 1 })).expect("the same value, re-encoded");
assert!(retried.keyframe);
}
#[test]
fn reset_republishes_an_unchanged_value() {
let mut encoder = Encoder::<Value>::new(Config::default());
commit(&mut encoder, &json!({ "a": 1 })).unwrap();
encoder.reset();
assert!(
commit(&mut encoder, &json!({ "a": 1 }))
.expect("a fresh snapshot")
.keyframe
);
}
#[test]
fn compressed_deltas_reuse_the_group_window() {
let phrase = "Media over QUIC delivers real-time latency at massive scale";
let frames = encode(
Config {
delta_ratio: 100,
compression: Compression::Deflate,
},
&[json!({ "note": phrase }), json!({ "note": phrase, "echo": phrase })],
);
let raw = serde_json::to_vec(&json!({ "echo": phrase })).unwrap().len();
assert_eq!(frames.len(), 2);
assert!(
frames[1].1 < raw / 2,
"windowed delta {} vs raw patch {raw}",
frames[1].1
);
}
struct Ticking(std::cell::Cell<u32>);
impl serde::Serialize for Ticking {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
use serde::ser::SerializeMap;
let n = self.0.get();
self.0.set(n + 1);
let mut map = serializer.serialize_map(Some(1))?;
map.serialize_entry("n", &n)?;
map.end()
}
}
#[test]
fn a_snapshot_serializes_its_value_once() {
let value = Ticking(std::cell::Cell::new(0));
let mut encoder = Encoder::<Ticking>::new(Config::default());
let payload = {
let frame = encoder.update(&value).unwrap().expect("a snapshot");
let payload = frame.payload.clone();
frame.commit();
payload
};
assert_eq!(value.0.get(), 1, "the value should be serialized exactly once");
let emitted: Value = serde_json::from_slice(&payload).unwrap();
assert_eq!(emitted, json!({ "n": 0 }));
assert_eq!(encoder.value(), Some(&emitted), "the baseline must be what was emitted");
}
#[test]
fn value_tracks_the_baseline() {
let mut encoder = Encoder::<Value>::new(Config::default().with_delta_ratio(100));
assert_eq!(encoder.value(), None);
commit(&mut encoder, &json!({ "a": 1, "b": 1 }));
commit(&mut encoder, &json!({ "a": 1, "b": 2 }));
assert_eq!(encoder.value(), Some(&json!({ "a": 1, "b": 2 })));
}
}