1mod consumer;
49mod decoder;
50mod encoder;
51mod op;
52mod producer;
53
54pub use consumer::Consumer;
55pub use decoder::{ConsumerConfig, Decoder, Event, Group};
56pub use encoder::{Encoded, Encoder, Pending, ProducerConfig};
57pub use producer::Producer;
58
59#[cfg(test)]
60mod test {
61 use std::task::Poll;
62
63 use serde_json::{Value, json};
64
65 use super::*;
66
67 fn producer(config: ProducerConfig) -> (Producer<Value>, moq_net::track::Subscriber) {
68 let track = moq_net::broadcast::Info::new()
69 .produce()
70 .create_track("test", None)
71 .unwrap();
72 let consumer = track.subscribe(None);
73 (Producer::new(track, config), consumer)
74 }
75
76 fn consumer(track: moq_net::track::Subscriber, compression: bool) -> Consumer<Value> {
77 Consumer::new(track, ConsumerConfig::default().with_compression(compression))
78 }
79
80 fn rejecting_track() -> moq_net::track::Producer {
82 let mut info = moq_net::track::Info::default();
83 info.timescale = moq_net::Timescale::new((1u64 << 62) - 1).unwrap();
84
85 moq_net::broadcast::Info::new()
86 .produce()
87 .create_track("test", Some(info))
88 .unwrap()
89 }
90
91 fn drain(consumer: &mut Consumer<Value>) -> Vec<Event<Value>> {
93 let waiter = kio::Waiter::noop();
94 let mut out = Vec::new();
95 while let Poll::Ready(Ok(Some(event))) = consumer.poll_next(&waiter) {
96 out.push(event);
97 }
98 out
99 }
100
101 fn rec(n: u64) -> Value {
102 json!({ "n": n })
103 }
104
105 struct Live {
111 producer: Producer<Value>,
112 consumer: Consumer<Value>,
113 events: Vec<Event<Value>>,
114 }
115
116 impl Live {
117 fn new(config: ProducerConfig) -> Self {
118 let compression = config.compression;
119 let (producer, track) = producer(config);
120 Self {
121 producer,
122 consumer: consumer(track, compression),
123 events: Vec::new(),
124 }
125 }
126
127 fn push(&mut self, n: u64) {
128 self.producer.push(&rec(n)).unwrap();
129 self.read();
130 }
131
132 fn pop(&mut self, count: u64) {
133 self.producer.pop(count).unwrap();
134 self.read();
135 }
136
137 fn read(&mut self) {
138 self.events.extend(drain(&mut self.consumer));
139 }
140
141 fn finish(self) -> Vec<Event<Value>> {
142 let Live {
143 producer,
144 mut consumer,
145 mut events,
146 } = self;
147 producer.finish().unwrap();
148 events.extend(drain(&mut consumer));
149 events
150 }
151
152 fn pushed(events: &[Event<Value>]) -> Vec<u64> {
154 events
155 .iter()
156 .filter_map(|e| match e {
157 Event::Push { index, .. } => Some(*index),
158 _ => None,
159 })
160 .collect()
161 }
162 }
163
164 #[test]
165 fn push_and_pop_round_trip() {
166 let mut live = Live::new(ProducerConfig::default());
167 live.push(0);
168 live.push(1);
169 live.pop(1);
170 live.push(2);
171
172 assert_eq!(
173 live.finish(),
174 vec![
175 Event::Push {
176 index: 0,
177 value: rec(0)
178 },
179 Event::Push {
180 index: 1,
181 value: rec(1)
182 },
183 Event::Pop(0..1),
184 Event::Push {
185 index: 2,
186 value: rec(2)
187 },
188 ]
189 );
190 }
191
192 #[test]
193 fn the_window_slides() {
194 let (mut producer, _track) = producer(ProducerConfig::default());
195 for n in 0..5 {
196 producer.push(&rec(n)).unwrap();
197 if n >= 2 {
198 producer.pop(1).unwrap();
199 }
200 }
201
202 assert_eq!(producer.range(), 3..5);
204 assert_eq!(producer.window(), vec![rec(3), rec(4)]);
205 }
206
207 #[test]
208 fn a_popped_record_is_never_restated() {
209 let mut live = Live::new(ProducerConfig::default().with_op_ratio(0));
211 live.push(0);
212 live.push(1);
213 live.pop(1);
214 live.push(2);
215
216 assert_eq!(
219 live.finish(),
220 vec![
221 Event::Push {
222 index: 0,
223 value: rec(0)
224 },
225 Event::Push {
226 index: 1,
227 value: rec(1)
228 },
229 Event::Pop(0..1),
230 Event::Push {
231 index: 2,
232 value: rec(2)
233 },
234 ]
235 );
236 }
237
238 #[test]
239 fn a_fresh_consumer_adopts_the_offset_without_skipping_history() {
240 let track = moq_net::broadcast::Info::new()
241 .produce()
242 .create_track("test", None)
243 .unwrap();
244 let mut producer = Producer::<Value>::new(track, ProducerConfig::default().with_op_ratio(0));
245
246 for n in 0..5 {
247 producer.push(&rec(n)).unwrap();
248 }
249 producer.pop(3).unwrap();
250 let mut subscriber = producer.consume();
251 subscriber.start_at(subscriber.latest().unwrap());
252 let mut fresh = consumer(subscriber, false);
253 producer.finish().unwrap();
254
255 let events = drain(&mut fresh);
258 assert_eq!(
259 events,
260 vec![
261 Event::Push {
262 index: 3,
263 value: rec(3)
264 },
265 Event::Push {
266 index: 4,
267 value: rec(4)
268 }
269 ]
270 );
271 assert!(!events.iter().any(|e| matches!(e, Event::Skip(_))));
272 }
273
274 #[test]
275 fn a_lagging_consumer_is_told_what_it_missed() {
276 let mut encoder = Encoder::<Value>::new(ProducerConfig::default().with_op_ratio(0));
280 let mut decoder = Decoder::<Value>::new(ConsumerConfig::default());
281 for n in 0..2 {
282 let frame = encoder.push(&rec(n)).unwrap();
283 let mut group = decoder.group();
284 group.decode(&frame.payload).unwrap();
285 frame.commit();
286 }
287 assert_eq!(
288 std::iter::from_fn(|| decoder.next_event()).collect::<Vec<_>>(),
289 vec![
290 Event::Push {
291 index: 0,
292 value: rec(0)
293 },
294 Event::Push {
295 index: 1,
296 value: rec(1)
297 }
298 ]
299 );
300
301 let mut latest = None;
302 for n in 2..8 {
303 let frame = encoder.push(&rec(n)).unwrap();
304 frame.commit();
305
306 let frame = encoder.pop(1).unwrap().unwrap();
307 latest = Some(frame.payload.clone());
308 frame.commit();
309 }
310 let mut group = decoder.group();
311 group.decode(&latest.unwrap()).unwrap();
312
313 let events = std::iter::from_fn(|| decoder.next_event()).collect::<Vec<_>>();
314 let skipped: Vec<std::ops::Range<u64>> = events
315 .iter()
316 .filter_map(|e| match e {
317 Event::Skip(range) => Some(range.clone()),
318 _ => None,
319 })
320 .collect();
321
322 assert!(!skipped.is_empty(), "expected skips, got {events:?}");
325 assert_eq!(skipped.first().map(|range| range.start), Some(2));
326
327 let reported: Vec<u64> = events
329 .iter()
330 .flat_map(|e| match e {
331 Event::Push { index, .. } => vec![*index],
332 Event::Skip(range) => range.clone().collect(),
333 Event::Pop(_) => Vec::new(),
334 })
335 .collect();
336 assert!(reported.windows(2).all(|w| w[1] == w[0] + 1), "gaps in {reported:?}");
337 }
338
339 #[test]
340 fn compressed_round_trip_across_rolls() {
341 let mut live = Live::new(ProducerConfig::default().with_compression(true).with_op_ratio(1));
342 for n in 0..40 {
343 live.push(n);
344 if n >= 10 {
345 live.pop(1);
346 }
347 }
348
349 assert_eq!(Live::pushed(&live.finish()), (0..40).collect::<Vec<_>>());
351 }
352
353 #[test]
354 fn an_empty_pop_writes_nothing() {
355 let (mut producer, track) = producer(ProducerConfig::default());
356 producer.pop(5).unwrap();
357 producer.finish().unwrap();
358
359 assert_eq!(track.latest(), None);
361 }
362
363 #[test]
364 fn a_rejected_edit_leaves_the_window_unchanged() {
365 let track = rejecting_track();
366 let mut subscriber = track.subscribe(None);
367 let mut producer = Producer::<Value>::new(track, ProducerConfig::default());
368
369 assert!(producer.push(&rec(1)).is_err());
370 assert_eq!(producer.range(), 0..0);
371 assert!(producer.window().is_empty());
372
373 let waiter = kio::Waiter::noop();
374 let Poll::Ready(Ok(Some(mut group))) = subscriber.poll_next_group(&waiter) else {
375 panic!("the rejected group's header was published");
376 };
377 assert!(matches!(group.poll_read_frame(&waiter), Poll::Ready(Ok(None))));
378 }
379
380 #[test]
381 fn writes_after_another_clone_finishes_are_rejected() {
382 let (mut producer, _track) = producer(ProducerConfig::default());
383 producer.push(&rec(1)).unwrap();
384 producer.clone().finish().unwrap();
385
386 assert!(matches!(
387 producer.push(&rec(2)),
388 Err(crate::Error::Net(moq_net::Error::Closed))
389 ));
390 assert!(matches!(
391 producer.pop(1),
392 Err(crate::Error::Net(moq_net::Error::Closed))
393 ));
394 assert_eq!(producer.window(), vec![rec(1)]);
395 }
396
397 #[test]
398 fn a_pop_is_clamped_to_the_window() {
399 let mut live = Live::new(ProducerConfig::default());
400 live.push(0);
401 live.pop(9);
402 live.push(1);
403
404 assert_eq!(
405 live.finish(),
406 vec![
407 Event::Push {
408 index: 0,
409 value: rec(0)
410 },
411 Event::Pop(0..1),
412 Event::Push {
413 index: 1,
414 value: rec(1)
415 },
416 ]
417 );
418 }
419
420 #[test]
421 fn a_large_gap_is_one_skip_event() {
422 let mut decoder = Decoder::<Value>::new(ConsumerConfig::default());
423 let mut group = decoder.group();
424 group.decode(br#"{"offset":0,"records":[]}"#).unwrap();
425 let mut group = decoder.group();
426 group.decode(br#"{"offset":9007199254740991,"records":[]}"#).unwrap();
427
428 assert_eq!(decoder.next_event(), Some(Event::Skip(0..super::encoder::MAX_INDEX)));
429 assert_eq!(decoder.next_event(), None);
430 }
431
432 #[test]
433 fn indices_must_fit_the_shared_safe_integer_range() {
434 let mut decoder = Decoder::<Value>::new(ConsumerConfig::default());
435 let mut group = decoder.group();
436 assert!(group.decode(br#"{"offset":9007199254740992,"records":[]}"#).is_err());
437
438 let mut decoder = Decoder::<Value>::new(ConsumerConfig::default());
439 let mut group = decoder.group();
440 group.decode(br#"{"offset":9007199254740991,"records":[]}"#).unwrap();
441 assert!(group.decode(br#"{"push":null}"#).is_err());
442 }
443
444 #[test]
445 fn every_group_requires_a_header() {
446 let mut decoder = Decoder::<Value>::new(ConsumerConfig::default());
447 let mut group = decoder.group();
448 group.decode(br#"{"offset":0,"records":[]}"#).unwrap();
449 let mut group = decoder.group();
450
451 assert!(group.decode(br#"{"push":null}"#).is_err());
452 }
453
454 #[test]
455 fn a_header_is_only_valid_as_frame_zero() {
456 let mut decoder = Decoder::<Value>::new(ConsumerConfig::default());
457 let mut group = decoder.group();
458 group.decode(br#"{"offset":0,"records":[]}"#).unwrap();
459
460 assert!(group.decode(br#"{"offset":0,"records":[]}"#).is_err());
461 }
462
463 #[test]
464 fn rolling_is_invisible_to_the_consumer() {
465 let edits = |ratio: u32| {
467 let mut live = Live::new(ProducerConfig::default().with_op_ratio(ratio));
468 for n in 0..6 {
469 live.push(n);
470 if n >= 3 {
471 live.pop(1);
472 }
473 }
474 live.finish()
475 };
476
477 assert_eq!(edits(1_000), edits(0));
478 }
479}