1mod diff;
13
14use std::marker::PhantomData;
15use std::ops::{Deref, DerefMut};
16use std::sync::{Arc, Mutex, MutexGuard};
17use std::task::Poll;
18
19use bytes::Bytes;
20use moq_flate::{Decoder, Encoder};
21use serde::Serialize;
22use serde::de::DeserializeOwned;
23use serde_json::Value;
24
25use crate::diff::diff;
26
27const MAX_DELTA_FRAMES: usize = 256;
32
33#[derive(thiserror::Error, Debug, Clone)]
35#[non_exhaustive]
36pub enum Error {
37 #[error(transparent)]
39 Net(#[from] moq_net::Error),
40
41 #[error("json: {0}")]
45 Json(String),
46
47 #[error(transparent)]
49 Flate(#[from] moq_flate::Error),
50}
51
52impl From<serde_json::Error> for Error {
53 fn from(err: serde_json::Error) -> Self {
54 Error::Json(err.to_string())
55 }
56}
57
58pub type Result<T> = std::result::Result<T, Error>;
60
61#[derive(Debug, Clone)]
66#[non_exhaustive]
67pub struct ProducerConfig {
68 pub delta_ratio: u32,
82
83 pub compression: bool,
89}
90
91impl Default for ProducerConfig {
92 fn default() -> Self {
93 Self {
94 delta_ratio: 8,
95 compression: false,
96 }
97 }
98}
99
100#[derive(Debug, Clone, Default)]
105#[non_exhaustive]
106pub struct ConsumerConfig {
107 pub compression: bool,
110}
111
112pub struct Producer<T> {
117 inner: Arc<Mutex<Inner>>,
118 _marker: PhantomData<fn(T)>,
119}
120
121impl<T> Clone for Producer<T> {
122 fn clone(&self) -> Self {
123 Self {
124 inner: self.inner.clone(),
125 _marker: PhantomData,
126 }
127 }
128}
129
130impl<T> Producer<T> {
131 pub fn consume(&self) -> moq_net::TrackConsumer {
133 self.inner.lock().unwrap().track.consume()
134 }
135}
136
137impl<T: Serialize> Producer<T> {
138 pub fn new(track: moq_net::TrackProducer, config: ProducerConfig) -> Self {
140 Self {
141 inner: Arc::new(Mutex::new(Inner {
142 track,
143 group: None,
144 encoder: None,
145 last: None,
146 delta_bytes: 0,
147 snapshot_len: 0,
148 group_frames: 0,
149 config,
150 })),
151 _marker: PhantomData,
152 }
153 }
154
155 pub fn update(&mut self, value: &T) -> Result<()> {
159 let json = serde_json::to_value(value)?;
160 let snapshot = serde_json::to_vec(value)?;
163 self.inner.lock().unwrap().update(json, snapshot)
164 }
165
166 pub fn lock(&mut self) -> Guard<'_, T>
177 where
178 T: Default + DeserializeOwned,
179 {
180 let inner = self.inner.lock().unwrap();
181 let value = inner
182 .last
183 .as_ref()
184 .and_then(|last| serde_json::from_value(last.clone()).ok())
185 .unwrap_or_default();
186
187 Guard {
188 inner,
189 value,
190 dirty: false,
191 }
192 }
193
194 pub fn finish(&mut self) -> Result<()> {
196 self.inner.lock().unwrap().finish()
197 }
198}
199
200pub struct Guard<'a, T: Serialize> {
205 inner: MutexGuard<'a, Inner>,
206 value: T,
207 dirty: bool,
208}
209
210impl<T: Serialize> Deref for Guard<'_, T> {
211 type Target = T;
212
213 fn deref(&self) -> &T {
214 &self.value
215 }
216}
217
218impl<T: Serialize> DerefMut for Guard<'_, T> {
219 fn deref_mut(&mut self) -> &mut T {
220 self.dirty = true;
221 &mut self.value
222 }
223}
224
225impl<T: Serialize> Drop for Guard<'_, T> {
226 fn drop(&mut self) {
227 if !self.dirty {
228 return;
229 }
230
231 let Ok(json) = serde_json::to_value(&self.value) else {
232 return;
233 };
234 let Ok(snapshot) = serde_json::to_vec(&self.value) else {
235 return;
236 };
237
238 let _ = self.inner.update(json, snapshot);
240 }
241}
242
243struct Inner {
245 track: moq_net::TrackProducer,
246 group: Option<moq_net::GroupProducer>,
247 encoder: Option<Encoder>,
249 last: Option<Value>,
250 delta_bytes: u64,
253 snapshot_len: u64,
256 group_frames: usize,
257 config: ProducerConfig,
258}
259
260impl Inner {
261 fn update(&mut self, json: Value, snapshot: Vec<u8>) -> Result<()> {
262 if self.last.as_ref() == Some(&json) {
263 return Ok(());
264 }
265
266 match self.delta(&json, snapshot.len())? {
267 Some(slice) => {
268 let group = self.group.as_mut().expect("delta requires an open group");
269 let len = slice.len() as u64;
270 group.write_frame(slice)?;
271 self.delta_bytes += len;
272 self.group_frames += 1;
273 }
274 None => self.snapshot(snapshot)?,
275 }
276
277 self.last = Some(json);
278 Ok(())
279 }
280
281 fn delta(&mut self, value: &Value, snapshot_len: usize) -> Result<Option<Bytes>> {
285 let ratio = self.config.delta_ratio;
286 if ratio == 0 {
287 return Ok(None);
288 }
289 if self.group.is_none() || self.group_frames >= MAX_DELTA_FRAMES {
290 return Ok(None);
291 }
292
293 let patch = {
294 let Some(last) = &self.last else {
295 return Ok(None);
296 };
297 let diff = diff(last, value);
298 if diff.forced_snapshot {
299 return Ok(None);
300 }
301 serde_json::to_vec(&diff.patch)?
302 };
303
304 match self.encoder.as_mut() {
305 Some(encoder) => {
310 let slice = encoder.frame(&patch);
311 let projected = self.delta_bytes + slice.len() as u64;
312 if projected > ratio as u64 * self.snapshot_len {
313 return Ok(None);
314 }
315 Ok(Some(slice))
316 }
317 None => {
319 let projected = self.delta_bytes + patch.len() as u64;
320 if projected > ratio as u64 * snapshot_len as u64 {
321 return Ok(None);
322 }
323 Ok(Some(Bytes::from(patch)))
324 }
325 }
326 }
327
328 fn snapshot(&mut self, snapshot: Vec<u8>) -> Result<()> {
330 if let Some(mut group) = self.group.take() {
332 group.finish()?;
333 }
334
335 let mut group = self.track.append_group()?;
336
337 let (slice, encoder) = if self.config.compression {
340 let mut encoder = Encoder::new();
341 let slice = encoder.frame(&snapshot);
342 (slice, Some(encoder))
343 } else {
344 (Bytes::from(snapshot), None)
345 };
346 self.snapshot_len = slice.len() as u64;
347 group.write_frame(slice)?;
348 self.delta_bytes = 0;
349 self.group_frames = 1;
350 self.encoder = encoder;
351
352 if self.config.delta_ratio != 0 {
353 self.group = Some(group);
355 } else {
356 self.encoder = None;
358 group.finish()?;
359 }
360
361 Ok(())
362 }
363
364 fn finish(&mut self) -> Result<()> {
365 if let Some(mut group) = self.group.take() {
366 group.finish()?;
367 }
368 self.track.finish()?;
369 Ok(())
370 }
371}
372
373pub struct Consumer<T> {
375 track: moq_net::TrackConsumer,
376 group: Option<moq_net::GroupConsumer>,
377 compressed: bool,
379 decoder: Option<Decoder>,
381 group_slices: Vec<Bytes>,
384 current: Option<Value>,
385 frames_read: usize,
386 _marker: PhantomData<fn() -> T>,
387}
388
389impl<T> Clone for Consumer<T> {
392 fn clone(&self) -> Self {
393 Self {
394 track: self.track.clone(),
395 group: self.group.clone(),
396 compressed: self.compressed,
397 decoder: None,
400 group_slices: self.group_slices.clone(),
401 current: self.current.clone(),
402 frames_read: self.frames_read,
403 _marker: PhantomData,
404 }
405 }
406}
407
408impl<T: DeserializeOwned> Consumer<T> {
409 pub fn new(track: moq_net::TrackConsumer, config: ConsumerConfig) -> Self {
414 Self {
415 track,
416 group: None,
417 compressed: config.compression,
418 decoder: None,
419 group_slices: Vec::new(),
420 current: None,
421 frames_read: 0,
422 _marker: PhantomData,
423 }
424 }
425
426 pub async fn next(&mut self) -> Result<Option<T>>
428 where
429 T: Unpin,
430 {
431 kio::wait(|waiter| self.poll_next(waiter)).await
432 }
433
434 pub fn poll_next(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<T>>> {
444 let track_finished = loop {
446 match self.track.poll_next_group(waiter)? {
447 Poll::Ready(Some(group)) => {
448 self.group = Some(group);
449 self.current = None;
450 self.frames_read = 0;
451 self.decoder = None;
453 self.group_slices.clear();
454 }
455 Poll::Ready(None) => break true,
456 Poll::Pending => break false,
457 }
458 };
459
460 let mut advanced = false;
465 let mut group_pending = false;
466 while let Some(group) = &mut self.group {
467 match group.poll_read_frame(waiter)? {
468 Poll::Ready(Some(frame)) => {
469 self.apply(frame)?;
470 advanced = true;
471 }
472 Poll::Ready(None) => {
474 self.group = None;
475 break;
476 }
477 Poll::Pending => {
479 group_pending = true;
480 break;
481 }
482 }
483 }
484
485 if advanced {
486 return Poll::Ready(Ok(Some(self.reconstruct()?)));
488 }
489
490 if group_pending {
493 return Poll::Pending;
494 }
495
496 if track_finished {
497 Poll::Ready(Ok(None))
498 } else {
499 Poll::Pending
500 }
501 }
502
503 fn decode(&mut self, slice: Bytes) -> Result<Bytes> {
509 if !self.compressed {
510 return Ok(slice);
511 }
512
513 if self.decoder.is_none() {
514 let mut decoder = Decoder::new();
515 for prev in &self.group_slices {
516 decoder.frame(prev)?;
517 }
518 self.decoder = Some(decoder);
519 }
520
521 let plain = self.decoder.as_mut().unwrap().frame(&slice)?;
522 self.group_slices.push(slice);
523 Ok(plain)
524 }
525
526 fn apply(&mut self, frame: Bytes) -> Result<()> {
529 let frame = self.decode(frame)?;
530 if self.frames_read == 0 {
531 self.current = Some(serde_json::from_slice(&frame)?);
532 } else {
533 let patch: Value = serde_json::from_slice(&frame)?;
534 let current = self.current.as_mut().expect("a snapshot precedes any delta");
535 json_patch::merge(current, &patch);
536 }
537 self.frames_read += 1;
538 Ok(())
539 }
540
541 fn reconstruct(&self) -> Result<T> {
544 let current = self
545 .current
546 .as_ref()
547 .expect("a value is present after applying a frame");
548 Ok(serde_json::from_value(current.clone())?)
549 }
550}
551
552#[cfg(test)]
553mod test {
554 use super::*;
555 use serde_json::json;
556
557 fn cfg(delta_ratio: u32) -> ProducerConfig {
559 ProducerConfig {
560 delta_ratio,
561 ..Default::default()
562 }
563 }
564
565 fn cfg_deflate(delta_ratio: u32) -> ProducerConfig {
567 ProducerConfig {
568 delta_ratio,
569 compression: true,
570 }
571 }
572
573 fn deflate_consumer(track: moq_net::TrackConsumer) -> Consumer<Value> {
575 Consumer::new(track, ConsumerConfig { compression: true })
576 }
577
578 fn producer(config: ProducerConfig) -> (Producer<Value>, moq_net::TrackConsumer) {
579 let track = moq_net::Track::new("test").produce();
580 let consumer = track.consume();
581 (Producer::new(track, config), consumer)
582 }
583
584 fn drain(track: moq_net::TrackConsumer) -> Vec<Value> {
586 drain_with(Consumer::<Value>::new(track, ConsumerConfig::default()))
587 }
588
589 fn drain_with(mut consumer: Consumer<Value>) -> Vec<Value> {
591 let waiter = kio::Waiter::noop();
592 let mut out = Vec::new();
593 while let Poll::Ready(Ok(Some(value))) = consumer.poll_next(&waiter) {
594 out.push(value);
595 }
596 out
597 }
598
599 #[test]
600 fn deltas_off_snapshot_per_group() {
601 let (mut producer, track) = producer(cfg(0));
602 producer.update(&json!({ "a": 1 })).unwrap();
603 producer.update(&json!({ "a": 2 })).unwrap();
604 producer.finish().unwrap();
605
606 assert_eq!(track.latest(), Some(1));
609 assert_eq!(drain(track), vec![json!({ "a": 2 })]);
610 }
611
612 #[test]
613 fn live_consumer_sees_each_update() {
614 let (mut producer, track) = producer(ProducerConfig::default());
615 let mut consumer = Consumer::<Value>::new(track, ConsumerConfig::default());
616 let waiter = kio::Waiter::noop();
617
618 for n in 1..=3 {
619 producer.update(&json!({ "a": n })).unwrap();
620 match consumer.poll_next(&waiter) {
621 Poll::Ready(Ok(Some(value))) => assert_eq!(value, json!({ "a": n })),
622 other => panic!("expected value, got {other:?}"),
623 }
624 }
625 }
626
627 #[test]
628 fn unchanged_value_writes_nothing() {
629 let (mut producer, track) = producer(ProducerConfig::default());
630 producer.update(&json!({ "a": 1 })).unwrap();
631 producer.update(&json!({ "a": 1 })).unwrap();
632 producer.finish().unwrap();
633
634 assert_eq!(track.latest(), Some(0));
635 assert_eq!(drain(track), vec![json!({ "a": 1 })]);
636 }
637
638 #[test]
639 fn deltas_share_one_group() {
640 let config = cfg(100);
641 let (mut producer, track) = producer(config);
642 producer.update(&json!({ "a": 1, "b": 1 })).unwrap();
643 producer.update(&json!({ "a": 1, "b": 2 })).unwrap();
644 producer.update(&json!({ "a": 1, "b": 3 })).unwrap();
645 producer.finish().unwrap();
646
647 assert_eq!(track.latest(), Some(0));
649 let values = drain(track);
650 assert_eq!(values.last().unwrap(), &json!({ "a": 1, "b": 3 }));
651 }
652
653 #[test]
654 fn tight_ratio_rolls_snapshots() {
655 let config = cfg(1);
659 let (mut producer, track) = producer(config);
660 producer.update(&json!({ "a": 1 })).unwrap(); producer.update(&json!({ "a": 2 })).unwrap(); producer.update(&json!({ "a": 3 })).unwrap(); producer.update(&json!({ "a": 4 })).unwrap(); producer.finish().unwrap();
665
666 assert_eq!(track.latest(), Some(1));
667 }
668
669 #[test]
670 fn deltas_stay_within_ratio_times_snapshot() {
671 let config = cfg(8);
676 let (mut producer, track) = producer(config);
677 for n in 0..=9 {
678 producer.update(&json!({ "n": n })).unwrap();
679 }
680 producer.finish().unwrap();
681
682 assert_eq!(track.latest(), Some(1));
684 assert_eq!(drain(track).last().unwrap(), &json!({ "n": 9 }));
685 }
686
687 #[test]
688 fn array_change_is_delta() {
689 let config = cfg(100);
690 let (mut producer, track) = producer(config);
691 producer.update(&json!({ "list": [1, 2] })).unwrap();
692 producer.update(&json!({ "list": [1, 2, 3] })).unwrap();
693 producer.finish().unwrap();
694
695 assert_eq!(track.latest(), Some(0));
697 assert_eq!(drain(track).last().unwrap(), &json!({ "list": [1, 2, 3] }));
698 }
699
700 #[test]
701 fn frame_cap_rolls_snapshot() {
702 let config = cfg(1_000_000);
703 let (mut producer, track) = producer(config);
704 for i in 0..=MAX_DELTA_FRAMES {
706 producer.update(&json!({ "n": i })).unwrap();
707 }
708 producer.finish().unwrap();
709
710 assert_eq!(track.latest(), Some(1));
712 assert_eq!(drain(track).last().unwrap(), &json!({ "n": MAX_DELTA_FRAMES }));
713 }
714
715 #[test]
716 fn late_joiner_reconstructs_from_deltas() {
717 let config = cfg(100);
718 let (mut producer, track) = producer(config);
719 producer.update(&json!({ "a": 1, "b": 1 })).unwrap();
720 producer.update(&json!({ "a": 1, "b": 2 })).unwrap();
721 producer.update(&json!({ "a": 5, "b": 2 })).unwrap();
722 producer.finish().unwrap();
723
724 assert_eq!(drain(track).last().unwrap(), &json!({ "a": 5, "b": 2 }));
726 }
727
728 #[test]
729 fn lock_composes_independent_owners() {
730 #[derive(serde::Serialize, serde::Deserialize, Default, PartialEq, Debug)]
732 struct Doc {
733 #[serde(skip_serializing_if = "Option::is_none")]
734 video: Option<String>,
735 #[serde(skip_serializing_if = "Option::is_none")]
736 scte35: Option<u32>,
737 }
738
739 let track = moq_net::Track::new("test").produce();
740 let consumer = track.consume();
741 let mut producer = Producer::<Doc>::new(track, ProducerConfig::default());
742
743 producer.lock().video = Some("v1".to_string());
745
746 producer.lock().scte35 = Some(42);
748
749 let _ = producer.lock();
751
752 producer.finish().unwrap();
753
754 let mut consumer = Consumer::<Doc>::new(consumer, ConsumerConfig::default());
755 let waiter = kio::Waiter::noop();
756 let mut last = None;
757 while let Poll::Ready(Ok(Some(value))) = consumer.poll_next(&waiter) {
758 last = Some(value);
759 }
760 assert_eq!(
761 last.unwrap(),
762 Doc {
763 video: Some("v1".to_string()),
764 scte35: Some(42),
765 }
766 );
767 }
768
769 #[test]
770 fn newer_group_supersedes_in_progress_reconstruction() {
771 let config = cfg(1);
773 let (mut producer, track) = producer(config);
774 let observer = producer.consume();
775 let mut consumer = Consumer::<Value>::new(track, ConsumerConfig::default());
776 let waiter = kio::Waiter::noop();
777
778 producer.update(&json!({ "a": 1 })).unwrap(); match consumer.poll_next(&waiter) {
780 Poll::Ready(Ok(Some(value))) => assert_eq!(value, json!({ "a": 1 })),
781 other => panic!("expected first value, got {other:?}"),
782 }
783
784 producer.update(&json!({ "a": 2 })).unwrap(); producer.update(&json!({ "a": 3 })).unwrap(); producer.finish().unwrap();
787 assert_eq!(observer.latest(), Some(1));
788
789 let mut last = None;
791 while let Poll::Ready(Ok(Some(value))) = consumer.poll_next(&waiter) {
792 last = Some(value);
793 }
794 assert_eq!(last.unwrap(), json!({ "a": 3 }));
795 }
796
797 #[test]
798 fn cloned_consumer_reconstructs_independently() {
799 let config = cfg(100);
801 let (mut producer, track) = producer(config);
802 let mut consumer = Consumer::<Value>::new(track, ConsumerConfig::default());
803 let waiter = kio::Waiter::noop();
804
805 producer.update(&json!({ "a": 1, "b": 1 })).unwrap(); match consumer.poll_next(&waiter) {
807 Poll::Ready(Ok(Some(value))) => assert_eq!(value, json!({ "a": 1, "b": 1 })),
808 other => panic!("expected snapshot, got {other:?}"),
809 }
810
811 let mut clone = consumer.clone();
813
814 producer.update(&json!({ "a": 1, "b": 2 })).unwrap(); producer.finish().unwrap();
816
817 let expected = json!({ "a": 1, "b": 2 });
819 for consumer in [&mut consumer, &mut clone] {
820 match consumer.poll_next(&waiter) {
821 Poll::Ready(Ok(Some(value))) => assert_eq!(value, expected),
822 other => panic!("expected delta, got {other:?}"),
823 }
824 }
825 }
826
827 #[test]
828 fn open_group_pends_after_track_finish() {
829 let mut track = moq_net::Track::new("test").produce();
832 let mut group = track.append_group().unwrap();
833 track.finish().unwrap();
834
835 let mut consumer = Consumer::<Value>::new(track.consume(), ConsumerConfig::default());
836 let waiter = kio::Waiter::noop();
837
838 assert!(matches!(consumer.poll_next(&waiter), Poll::Pending));
840
841 group
842 .write_frame(Bytes::from(serde_json::to_vec(&json!({ "a": 1 })).unwrap()))
843 .unwrap();
844 group.finish().unwrap();
845
846 match consumer.poll_next(&waiter) {
847 Poll::Ready(Ok(Some(value))) => assert_eq!(value, json!({ "a": 1 })),
848 other => panic!("expected the catalog value, got {other:?}"),
849 }
850 }
851
852 #[test]
853 fn late_joiner_collapses_backlog_to_latest() {
854 let (mut producer, track) = producer(cfg(100));
857 for n in 0..=20 {
858 producer.update(&json!({ "n": n })).unwrap();
859 }
860 producer.finish().unwrap();
861
862 assert_eq!(track.latest(), Some(0));
864 let values = drain(track);
865 assert_eq!(
866 values,
867 vec![json!({ "n": 20 })],
868 "backlog should collapse to the latest value"
869 );
870 }
871
872 #[test]
873 fn compressed_late_joiner_collapses_backlog_to_latest() {
874 let (mut producer, track) = producer(cfg_deflate(100));
876 for n in 0..=20 {
877 producer.update(&json!({ "n": n })).unwrap();
878 }
879 producer.finish().unwrap();
880
881 assert_eq!(track.latest(), Some(0));
882 let values = drain_with(deflate_consumer(track));
883 assert_eq!(
884 values,
885 vec![json!({ "n": 20 })],
886 "compressed backlog should collapse to the latest"
887 );
888 }
889
890 #[test]
891 fn compressed_snapshot_per_group_roundtrips() {
892 let (mut producer, track) = producer(cfg_deflate(0));
893 producer.update(&json!({ "a": 1 })).unwrap();
894 producer.update(&json!({ "a": 2 })).unwrap();
895 producer.finish().unwrap();
896
897 assert_eq!(track.latest(), Some(1));
899 let values = drain_with(deflate_consumer(track));
900 assert_eq!(values, vec![json!({ "a": 2 })]);
901 }
902
903 #[test]
904 fn compressed_deltas_share_one_group() {
905 let (mut producer, track) = producer(cfg_deflate(100));
906 producer.update(&json!({ "a": 1, "b": 1 })).unwrap();
907 producer.update(&json!({ "a": 1, "b": 2 })).unwrap();
908 producer.update(&json!({ "a": 1, "b": 3 })).unwrap();
909 producer.finish().unwrap();
910
911 assert_eq!(track.latest(), Some(0));
913 let values = drain_with(deflate_consumer(track));
914 assert_eq!(values.last().unwrap(), &json!({ "a": 1, "b": 3 }));
915 }
916
917 #[test]
918 fn compressed_late_joiner_reconstructs_from_deltas() {
919 let (mut producer, track) = producer(cfg_deflate(100));
920 producer.update(&json!({ "a": 1, "b": 1 })).unwrap();
921 producer.update(&json!({ "a": 1, "b": 2 })).unwrap();
922 producer.update(&json!({ "a": 5, "b": 2 })).unwrap();
923 producer.finish().unwrap();
924
925 let values = drain_with(deflate_consumer(track));
927 assert_eq!(values.last().unwrap(), &json!({ "a": 5, "b": 2 }));
928 }
929
930 #[test]
931 fn compressed_cloned_consumer_reconstructs_mid_group() {
932 let (mut producer, track) = producer(cfg_deflate(100));
934 let mut consumer = deflate_consumer(track);
935 let waiter = kio::Waiter::noop();
936
937 producer.update(&json!({ "a": 1, "b": 1 })).unwrap(); match consumer.poll_next(&waiter) {
939 Poll::Ready(Ok(Some(value))) => assert_eq!(value, json!({ "a": 1, "b": 1 })),
940 other => panic!("expected snapshot, got {other:?}"),
941 }
942
943 let mut clone = consumer.clone();
944
945 producer.update(&json!({ "a": 1, "b": 2 })).unwrap(); producer.finish().unwrap();
947
948 let expected = json!({ "a": 1, "b": 2 });
949 for consumer in [&mut consumer, &mut clone] {
950 match consumer.poll_next(&waiter) {
951 Poll::Ready(Ok(Some(value))) => assert_eq!(value, expected),
952 other => panic!("expected delta, got {other:?}"),
953 }
954 }
955 }
956
957 #[test]
958 fn compression_shrinks_wire_frames() {
959 let value = json!({ "renditions": ["video".repeat(50), "video".repeat(50), "video".repeat(50)] });
961
962 let plaintext_bytes = wire_frame_len(cfg(0), &value);
963 let compressed_bytes = wire_frame_len(cfg_deflate(0), &value);
964 assert!(
965 compressed_bytes < plaintext_bytes,
966 "compressed frame {compressed_bytes} should be smaller than plaintext {plaintext_bytes}"
967 );
968 }
969
970 #[test]
971 fn compressed_deltas_reuse_window() {
972 let (mut producer, mut track) = producer(cfg_deflate(100));
975 let phrase = "Media over QUIC delivers real-time latency at massive scale";
976 producer.update(&json!({ "note": phrase })).unwrap();
977 producer.update(&json!({ "note": phrase, "echo": phrase })).unwrap();
978 producer.finish().unwrap();
979
980 let waiter = kio::Waiter::noop();
982 let Poll::Ready(Ok(Some(mut group))) = track.poll_next_group(&waiter) else {
983 panic!("expected a group");
984 };
985 let mut frames = Vec::new();
986 while let Poll::Ready(Ok(Some(frame))) = group.poll_read_frame(&waiter) {
987 frames.push(frame);
988 }
989 assert_eq!(frames.len(), 2, "snapshot + one delta in a single group");
990
991 let raw_delta = serde_json::to_vec(&json!({ "echo": phrase })).unwrap();
993 assert!(
994 frames[1].len() < raw_delta.len() / 2,
995 "windowed delta {} should be far below the raw patch {}",
996 frames[1].len(),
997 raw_delta.len()
998 );
999 }
1000
1001 fn wire_frame_len(config: ProducerConfig, value: &Value) -> usize {
1003 let (mut producer, mut track) = producer(config);
1004 producer.update(value).unwrap();
1005 producer.finish().unwrap();
1006
1007 let waiter = kio::Waiter::noop();
1008 let Poll::Ready(Ok(Some(mut group))) = track.poll_next_group(&waiter) else {
1009 panic!("expected a group");
1010 };
1011 let Poll::Ready(Ok(Some(frame))) = group.poll_read_frame(&waiter) else {
1013 panic!("expected a frame");
1014 };
1015 frame.len()
1016 }
1017}