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
25pub use crate::diff::{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,
83
84 pub compression: bool,
90}
91
92impl Default for ProducerConfig {
93 fn default() -> Self {
94 Self {
95 delta_ratio: 8,
96 compression: false,
97 }
98 }
99}
100
101#[derive(Debug, Clone, Default)]
106#[non_exhaustive]
107pub struct ConsumerConfig {
108 pub compression: bool,
111}
112
113pub struct Producer<T> {
118 inner: Arc<Mutex<Inner>>,
119 _marker: PhantomData<fn(T)>,
120}
121
122impl<T> Clone for Producer<T> {
123 fn clone(&self) -> Self {
124 Self {
125 inner: self.inner.clone(),
126 _marker: PhantomData,
127 }
128 }
129}
130
131impl<T> Producer<T> {
132 pub fn consume(&self) -> moq_net::TrackConsumer {
134 self.inner.lock().unwrap().track.consume()
135 }
136}
137
138impl<T: Serialize> Producer<T> {
139 pub fn new(track: moq_net::TrackProducer, config: ProducerConfig) -> Self {
141 Self {
142 inner: Arc::new(Mutex::new(Inner {
143 track,
144 group: None,
145 encoder: None,
146 last: None,
147 delta_bytes: 0,
148 snapshot_len: 0,
149 group_frames: 0,
150 config,
151 })),
152 _marker: PhantomData,
153 }
154 }
155
156 pub fn update(&mut self, value: &T) -> Result<()> {
160 self.inner.lock().unwrap().update(value)
161 }
162
163 pub fn lock(&mut self) -> Guard<'_, T>
174 where
175 T: Default + DeserializeOwned,
176 {
177 let inner = self.inner.lock().unwrap();
178 let value = inner
179 .last
180 .as_ref()
181 .and_then(|last| serde_json::from_value(last.clone()).ok())
182 .unwrap_or_default();
183
184 Guard {
185 inner,
186 value,
187 dirty: false,
188 }
189 }
190
191 pub fn finish(&mut self) -> Result<()> {
193 self.inner.lock().unwrap().finish()
194 }
195}
196
197pub struct Guard<'a, T: Serialize> {
202 inner: MutexGuard<'a, Inner>,
203 value: T,
204 dirty: bool,
205}
206
207impl<T: Serialize> Deref for Guard<'_, T> {
208 type Target = T;
209
210 fn deref(&self) -> &T {
211 &self.value
212 }
213}
214
215impl<T: Serialize> DerefMut for Guard<'_, T> {
216 fn deref_mut(&mut self) -> &mut T {
217 self.dirty = true;
218 &mut self.value
219 }
220}
221
222impl<T: Serialize> Drop for Guard<'_, T> {
223 fn drop(&mut self) {
224 if !self.dirty {
225 return;
226 }
227
228 let _ = self.inner.update(&self.value);
230 }
231}
232
233struct Inner {
235 track: moq_net::TrackProducer,
236 group: Option<moq_net::GroupProducer>,
237 encoder: Option<Encoder>,
239 last: Option<Value>,
240 delta_bytes: u64,
243 snapshot_len: u64,
246 group_frames: usize,
247 config: ProducerConfig,
248}
249
250impl Inner {
251 fn update<T: Serialize>(&mut self, value: &T) -> Result<()> {
252 let Some(last) = self.last.as_ref() else {
255 return self.snapshot(value);
256 };
257
258 let Diff { patch, forced_snapshot } = diff(last, value);
260
261 if !forced_snapshot && patch.as_object().is_some_and(serde_json::Map::is_empty) {
263 return Ok(());
264 }
265
266 if forced_snapshot || !self.delta_allowed() {
269 return self.snapshot(value);
270 }
271
272 let bytes = serde_json::to_vec(&patch)?;
274 let slice = match self.encoder.as_mut() {
275 Some(encoder) => encoder.frame(&bytes),
276 None => Bytes::from(bytes),
277 };
278 let len = slice.len() as u64;
279 self.group
280 .as_mut()
281 .expect("delta_allowed guarantees an open group")
282 .write_frame(slice)?;
283 self.delta_bytes += len;
284 self.group_frames += 1;
285
286 json_patch::merge(self.last.as_mut().expect("a snapshot precedes any delta"), &patch);
288 Ok(())
289 }
290
291 fn delta_allowed(&self) -> bool {
299 let ratio = self.config.delta_ratio as u64;
300 ratio != 0
301 && self.group.is_some()
302 && self.group_frames < MAX_DELTA_FRAMES
303 && self.delta_bytes <= ratio * self.snapshot_len
304 }
305
306 fn snapshot<T: Serialize>(&mut self, value: &T) -> Result<()> {
308 let snapshot = serde_json::to_vec(value)?;
311
312 if let Some(mut group) = self.group.take() {
314 group.finish()?;
315 }
316
317 let mut group = self.track.append_group()?;
318
319 let (slice, encoder) = if self.config.compression {
322 let mut encoder = Encoder::new();
323 let slice = encoder.frame(&snapshot);
324 (slice, Some(encoder))
325 } else {
326 (Bytes::from(snapshot), None)
327 };
328 self.snapshot_len = slice.len() as u64;
329 group.write_frame(slice)?;
330 self.delta_bytes = 0;
331 self.group_frames = 1;
332 self.encoder = encoder;
333
334 if self.config.delta_ratio != 0 {
335 self.group = Some(group);
337 } else {
338 self.encoder = None;
340 group.finish()?;
341 }
342
343 self.last = Some(serde_json::to_value(value)?);
345 Ok(())
346 }
347
348 fn finish(&mut self) -> Result<()> {
349 if let Some(mut group) = self.group.take() {
350 group.finish()?;
351 }
352 self.track.finish()?;
353 Ok(())
354 }
355}
356
357pub struct Consumer<T> {
359 track: moq_net::TrackConsumer,
360 group: Option<moq_net::GroupConsumer>,
361 compressed: bool,
363 decoder: Option<Decoder>,
365 group_slices: Vec<Bytes>,
368 current: Option<Value>,
369 frames_read: usize,
370 _marker: PhantomData<fn() -> T>,
371}
372
373impl<T> Clone for Consumer<T> {
376 fn clone(&self) -> Self {
377 Self {
378 track: self.track.clone(),
379 group: self.group.clone(),
380 compressed: self.compressed,
381 decoder: None,
384 group_slices: self.group_slices.clone(),
385 current: self.current.clone(),
386 frames_read: self.frames_read,
387 _marker: PhantomData,
388 }
389 }
390}
391
392impl<T: DeserializeOwned> Consumer<T> {
393 pub fn new(track: moq_net::TrackConsumer, config: ConsumerConfig) -> Self {
398 Self {
399 track,
400 group: None,
401 compressed: config.compression,
402 decoder: None,
403 group_slices: Vec::new(),
404 current: None,
405 frames_read: 0,
406 _marker: PhantomData,
407 }
408 }
409
410 pub async fn next(&mut self) -> Result<Option<T>>
412 where
413 T: Unpin,
414 {
415 kio::wait(|waiter| self.poll_next(waiter)).await
416 }
417
418 pub fn poll_next(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<T>>> {
428 let track_finished = loop {
430 match self.track.poll_next_group(waiter)? {
431 Poll::Ready(Some(group)) => {
432 self.group = Some(group);
433 self.current = None;
434 self.frames_read = 0;
435 self.decoder = None;
437 self.group_slices.clear();
438 }
439 Poll::Ready(None) => break true,
440 Poll::Pending => break false,
441 }
442 };
443
444 let mut advanced = false;
449 let mut group_pending = false;
450 while let Some(group) = &mut self.group {
451 match group.poll_read_frame(waiter)? {
452 Poll::Ready(Some(frame)) => {
453 self.apply(frame)?;
454 advanced = true;
455 }
456 Poll::Ready(None) => {
458 self.group = None;
459 break;
460 }
461 Poll::Pending => {
463 group_pending = true;
464 break;
465 }
466 }
467 }
468
469 if advanced {
470 return Poll::Ready(Ok(Some(self.reconstruct()?)));
472 }
473
474 if group_pending {
477 return Poll::Pending;
478 }
479
480 if track_finished {
481 Poll::Ready(Ok(None))
482 } else {
483 Poll::Pending
484 }
485 }
486
487 fn decode(&mut self, slice: Bytes) -> Result<Bytes> {
493 if !self.compressed {
494 return Ok(slice);
495 }
496
497 if self.decoder.is_none() {
498 let mut decoder = Decoder::new();
499 for prev in &self.group_slices {
500 decoder.frame(prev)?;
501 }
502 self.decoder = Some(decoder);
503 }
504
505 let plain = self.decoder.as_mut().unwrap().frame(&slice)?;
506 self.group_slices.push(slice);
507 Ok(plain)
508 }
509
510 fn apply(&mut self, frame: Bytes) -> Result<()> {
513 let frame = self.decode(frame)?;
514 if self.frames_read == 0 {
515 self.current = Some(serde_json::from_slice(&frame)?);
516 } else {
517 let patch: Value = serde_json::from_slice(&frame)?;
518 let current = self.current.as_mut().expect("a snapshot precedes any delta");
519 json_patch::merge(current, &patch);
520 }
521 self.frames_read += 1;
522 Ok(())
523 }
524
525 fn reconstruct(&self) -> Result<T> {
528 let current = self
529 .current
530 .as_ref()
531 .expect("a value is present after applying a frame");
532 Ok(serde_json::from_value(current.clone())?)
533 }
534}
535
536#[cfg(test)]
537mod test {
538 use super::*;
539 use serde_json::json;
540
541 fn cfg(delta_ratio: u32) -> ProducerConfig {
543 ProducerConfig {
544 delta_ratio,
545 ..Default::default()
546 }
547 }
548
549 fn cfg_deflate(delta_ratio: u32) -> ProducerConfig {
551 ProducerConfig {
552 delta_ratio,
553 compression: true,
554 }
555 }
556
557 fn deflate_consumer(track: moq_net::TrackConsumer) -> Consumer<Value> {
559 Consumer::new(track, ConsumerConfig { compression: true })
560 }
561
562 fn producer(config: ProducerConfig) -> (Producer<Value>, moq_net::TrackConsumer) {
563 let track = moq_net::Track::new("test").produce();
564 let consumer = track.consume();
565 (Producer::new(track, config), consumer)
566 }
567
568 fn drain(track: moq_net::TrackConsumer) -> Vec<Value> {
570 drain_with(Consumer::<Value>::new(track, ConsumerConfig::default()))
571 }
572
573 fn drain_with(mut consumer: Consumer<Value>) -> Vec<Value> {
575 let waiter = kio::Waiter::noop();
576 let mut out = Vec::new();
577 while let Poll::Ready(Ok(Some(value))) = consumer.poll_next(&waiter) {
578 out.push(value);
579 }
580 out
581 }
582
583 #[test]
584 fn deltas_off_snapshot_per_group() {
585 let (mut producer, track) = producer(cfg(0));
586 producer.update(&json!({ "a": 1 })).unwrap();
587 producer.update(&json!({ "a": 2 })).unwrap();
588 producer.finish().unwrap();
589
590 assert_eq!(track.latest(), Some(1));
593 assert_eq!(drain(track), vec![json!({ "a": 2 })]);
594 }
595
596 #[test]
597 fn live_consumer_sees_each_update() {
598 let (mut producer, track) = producer(ProducerConfig::default());
599 let mut consumer = Consumer::<Value>::new(track, ConsumerConfig::default());
600 let waiter = kio::Waiter::noop();
601
602 for n in 1..=3 {
603 producer.update(&json!({ "a": n })).unwrap();
604 match consumer.poll_next(&waiter) {
605 Poll::Ready(Ok(Some(value))) => assert_eq!(value, json!({ "a": n })),
606 other => panic!("expected value, got {other:?}"),
607 }
608 }
609 }
610
611 #[test]
612 fn unchanged_value_writes_nothing() {
613 let (mut producer, track) = producer(ProducerConfig::default());
614 producer.update(&json!({ "a": 1 })).unwrap();
615 producer.update(&json!({ "a": 1 })).unwrap();
616 producer.finish().unwrap();
617
618 assert_eq!(track.latest(), Some(0));
619 assert_eq!(drain(track), vec![json!({ "a": 1 })]);
620 }
621
622 #[test]
623 fn deltas_share_one_group() {
624 let config = cfg(100);
625 let (mut producer, track) = producer(config);
626 producer.update(&json!({ "a": 1, "b": 1 })).unwrap();
627 producer.update(&json!({ "a": 1, "b": 2 })).unwrap();
628 producer.update(&json!({ "a": 1, "b": 3 })).unwrap();
629 producer.finish().unwrap();
630
631 assert_eq!(track.latest(), Some(0));
633 let values = drain(track);
634 assert_eq!(values.last().unwrap(), &json!({ "a": 1, "b": 3 }));
635 }
636
637 #[test]
638 fn tight_ratio_rolls_snapshots() {
639 let config = cfg(1);
644 let (mut producer, track) = producer(config);
645 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();
650
651 assert_eq!(track.latest(), Some(1));
652 }
653
654 #[test]
655 fn deltas_stay_within_ratio_times_snapshot() {
656 let config = cfg(8);
662 let (mut producer, track) = producer(config);
663 for n in 0..=10 {
664 producer.update(&json!({ "n": n })).unwrap();
665 }
666 producer.finish().unwrap();
667
668 assert_eq!(track.latest(), Some(1));
670 assert_eq!(drain(track).last().unwrap(), &json!({ "n": 10 }));
671 }
672
673 #[test]
674 fn array_change_is_delta() {
675 let config = cfg(100);
676 let (mut producer, track) = producer(config);
677 producer.update(&json!({ "list": [1, 2] })).unwrap();
678 producer.update(&json!({ "list": [1, 2, 3] })).unwrap();
679 producer.finish().unwrap();
680
681 assert_eq!(track.latest(), Some(0));
683 assert_eq!(drain(track).last().unwrap(), &json!({ "list": [1, 2, 3] }));
684 }
685
686 #[test]
687 fn frame_cap_rolls_snapshot() {
688 let config = cfg(1_000_000);
689 let (mut producer, track) = producer(config);
690 for i in 0..=MAX_DELTA_FRAMES {
692 producer.update(&json!({ "n": i })).unwrap();
693 }
694 producer.finish().unwrap();
695
696 assert_eq!(track.latest(), Some(1));
698 assert_eq!(drain(track).last().unwrap(), &json!({ "n": MAX_DELTA_FRAMES }));
699 }
700
701 #[test]
702 fn late_joiner_reconstructs_from_deltas() {
703 let config = cfg(100);
704 let (mut producer, track) = producer(config);
705 producer.update(&json!({ "a": 1, "b": 1 })).unwrap();
706 producer.update(&json!({ "a": 1, "b": 2 })).unwrap();
707 producer.update(&json!({ "a": 5, "b": 2 })).unwrap();
708 producer.finish().unwrap();
709
710 assert_eq!(drain(track).last().unwrap(), &json!({ "a": 5, "b": 2 }));
712 }
713
714 #[test]
715 fn lock_composes_independent_owners() {
716 #[derive(serde::Serialize, serde::Deserialize, Default, PartialEq, Debug)]
718 struct Doc {
719 #[serde(skip_serializing_if = "Option::is_none")]
720 video: Option<String>,
721 #[serde(skip_serializing_if = "Option::is_none")]
722 scte35: Option<u32>,
723 }
724
725 let track = moq_net::Track::new("test").produce();
726 let consumer = track.consume();
727 let mut producer = Producer::<Doc>::new(track, ProducerConfig::default());
728
729 producer.lock().video = Some("v1".to_string());
731
732 producer.lock().scte35 = Some(42);
734
735 let _ = producer.lock();
737
738 producer.finish().unwrap();
739
740 let mut consumer = Consumer::<Doc>::new(consumer, ConsumerConfig::default());
741 let waiter = kio::Waiter::noop();
742 let mut last = None;
743 while let Poll::Ready(Ok(Some(value))) = consumer.poll_next(&waiter) {
744 last = Some(value);
745 }
746 assert_eq!(
747 last.unwrap(),
748 Doc {
749 video: Some("v1".to_string()),
750 scte35: Some(42),
751 }
752 );
753 }
754
755 #[test]
756 fn newer_group_supersedes_in_progress_reconstruction() {
757 let config = cfg(1);
760 let (mut producer, track) = producer(config);
761 let observer = producer.consume();
762 let mut consumer = Consumer::<Value>::new(track, ConsumerConfig::default());
763 let waiter = kio::Waiter::noop();
764
765 producer.update(&json!({ "a": 1 })).unwrap(); match consumer.poll_next(&waiter) {
767 Poll::Ready(Ok(Some(value))) => assert_eq!(value, json!({ "a": 1 })),
768 other => panic!("expected first value, got {other:?}"),
769 }
770
771 producer.update(&json!({ "a": 2 })).unwrap(); producer.update(&json!({ "a": 3 })).unwrap(); producer.update(&json!({ "a": 4 })).unwrap(); producer.finish().unwrap();
775 assert_eq!(observer.latest(), Some(1));
776
777 let mut last = None;
779 while let Poll::Ready(Ok(Some(value))) = consumer.poll_next(&waiter) {
780 last = Some(value);
781 }
782 assert_eq!(last.unwrap(), json!({ "a": 4 }));
783 }
784
785 #[test]
786 fn cloned_consumer_reconstructs_independently() {
787 let config = cfg(100);
789 let (mut producer, track) = producer(config);
790 let mut consumer = Consumer::<Value>::new(track, ConsumerConfig::default());
791 let waiter = kio::Waiter::noop();
792
793 producer.update(&json!({ "a": 1, "b": 1 })).unwrap(); match consumer.poll_next(&waiter) {
795 Poll::Ready(Ok(Some(value))) => assert_eq!(value, json!({ "a": 1, "b": 1 })),
796 other => panic!("expected snapshot, got {other:?}"),
797 }
798
799 let mut clone = consumer.clone();
801
802 producer.update(&json!({ "a": 1, "b": 2 })).unwrap(); producer.finish().unwrap();
804
805 let expected = json!({ "a": 1, "b": 2 });
807 for consumer in [&mut consumer, &mut clone] {
808 match consumer.poll_next(&waiter) {
809 Poll::Ready(Ok(Some(value))) => assert_eq!(value, expected),
810 other => panic!("expected delta, got {other:?}"),
811 }
812 }
813 }
814
815 #[test]
816 fn open_group_pends_after_track_finish() {
817 let mut track = moq_net::Track::new("test").produce();
820 let mut group = track.append_group().unwrap();
821 track.finish().unwrap();
822
823 let mut consumer = Consumer::<Value>::new(track.consume(), ConsumerConfig::default());
824 let waiter = kio::Waiter::noop();
825
826 assert!(matches!(consumer.poll_next(&waiter), Poll::Pending));
828
829 group
830 .write_frame(Bytes::from(serde_json::to_vec(&json!({ "a": 1 })).unwrap()))
831 .unwrap();
832 group.finish().unwrap();
833
834 match consumer.poll_next(&waiter) {
835 Poll::Ready(Ok(Some(value))) => assert_eq!(value, json!({ "a": 1 })),
836 other => panic!("expected the catalog value, got {other:?}"),
837 }
838 }
839
840 #[test]
841 fn late_joiner_collapses_backlog_to_latest() {
842 let (mut producer, track) = producer(cfg(100));
845 for n in 0..=20 {
846 producer.update(&json!({ "n": n })).unwrap();
847 }
848 producer.finish().unwrap();
849
850 assert_eq!(track.latest(), Some(0));
852 let values = drain(track);
853 assert_eq!(
854 values,
855 vec![json!({ "n": 20 })],
856 "backlog should collapse to the latest value"
857 );
858 }
859
860 #[test]
861 fn compressed_late_joiner_collapses_backlog_to_latest() {
862 let (mut producer, track) = producer(cfg_deflate(100));
864 for n in 0..=20 {
865 producer.update(&json!({ "n": n })).unwrap();
866 }
867 producer.finish().unwrap();
868
869 assert_eq!(track.latest(), Some(0));
870 let values = drain_with(deflate_consumer(track));
871 assert_eq!(
872 values,
873 vec![json!({ "n": 20 })],
874 "compressed backlog should collapse to the latest"
875 );
876 }
877
878 #[test]
879 fn compressed_snapshot_per_group_roundtrips() {
880 let (mut producer, track) = producer(cfg_deflate(0));
881 producer.update(&json!({ "a": 1 })).unwrap();
882 producer.update(&json!({ "a": 2 })).unwrap();
883 producer.finish().unwrap();
884
885 assert_eq!(track.latest(), Some(1));
887 let values = drain_with(deflate_consumer(track));
888 assert_eq!(values, vec![json!({ "a": 2 })]);
889 }
890
891 #[test]
892 fn compressed_deltas_share_one_group() {
893 let (mut producer, track) = producer(cfg_deflate(100));
894 producer.update(&json!({ "a": 1, "b": 1 })).unwrap();
895 producer.update(&json!({ "a": 1, "b": 2 })).unwrap();
896 producer.update(&json!({ "a": 1, "b": 3 })).unwrap();
897 producer.finish().unwrap();
898
899 assert_eq!(track.latest(), Some(0));
901 let values = drain_with(deflate_consumer(track));
902 assert_eq!(values.last().unwrap(), &json!({ "a": 1, "b": 3 }));
903 }
904
905 #[test]
906 fn compressed_late_joiner_reconstructs_from_deltas() {
907 let (mut producer, track) = producer(cfg_deflate(100));
908 producer.update(&json!({ "a": 1, "b": 1 })).unwrap();
909 producer.update(&json!({ "a": 1, "b": 2 })).unwrap();
910 producer.update(&json!({ "a": 5, "b": 2 })).unwrap();
911 producer.finish().unwrap();
912
913 let values = drain_with(deflate_consumer(track));
915 assert_eq!(values.last().unwrap(), &json!({ "a": 5, "b": 2 }));
916 }
917
918 #[test]
919 fn compressed_deltas_roll_on_compressed_budget() {
920 let (mut producer, track) = producer(cfg_deflate(2));
926 for n in 0..=40 {
927 producer.update(&json!({ "n": n })).unwrap();
928 }
929 producer.finish().unwrap();
930
931 assert!(
932 track.latest().unwrap() > 0,
933 "a tight ratio should roll at least one compressed group"
934 );
935 assert_eq!(drain_with(deflate_consumer(track)).last().unwrap(), &json!({ "n": 40 }));
936 }
937
938 #[test]
939 fn compressed_cloned_consumer_reconstructs_mid_group() {
940 let (mut producer, track) = producer(cfg_deflate(100));
942 let mut consumer = deflate_consumer(track);
943 let waiter = kio::Waiter::noop();
944
945 producer.update(&json!({ "a": 1, "b": 1 })).unwrap(); match consumer.poll_next(&waiter) {
947 Poll::Ready(Ok(Some(value))) => assert_eq!(value, json!({ "a": 1, "b": 1 })),
948 other => panic!("expected snapshot, got {other:?}"),
949 }
950
951 let mut clone = consumer.clone();
952
953 producer.update(&json!({ "a": 1, "b": 2 })).unwrap(); producer.finish().unwrap();
955
956 let expected = json!({ "a": 1, "b": 2 });
957 for consumer in [&mut consumer, &mut clone] {
958 match consumer.poll_next(&waiter) {
959 Poll::Ready(Ok(Some(value))) => assert_eq!(value, expected),
960 other => panic!("expected delta, got {other:?}"),
961 }
962 }
963 }
964
965 #[test]
966 fn compression_shrinks_wire_frames() {
967 let value = json!({ "renditions": ["video".repeat(50), "video".repeat(50), "video".repeat(50)] });
969
970 let plaintext_bytes = wire_frame_len(cfg(0), &value);
971 let compressed_bytes = wire_frame_len(cfg_deflate(0), &value);
972 assert!(
973 compressed_bytes < plaintext_bytes,
974 "compressed frame {compressed_bytes} should be smaller than plaintext {plaintext_bytes}"
975 );
976 }
977
978 #[test]
979 fn compressed_deltas_reuse_window() {
980 let (mut producer, mut track) = producer(cfg_deflate(100));
983 let phrase = "Media over QUIC delivers real-time latency at massive scale";
984 producer.update(&json!({ "note": phrase })).unwrap();
985 producer.update(&json!({ "note": phrase, "echo": phrase })).unwrap();
986 producer.finish().unwrap();
987
988 let waiter = kio::Waiter::noop();
990 let Poll::Ready(Ok(Some(mut group))) = track.poll_next_group(&waiter) else {
991 panic!("expected a group");
992 };
993 let mut frames = Vec::new();
994 while let Poll::Ready(Ok(Some(frame))) = group.poll_read_frame(&waiter) {
995 frames.push(frame);
996 }
997 assert_eq!(frames.len(), 2, "snapshot + one delta in a single group");
998
999 let raw_delta = serde_json::to_vec(&json!({ "echo": phrase })).unwrap();
1001 assert!(
1002 frames[1].len() < raw_delta.len() / 2,
1003 "windowed delta {} should be far below the raw patch {}",
1004 frames[1].len(),
1005 raw_delta.len()
1006 );
1007 }
1008
1009 fn wire_frame_len(config: ProducerConfig, value: &Value) -> usize {
1011 let (mut producer, mut track) = producer(config);
1012 producer.update(value).unwrap();
1013 producer.finish().unwrap();
1014
1015 let waiter = kio::Waiter::noop();
1016 let Poll::Ready(Ok(Some(mut group))) = track.poll_next_group(&waiter) else {
1017 panic!("expected a group");
1018 };
1019 let Poll::Ready(Ok(Some(frame))) = group.poll_read_frame(&waiter) else {
1021 panic!("expected a frame");
1022 };
1023 frame.len()
1024 }
1025}