1use std::{
44 collections::BTreeMap,
45 fmt,
46 sync::Arc,
47 time::{Duration, Instant},
48};
49
50use crate::{
51 Direction, PluginSpec,
52 channel::HostBuilder,
53 error::{PluginError, Result},
54 normalize,
55 plugin::{
56 BuildCtx, Ctx, EffectSink, Emission, Emit, Execution, ExternalStage, PipelineMeta, Plugin,
57 PluginFactory, Stage, StageInfo,
58 },
59};
60
61const EMPTY: &[u8] = &[];
62
63const NO_BOUNDS: &[usize] = &[];
65
66#[derive(Clone, Copy, PartialEq, Eq)]
68enum Slot {
69 Input,
71 A,
72 B,
73}
74
75#[derive(Debug, Clone, Copy)]
87pub struct Emitted<'p> {
88 bytes: &'p [u8],
89 bounds: &'p [usize],
95}
96
97impl<'p> Emitted<'p> {
98 #[must_use]
100 pub const fn whole(bytes: &'p [u8]) -> Self {
101 Self {
102 bytes,
103 bounds: NO_BOUNDS,
104 }
105 }
106
107 #[must_use]
109 pub const fn empty() -> Self {
110 Self::whole(EMPTY)
111 }
112
113 #[must_use]
115 pub const fn bytes(&self) -> &'p [u8] {
116 self.bytes
117 }
118
119 #[must_use]
120 pub const fn is_empty(&self) -> bool {
121 self.bytes.is_empty()
122 }
123
124 #[must_use]
125 pub const fn len(&self) -> usize {
126 self.bytes.len()
127 }
128
129 pub fn units(self) -> impl Iterator<Item = &'p [u8]> {
135 let Self { bytes, bounds } = self;
136
137 let unframed = bounds.is_empty() && !bytes.is_empty();
140
141 units(bytes, bounds).chain(unframed.then_some(bytes))
142 }
143}
144
145type Halves<'b> = (Slot, &'b [u8], &'b [usize], &'b mut Emission);
148
149#[derive(Default)]
156struct Buffers {
157 a: Emission,
158 b: Emission,
159}
160
161impl Buffers {
162 fn borrow<'b>(&'b mut self, live: Slot, input: &'b [u8]) -> Halves<'b> {
169 match live {
170 Slot::Input => (Slot::A, input, NO_BOUNDS, &mut self.a),
171 Slot::A => (Slot::B, self.a.bytes(), self.a.bounds(), &mut self.b),
172 Slot::B => (Slot::A, self.b.bytes(), self.b.bounds(), &mut self.a),
173 }
174 }
175
176 fn live<'b>(&'b self, live: Slot, input: &'b [u8]) -> (&'b [u8], &'b [usize]) {
178 match live {
179 Slot::Input => (input, NO_BOUNDS),
180 Slot::A => (self.a.bytes(), self.a.bounds()),
181 Slot::B => (self.b.bytes(), self.b.bounds()),
182 }
183 }
184}
185
186pub struct Pipeline {
188 meta: PipelineMeta,
190 stages: Vec<Box<dyn Plugin>>,
192 names: Vec<String>,
198 bufs: Buffers,
200 ticks: Vec<Schedule>,
202}
203
204struct Schedule {
206 stage: usize,
207 period: Duration,
208 next: Instant,
209}
210
211impl Pipeline {
212 #[must_use]
213 pub fn new(meta: PipelineMeta, stages: Vec<Box<dyn Plugin>>) -> Self {
214 let names = stages.iter().map(|s| s.name().to_string()).collect();
215 Self::with_names(meta, stages, names)
216 }
217
218 #[must_use]
221 pub fn with_names(
222 meta: PipelineMeta,
223 stages: Vec<Box<dyn Plugin>>,
224 names: Vec<String>,
225 ) -> Self {
226 debug_assert_eq!(stages.len(), names.len());
227
228 let start = Instant::now();
233 let ticks = stages
234 .iter()
235 .enumerate()
236 .filter_map(|(stage, plugin)| {
237 let period = plugin.tick_interval().filter(|p| !p.is_zero())?;
238
239 Some(Schedule {
240 stage,
241 period,
242 next: start + period,
243 })
244 })
245 .collect();
246
247 Self {
248 meta,
249 stages,
250 names,
251 bufs: Buffers::default(),
252 ticks,
253 }
254 }
255
256 #[must_use]
257 pub fn meta(&self) -> &PipelineMeta {
258 &self.meta
259 }
260
261 #[must_use]
262 pub fn is_empty(&self) -> bool {
263 self.stages.is_empty()
264 }
265
266 #[must_use]
267 pub fn len(&self) -> usize {
268 self.stages.len()
269 }
270
271 pub fn stage_names(&self) -> impl Iterator<Item = &str> {
272 self.names.iter().map(String::as_str)
273 }
274
275 #[must_use]
282 pub fn tick_interval(&self) -> Option<Duration> {
283 self.ticks.iter().map(|schedule| schedule.period).min()
284 }
285
286 fn due(&mut self, now: Instant) -> Option<usize> {
288 let schedule = self
289 .ticks
290 .iter_mut()
291 .find(|schedule| schedule.next <= now)?;
292
293 schedule.next += schedule.period;
294
295 if schedule.next <= now {
299 schedule.next = now + schedule.period;
300 }
301
302 Some(schedule.stage)
303 }
304
305 fn rearm(&mut self, stage: usize) {
317 if let Some(schedule) = self.ticks.iter_mut().find(|s| s.stage == stage) {
318 schedule.next = Instant::now() + schedule.period;
319 }
320 }
321
322 pub fn tick<'p>(
334 &'p mut self,
335 now: Instant,
336 sink: &mut dyn EffectSink,
337 ) -> Result<Option<Emitted<'p>>> {
338 let Some(index) = self.due(now) else {
339 return Ok(None);
340 };
341
342 run_tick(
343 &mut self.stages[index],
344 &self.meta,
345 &self.names[index],
346 &mut self.bufs.a,
347 sink,
348 )?;
349
350 if self.bufs.a.rearm_requested() {
353 self.rearm(index);
354 }
355
356 if self.bufs.a.bytes().is_empty() {
359 return Ok(Some(Emitted::empty()));
360 }
361
362 self.drive(EMPTY, index + 1, Slot::A, false, sink).map(Some)
363 }
364
365 #[must_use]
367 pub fn datagram_hazard(&self) -> Option<&str> {
368 self.stages
369 .iter()
370 .zip(&self.names)
371 .find(|(stage, _)| !stage.datagram_safe())
372 .map(|(_, name)| name.as_str())
373 }
374
375 pub fn process<'p>(
380 &'p mut self,
381 input: &'p [u8],
382 sink: &mut dyn EffectSink,
383 ) -> Result<Emitted<'p>> {
384 self.drive(input, 0, Slot::Input, false, sink)
385 }
386
387 pub fn finish<'p>(&'p mut self, sink: &mut dyn EffectSink) -> Result<Emitted<'p>> {
389 self.drive(EMPTY, 0, Slot::Input, true, sink)
390 }
391
392 fn drive<'p>(
398 &'p mut self,
399 input: &'p [u8],
400 from: usize,
401 live: Slot,
402 eof: bool,
403 sink: &mut dyn EffectSink,
404 ) -> Result<Emitted<'p>> {
405 let mut live = live;
406
407 for index in from..self.stages.len() {
408 let (slot, src, src_bounds, dst) = self.bufs.borrow(live, input);
409
410 run(
411 &mut self.stages[index],
412 &self.meta,
413 &self.names[index],
414 src,
415 src_bounds,
416 dst,
417 sink,
418 eof,
419 )?;
420
421 let emitted = dst.emit();
422 let rearm = dst.rearm_requested();
423
424 if rearm {
425 self.rearm(index);
426 }
427
428 if emitted != Emit::Passthrough {
429 live = slot;
430 }
431
432 if !eof && self.bufs.live(live, input).0.is_empty() {
436 return Ok(Emitted::empty());
437 }
438 }
439
440 let (bytes, bounds) = self.bufs.live(live, input);
441
442 Ok(Emitted { bytes, bounds })
443 }
444}
445
446fn run(
457 plugin: &mut Box<dyn Plugin>,
458 meta: &PipelineMeta,
459 stage: &str,
460 input: &[u8],
461 in_bounds: &[usize],
462 dst: &mut Emission,
463 sink: &mut dyn EffectSink,
464 eof: bool,
465) -> Result<()> {
466 dst.reset();
467
468 if in_bounds.is_empty() {
469 {
470 let mut ctx = Ctx::new(meta, stage, input, dst, sink);
471
472 if eof {
473 if !input.is_empty() {
476 plugin.on_bytes(&mut ctx, input)?;
477 }
478
479 plugin.on_eof(&mut ctx)?;
480 } else {
481 plugin.on_bytes(&mut ctx, input)?;
482 }
483 }
484
485 if !dst.bounds().is_empty() {
488 dst.close();
489 }
490
491 return Ok(());
492 }
493
494 let mut copied = false;
495
496 for (index, unit) in units(input, in_bounds).enumerate() {
497 dst.next_unit();
498
499 {
500 let mut ctx = Ctx::new(meta, stage, unit, dst, sink);
501 plugin.on_bytes(&mut ctx, unit)?;
502 }
503
504 if !copied {
505 if dst.emit() == Emit::Passthrough {
506 continue;
509 }
510
511 materialise(input, in_bounds, index, dst);
514 copied = true;
515 }
516
517 if dst.emit() == Emit::Passthrough {
518 dst.out.extend_from_slice(unit);
519 }
520
521 dst.close();
522 }
523
524 if eof {
525 dst.next_unit();
526
527 {
528 let mut ctx = Ctx::new(meta, stage, EMPTY, dst, sink);
529 plugin.on_eof(&mut ctx)?;
530 }
531
532 if !copied && !dst.bytes().is_empty() {
535 materialise(input, in_bounds, in_bounds.len(), dst);
536 copied = true;
537 }
538
539 if copied {
540 dst.close();
541 }
542 }
543
544 dst.emit = if copied {
545 Emit::Buffered
546 } else {
547 Emit::Passthrough
548 };
549
550 Ok(())
551}
552
553fn run_tick(
556 plugin: &mut Box<dyn Plugin>,
557 meta: &PipelineMeta,
558 stage: &str,
559 dst: &mut Emission,
560 sink: &mut dyn EffectSink,
561) -> Result<()> {
562 dst.reset();
563
564 {
565 let mut ctx = Ctx::new(meta, stage, EMPTY, dst, sink);
566 plugin.on_tick(&mut ctx)?;
567 }
568
569 if !dst.bounds().is_empty() {
570 dst.close();
571 }
572
573 Ok(())
574}
575
576fn units<'a>(bytes: &'a [u8], bounds: &'a [usize]) -> impl Iterator<Item = &'a [u8]> {
578 let mut start = 0;
579
580 bounds.iter().map(move |&end| {
581 let unit = &bytes[start..end];
582 start = end;
583 unit
584 })
585}
586
587fn materialise(input: &[u8], in_bounds: &[usize], done: usize, dst: &mut Emission) {
595 let prefix = if done == 0 { 0 } else { in_bounds[done - 1] };
596
597 if prefix == 0 {
598 return;
599 }
600
601 dst.out.splice(0..0, input[..prefix].iter().copied());
602
603 for bound in dst.bounds.iter_mut() {
606 *bound += prefix;
607 }
608
609 dst.bounds.splice(0..0, in_bounds[..done].iter().copied());
610}
611
612impl fmt::Debug for Pipeline {
613 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
614 f.debug_struct("Pipeline")
615 .field("direction", &self.meta.direction)
616 .field("stages", &self.stage_names().collect::<Vec<_>>())
617 .finish()
618 }
619}
620
621#[derive(Debug)]
630pub enum Segment {
631 Inline(Pipeline),
632 Process(ExternalStage),
633}
634
635#[derive(Debug)]
637pub struct Chain {
638 meta: PipelineMeta,
639 segments: Vec<Segment>,
640}
641
642impl Chain {
643 #[must_use]
644 pub fn new(meta: PipelineMeta, segments: Vec<Segment>) -> Self {
645 Self { meta, segments }
646 }
647
648 #[must_use]
649 pub fn meta(&self) -> &PipelineMeta {
650 &self.meta
651 }
652
653 #[must_use]
655 pub fn is_empty(&self) -> bool {
656 self.segments.is_empty()
657 }
658
659 #[must_use]
660 pub fn segments(&self) -> &[Segment] {
661 &self.segments
662 }
663
664 #[must_use]
665 pub fn into_segments(self) -> Vec<Segment> {
666 self.segments
667 }
668
669 #[must_use]
674 pub fn datagram_hazard(&self) -> Option<&str> {
675 self.segments().iter().find_map(|segment| match segment {
676 Segment::Inline(pipeline) => pipeline.datagram_hazard(),
677 Segment::Process(external) => Some(external.name.as_str()),
678 })
679 }
680
681 #[must_use]
682 pub fn stage_names(&self) -> Vec<&str> {
683 self.segments
684 .iter()
685 .flat_map(|segment| match segment {
686 Segment::Inline(pipeline) => pipeline.stage_names().collect::<Vec<_>>(),
687 Segment::Process(external) => vec![external.name.as_str()],
688 })
689 .collect()
690 }
691}
692
693#[derive(Default)]
695pub struct Registry {
696 factories: BTreeMap<String, Arc<dyn PluginFactory>>,
697}
698
699impl Registry {
700 #[must_use]
701 pub fn new() -> Self {
702 Self::default()
703 }
704
705 pub fn register(&mut self, factory: impl PluginFactory) -> &mut Self {
706 self.register_arc(Arc::new(factory))
707 }
708
709 pub fn register_arc(&mut self, factory: Arc<dyn PluginFactory>) -> &mut Self {
710 self.factories.insert(normalize(factory.name()), factory);
711 self
712 }
713
714 #[must_use]
715 pub fn get(&self, name: &str) -> Option<&Arc<dyn PluginFactory>> {
716 self.factories.get(&normalize(name))
717 }
718
719 pub fn iter(&self) -> impl Iterator<Item = &Arc<dyn PluginFactory>> {
720 self.factories.values()
721 }
722
723 pub fn names(&self) -> impl Iterator<Item = &str> {
727 self.factories.values().map(|f| f.name())
729 }
730
731 pub fn build(
737 &self,
738 specs: &[PluginSpec],
739 meta: &PipelineMeta,
740 host: &mut dyn HostBuilder,
741 ) -> Result<Chain> {
742 let mut selected: Vec<&PluginSpec> = specs
743 .iter()
744 .filter(|spec| spec.direction.contains(meta.direction))
745 .collect();
746
747 if meta.direction == Direction::SinkToSource {
748 selected.reverse();
749 }
750
751 let display = display_names(&selected);
752
753 let mut labels = Vec::with_capacity(display.len() + 2);
756 labels.push(meta.upstream().to_string());
757 labels.extend(display.iter().cloned());
758 labels.push(meta.downstream().to_string());
759
760 let total = selected.len();
761 let mut segments: Vec<Segment> = Vec::new();
762 let mut draft: Option<SegmentDraft> = None;
763
764 for (index, spec) in selected.iter().enumerate() {
765 let factory = self
766 .get(&spec.name)
767 .ok_or_else(|| PluginError::unknown(&spec.name, self.names()))?
768 .clone();
769
770 let execution = match spec.detach {
771 Some(true) => Execution::Detached,
772 Some(false) => Execution::Inline,
773 None => factory.execution(),
774 };
775
776 let stage_info = StageInfo {
777 index,
778 total,
779 name: &display[index],
780 upstream: &labels[index],
781 downstream: &labels[index + 2],
782 };
783
784 let mut ctx = BuildCtx::new(&spec.name, &spec.config, meta, stage_info, host);
785
786 match factory.build(&mut ctx)? {
787 Stage::Filter(plugin) => {
788 if draft.is_none() || execution == Execution::Detached {
791 if let Some(ready) = draft.take() {
792 segments.push(Segment::Inline(ready.into_pipeline(meta.clone())));
793 }
794 draft = Some(SegmentDraft::default());
795 }
796
797 draft
798 .as_mut()
799 .expect("a draft was just ensured")
800 .push(plugin, display[index].clone());
801 }
802 Stage::External(external) => {
803 if spec.detach == Some(false) {
804 return Err(PluginError::config(
805 &spec.name,
806 "runs as a subprocess and always has its own task; `detach = false` \
807 cannot be honoured",
808 ));
809 }
810
811 if let Some(ready) = draft.take() {
812 segments.push(Segment::Inline(ready.into_pipeline(meta.clone())));
813 }
814
815 segments.push(Segment::Process(external));
816 }
817 }
818 }
819
820 if let Some(ready) = draft.take() {
821 segments.push(Segment::Inline(ready.into_pipeline(meta.clone())));
822 }
823
824 Ok(Chain::new(meta.clone(), segments))
825 }
826
827 pub fn build_pair(
833 &self,
834 specs: &[PluginSpec],
835 source: &str,
836 sink: &str,
837 peer: Option<&str>,
838 host: &mut dyn HostBuilder,
839 ) -> Result<(Chain, Chain)> {
840 let forward = PipelineMeta::new(Direction::SourceToSink, source, sink).with_peer(peer);
841 let reverse = PipelineMeta {
842 direction: Direction::SinkToSource,
843 ..forward.clone()
844 };
845
846 Ok((
847 self.build(specs, &forward, host)?,
848 self.build(specs, &reverse, host)?,
849 ))
850 }
851}
852
853#[derive(Default)]
856struct SegmentDraft {
857 stages: Vec<Box<dyn Plugin>>,
858 names: Vec<String>,
859}
860
861impl SegmentDraft {
862 fn push(&mut self, plugin: Box<dyn Plugin>, name: String) {
863 self.stages.push(plugin);
864 self.names.push(name);
865 }
866
867 fn into_pipeline(self, meta: PipelineMeta) -> Pipeline {
868 Pipeline::with_names(meta, self.stages, self.names)
869 }
870}
871
872fn display_names(specs: &[&PluginSpec]) -> Vec<String> {
875 let base: Vec<&str> = specs
876 .iter()
877 .map(|spec| spec.alias.as_deref().unwrap_or(spec.name.as_str()))
878 .collect();
879
880 let mut seen: BTreeMap<&str, usize> = BTreeMap::new();
881 for name in &base {
882 *seen.entry(name).or_insert(0) += 1;
883 }
884
885 let mut used: BTreeMap<&str, usize> = BTreeMap::new();
886 base.iter()
887 .map(|name| {
888 if seen.get(name).copied().unwrap_or(0) > 1 {
889 let n = used.entry(name).or_insert(0);
890 *n += 1;
891 format!("{name}#{n}")
892 } else {
893 (*name).to_string()
894 }
895 })
896 .collect()
897}
898
899impl fmt::Debug for Registry {
900 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
901 f.debug_struct("Registry")
902 .field("plugins", &self.names().collect::<Vec<_>>())
903 .finish()
904 }
905}
906
907#[cfg(test)]
908mod tests {
909 use super::*;
910 use crate::{ChannelId, DirectionSpec, plugin::LogLevel};
911
912 #[derive(Default)]
913 struct Recorder {
914 writes: Vec<(ChannelId, Vec<u8>)>,
915 logs: Vec<String>,
916 }
917
918 impl EffectSink for Recorder {
919 fn write(&mut self, channel: ChannelId, bytes: &[u8]) {
920 self.writes.push((channel, bytes.to_vec()));
921 }
922
923 fn log(&mut self, _level: LogLevel, stage: &str, message: &str) {
924 self.logs.push(format!("{stage}: {message}"));
925 }
926 }
927
928 struct Observer;
930
931 impl Plugin for Observer {
932 fn name(&self) -> &str {
933 "observer"
934 }
935
936 fn on_bytes(&mut self, ctx: &mut Ctx<'_>, input: &[u8]) -> Result<()> {
937 ctx.side_write(ChannelId(0), input);
938 ctx.pass_through();
939 Ok(())
940 }
941 }
942
943 struct Upper;
944
945 impl Plugin for Upper {
946 fn name(&self) -> &str {
947 "upper"
948 }
949
950 fn on_bytes(&mut self, ctx: &mut Ctx<'_>, input: &[u8]) -> Result<()> {
951 let upper: Vec<u8> = input.iter().map(u8::to_ascii_uppercase).collect();
952 ctx.forward(&upper);
953 Ok(())
954 }
955 }
956
957 #[derive(Default)]
959 struct Reverse(Vec<u8>);
960
961 impl Plugin for Reverse {
962 fn name(&self) -> &str {
963 "reverse"
964 }
965
966 fn on_bytes(&mut self, _ctx: &mut Ctx<'_>, input: &[u8]) -> Result<()> {
967 self.0.extend_from_slice(input);
968 Ok(())
969 }
970
971 fn on_eof(&mut self, ctx: &mut Ctx<'_>) -> Result<()> {
972 let mut buf = std::mem::take(&mut self.0);
973 buf.reverse();
974 ctx.forward(&buf);
975 Ok(())
976 }
977 }
978
979 struct Beacon(Duration);
981
982 impl Plugin for Beacon {
983 fn name(&self) -> &str {
984 "beacon"
985 }
986
987 fn on_bytes(&mut self, ctx: &mut Ctx<'_>, _input: &[u8]) -> Result<()> {
988 ctx.pass_through();
989 Ok(())
990 }
991
992 fn tick_interval(&self) -> Option<Duration> {
993 Some(self.0)
994 }
995
996 fn on_tick(&mut self, ctx: &mut Ctx<'_>) -> Result<()> {
997 ctx.forward(b"ping");
998 Ok(())
999 }
1000 }
1001
1002 struct Quiet(Duration);
1005
1006 impl Plugin for Quiet {
1007 fn name(&self) -> &str {
1008 "quiet"
1009 }
1010
1011 fn on_bytes(&mut self, ctx: &mut Ctx<'_>, _input: &[u8]) -> Result<()> {
1012 ctx.pass_through();
1013 Ok(())
1014 }
1015
1016 fn tick_interval(&self) -> Option<Duration> {
1017 Some(self.0)
1018 }
1019
1020 fn on_tick(&mut self, ctx: &mut Ctx<'_>) -> Result<()> {
1021 ctx.log(LogLevel::Info, "still here");
1022 Ok(())
1023 }
1024 }
1025
1026 struct Chop {
1029 size: usize,
1030 held: Vec<u8>,
1031 }
1032
1033 impl Chop {
1034 fn new(size: usize) -> Self {
1035 Self {
1036 size,
1037 held: Vec::new(),
1038 }
1039 }
1040 }
1041
1042 impl Plugin for Chop {
1043 fn name(&self) -> &str {
1044 "chop"
1045 }
1046
1047 fn on_bytes(&mut self, ctx: &mut Ctx<'_>, input: &[u8]) -> Result<()> {
1048 self.held.extend_from_slice(input);
1049
1050 while self.held.len() >= self.size {
1051 let rest = self.held.split_off(self.size);
1052 ctx.forward(&self.held);
1053 ctx.boundary();
1054 self.held = rest;
1055 }
1056
1057 Ok(())
1058 }
1059
1060 fn on_eof(&mut self, ctx: &mut Ctx<'_>) -> Result<()> {
1061 if !self.held.is_empty() {
1062 let held = std::mem::take(&mut self.held);
1063 ctx.forward(&held);
1064 ctx.boundary();
1065 }
1066
1067 Ok(())
1068 }
1069 }
1070
1071 struct Sieve(u8);
1074
1075 impl Plugin for Sieve {
1076 fn name(&self) -> &str {
1077 "sieve"
1078 }
1079
1080 fn on_bytes(&mut self, ctx: &mut Ctx<'_>, input: &[u8]) -> Result<()> {
1081 if input.first() == Some(&self.0) {
1082 ctx.drop_chunk();
1083 } else {
1084 ctx.pass_through();
1085 }
1086
1087 Ok(())
1088 }
1089 }
1090
1091 struct Trailer(&'static [u8]);
1094
1095 impl Plugin for Trailer {
1096 fn name(&self) -> &str {
1097 "trailer"
1098 }
1099
1100 fn on_bytes(&mut self, ctx: &mut Ctx<'_>, _input: &[u8]) -> Result<()> {
1101 ctx.pass_through();
1102 Ok(())
1103 }
1104
1105 fn on_eof(&mut self, ctx: &mut Ctx<'_>) -> Result<()> {
1106 ctx.forward(self.0);
1107 Ok(())
1108 }
1109 }
1110
1111 struct Restart(Option<Duration>);
1114
1115 impl Plugin for Restart {
1116 fn name(&self) -> &str {
1117 "restart"
1118 }
1119
1120 fn on_bytes(&mut self, ctx: &mut Ctx<'_>, _input: &[u8]) -> Result<()> {
1121 ctx.rearm();
1122 ctx.pass_through();
1123 Ok(())
1124 }
1125
1126 fn tick_interval(&self) -> Option<Duration> {
1127 self.0
1128 }
1129 }
1130
1131 fn meta() -> PipelineMeta {
1132 PipelineMeta::new(Direction::SourceToSink, "src", "sink")
1133 }
1134
1135 fn later() -> Instant {
1137 Instant::now() + Duration::from_secs(3600)
1138 }
1139
1140 fn parts<'a>(emitted: &Emitted<'a>) -> Vec<&'a [u8]> {
1142 emitted.units().collect()
1143 }
1144
1145 #[test]
1146 fn empty_pipeline_returns_the_input_slice() {
1147 let mut p = Pipeline::new(meta(), Vec::new());
1148 let mut sink = Recorder::default();
1149 let input = b"hello";
1150
1151 let out = p.process(input, &mut sink).unwrap();
1152 assert!(std::ptr::eq(out.bytes().as_ptr(), input.as_ptr()));
1153 }
1154
1155 #[test]
1156 fn observers_never_copy_the_payload() {
1157 let mut p = Pipeline::new(meta(), vec![Box::new(Observer), Box::new(Observer)]);
1158 let mut sink = Recorder::default();
1159 let input = b"payload";
1160
1161 let out = p.process(input, &mut sink).unwrap();
1162
1163 assert!(
1164 std::ptr::eq(out.bytes().as_ptr(), input.as_ptr()),
1165 "a chain of observers must hand the original buffer downstream",
1166 );
1167 assert_eq!(sink.writes.len(), 2);
1168 }
1169
1170 #[test]
1171 fn stages_chain_in_order() {
1172 let mut p = Pipeline::new(meta(), vec![Box::new(Upper), Box::new(Reverse::default())]);
1173 let mut sink = Recorder::default();
1174
1175 assert!(p.process(b"ab", &mut sink).unwrap().is_empty());
1176 assert!(p.process(b"cd", &mut sink).unwrap().is_empty());
1177 assert_eq!(p.finish(&mut sink).unwrap().bytes(), b"DCBA");
1178 }
1179
1180 #[test]
1181 fn repeated_plugins_get_distinct_display_names() {
1182 let specs = [
1183 PluginSpec::new("tee", DirectionSpec::Both),
1184 PluginSpec::new("tee", DirectionSpec::Both).named("audit"),
1185 PluginSpec::new("tee", DirectionSpec::Both),
1186 ];
1187 let refs: Vec<&PluginSpec> = specs.iter().collect();
1188
1189 assert_eq!(display_names(&refs), ["tee#1", "audit", "tee#2"]);
1190 }
1191
1192 #[test]
1193 fn a_pipeline_with_nothing_ticking_has_no_schedule() {
1194 let mut p = Pipeline::new(meta(), vec![Box::new(Observer)]);
1195 let mut sink = Recorder::default();
1196
1197 assert_eq!(p.tick_interval(), None, "so the host builds no timer");
1198 assert!(p.tick(later(), &mut sink).unwrap().is_none());
1199 }
1200
1201 #[test]
1204 fn the_schedule_is_the_shortest_period_asked_for() {
1205 let p = Pipeline::new(
1206 meta(),
1207 vec![
1208 Box::new(Quiet(Duration::from_secs(30))),
1209 Box::new(Beacon(Duration::from_secs(5))),
1210 ],
1211 );
1212
1213 assert_eq!(p.tick_interval(), Some(Duration::from_secs(5)));
1214 }
1215
1216 #[test]
1217 fn a_tick_cascades_through_the_stages_below_it() {
1218 let mut p = Pipeline::new(
1219 meta(),
1220 vec![Box::new(Beacon(Duration::from_secs(60))), Box::new(Upper)],
1221 );
1222 let mut sink = Recorder::default();
1223
1224 assert!(
1225 p.tick(Instant::now(), &mut sink).unwrap().is_none(),
1226 "not due yet",
1227 );
1228
1229 let now = later();
1230 assert_eq!(p.tick(now, &mut sink).unwrap().unwrap().bytes(), b"PING");
1231 assert!(
1232 p.tick(now, &mut sink).unwrap().is_none(),
1233 "one turn per stage per wakeup, however far behind the schedule is",
1234 );
1235 }
1236
1237 #[test]
1240 fn a_silent_tick_does_not_disturb_the_stages_below() {
1241 let mut p = Pipeline::new(
1242 meta(),
1243 vec![Box::new(Quiet(Duration::from_secs(60))), Box::new(Observer)],
1244 );
1245 let mut sink = Recorder::default();
1246
1247 assert!(p.tick(later(), &mut sink).unwrap().unwrap().is_empty());
1248 assert!(sink.writes.is_empty(), "the observer below never ran");
1249 assert_eq!(sink.logs, ["quiet: still here"]);
1250 }
1251
1252 #[test]
1255 fn ticks_and_chunks_do_not_interfere() {
1256 let mut p = Pipeline::new(
1257 meta(),
1258 vec![
1259 Box::new(Observer),
1260 Box::new(Beacon(Duration::from_secs(60))),
1261 ],
1262 );
1263 let mut sink = Recorder::default();
1264
1265 assert_eq!(
1266 p.tick(later(), &mut sink).unwrap().unwrap().bytes(),
1267 b"ping"
1268 );
1269 assert!(
1270 sink.writes.is_empty(),
1271 "the observer sits above the beacon and saw nothing",
1272 );
1273
1274 assert_eq!(
1275 p.process(b"payload", &mut sink).unwrap().bytes(),
1276 b"payload"
1277 );
1278 assert_eq!(sink.writes, [(ChannelId(0), b"payload".to_vec())]);
1279 }
1280
1281 #[test]
1282 fn two_stages_due_at_once_each_get_a_turn() {
1283 let mut p = Pipeline::new(
1284 meta(),
1285 vec![
1286 Box::new(Beacon(Duration::from_secs(60))),
1287 Box::new(Quiet(Duration::from_secs(60))),
1288 ],
1289 );
1290 let mut sink = Recorder::default();
1291 let now = later();
1292
1293 assert_eq!(p.tick(now, &mut sink).unwrap().unwrap().bytes(), b"ping");
1294 assert!(p.tick(now, &mut sink).unwrap().unwrap().is_empty());
1295 assert!(p.tick(now, &mut sink).unwrap().is_none());
1296 }
1297
1298 #[test]
1299 fn transform_then_observe_keeps_the_transformed_bytes() {
1300 let mut p = Pipeline::new(meta(), vec![Box::new(Upper), Box::new(Observer)]);
1301 let mut sink = Recorder::default();
1302
1303 assert_eq!(p.process(b"hi", &mut sink).unwrap().bytes(), b"HI");
1304 assert_eq!(sink.writes[0].1, b"HI".to_vec());
1305 }
1306
1307 #[test]
1309 fn an_unframed_emission_is_one_unit() {
1310 let mut p = Pipeline::new(meta(), vec![Box::new(Upper)]);
1311 let mut sink = Recorder::default();
1312
1313 let out = p.process(b"hi", &mut sink).unwrap();
1314 assert_eq!(parts(&out), [b"HI".as_slice()]);
1315 }
1316
1317 #[test]
1318 fn an_empty_emission_has_no_units() {
1319 assert!(Emitted::empty().units().next().is_none());
1320 }
1321
1322 #[test]
1325 fn a_stage_can_emit_several_units_from_one_chunk() {
1326 let mut p = Pipeline::new(meta(), vec![Box::new(Chop::new(2))]);
1327 let mut sink = Recorder::default();
1328
1329 let out = p.process(b"abcdef", &mut sink).unwrap();
1330
1331 assert_eq!(out.bytes(), b"abcdef", "the bytes are still the bytes");
1332 assert_eq!(parts(&out), [b"ab".as_slice(), b"cd", b"ef"]);
1333 }
1334
1335 #[test]
1338 fn framing_survives_a_stage_that_rewrites_it() {
1339 let mut p = Pipeline::new(meta(), vec![Box::new(Chop::new(2)), Box::new(Upper)]);
1340 let mut sink = Recorder::default();
1341
1342 let out = p.process(b"abcdef", &mut sink).unwrap();
1343 assert_eq!(parts(&out), [b"AB".as_slice(), b"CD", b"EF"]);
1344 }
1345
1346 #[test]
1349 fn an_observer_under_a_framing_stage_still_copies_nothing() {
1350 let mut p = Pipeline::new(meta(), vec![Box::new(Chop::new(2)), Box::new(Observer)]);
1351 let mut sink = Recorder::default();
1352
1353 let out = p.process(b"abcdef", &mut sink).unwrap();
1354
1355 assert_eq!(parts(&out), [b"ab".as_slice(), b"cd", b"ef"]);
1356 assert_eq!(
1357 sink.writes.len(),
1358 3,
1359 "the observer was called once per unit, not once per chunk",
1360 );
1361 }
1362
1363 #[test]
1366 fn units_passed_through_before_a_drop_are_kept() {
1367 let mut p = Pipeline::new(meta(), vec![Box::new(Chop::new(2)), Box::new(Sieve(b'c'))]);
1368 let mut sink = Recorder::default();
1369
1370 let out = p.process(b"abcdef", &mut sink).unwrap();
1371
1372 assert_eq!(out.bytes(), b"abef");
1373 assert_eq!(parts(&out), [b"ab".as_slice(), b"ef"]);
1374 }
1375
1376 #[test]
1379 fn dropping_the_first_unit_keeps_the_rest() {
1380 let mut p = Pipeline::new(meta(), vec![Box::new(Chop::new(2)), Box::new(Sieve(b'a'))]);
1381 let mut sink = Recorder::default();
1382
1383 let out = p.process(b"abcdef", &mut sink).unwrap();
1384
1385 assert_eq!(parts(&out), [b"cd".as_slice(), b"ef"]);
1386 }
1387
1388 #[test]
1391 fn an_epilogue_after_a_run_of_passthroughs_keeps_both() {
1392 let mut p = Pipeline::new(
1393 meta(),
1394 vec![Box::new(Chop::new(4)), Box::new(Trailer(b"!"))],
1395 );
1396 let mut sink = Recorder::default();
1397
1398 let out = p.process(b"abcdef", &mut sink).unwrap();
1399 assert_eq!(parts(&out), [b"abcd".as_slice()]);
1400
1401 let out = p.finish(&mut sink).unwrap();
1402 assert_eq!(out.bytes(), b"ef!");
1403 assert_eq!(parts(&out), [b"ef".as_slice(), b"!"]);
1404 }
1405
1406 #[test]
1408 fn a_short_final_unit_is_emitted_at_end_of_stream() {
1409 let mut p = Pipeline::new(meta(), vec![Box::new(Chop::new(4))]);
1410 let mut sink = Recorder::default();
1411
1412 let out = p.process(b"abcdef", &mut sink).unwrap();
1413 assert_eq!(parts(&out), [b"abcd".as_slice()]);
1414
1415 let out = p.finish(&mut sink).unwrap();
1416 assert_eq!(parts(&out), [b"ef".as_slice()]);
1417 }
1418
1419 #[test]
1422 fn framing_stages_compose() {
1423 let mut p = Pipeline::new(meta(), vec![Box::new(Chop::new(4)), Box::new(Chop::new(2))]);
1424 let mut sink = Recorder::default();
1425
1426 let out = p.process(b"abcdefgh", &mut sink).unwrap();
1427 assert_eq!(parts(&out), [b"ab".as_slice(), b"cd", b"ef", b"gh"]);
1428 }
1429
1430 #[test]
1434 fn a_stage_can_restart_its_own_schedule() {
1435 let period = Duration::from_secs(600);
1436 let mut p = Pipeline::new(meta(), vec![Box::new(Restart(Some(period)))]);
1437 let mut sink = Recorder::default();
1438 let start = Instant::now();
1439
1440 assert!(
1442 p.tick(start + period + Duration::from_secs(100), &mut sink)
1443 .unwrap()
1444 .is_some(),
1445 );
1446 assert!(
1447 p.tick(start + period + Duration::from_secs(200), &mut sink)
1448 .unwrap()
1449 .is_none(),
1450 "the cadence has moved past this",
1451 );
1452
1453 p.process(b"payload", &mut sink).unwrap();
1454
1455 assert!(
1456 p.tick(start + period + Duration::from_secs(300), &mut sink)
1457 .unwrap()
1458 .is_some(),
1459 "the chunk restarted the schedule, so a period from now is due \
1460 again well before the cadence would have come round",
1461 );
1462 }
1463
1464 #[test]
1465 fn rearming_a_stage_that_asked_for_no_ticks_does_nothing() {
1466 let mut p = Pipeline::new(meta(), vec![Box::new(Restart(None))]);
1467 let mut sink = Recorder::default();
1468
1469 assert_eq!(
1470 p.process(b"payload", &mut sink).unwrap().bytes(),
1471 b"payload"
1472 );
1473 assert!(p.tick(later(), &mut sink).unwrap().is_none());
1474 }
1475
1476 #[test]
1479 fn a_buffering_stage_under_a_framing_stage_holds_across_units() {
1480 let mut p = Pipeline::new(
1481 meta(),
1482 vec![Box::new(Chop::new(2)), Box::new(Reverse::default())],
1483 );
1484 let mut sink = Recorder::default();
1485
1486 assert!(p.process(b"abcd", &mut sink).unwrap().is_empty());
1487 assert_eq!(p.finish(&mut sink).unwrap().bytes(), b"dcba");
1488 }
1489}