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 Boundaries, BuildCtx, Ctx, EffectSink, Emission, Emit, Execution, ExternalStage, Needs,
57 PipelineMeta, Plugin, 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
211struct Input<'a> {
219 pub bytes: &'a [u8],
220 pub bounds: &'a [usize],
221 pub eof: bool,
222}
223
224struct Wiring<'a> {
231 meta: &'a PipelineMeta,
232 stage: &'a str,
233 dst: &'a mut Emission,
234 sink: &'a mut dyn EffectSink,
235}
236
237impl Wiring<'_> {
238 fn ctx<'b>(&'b mut self, bytes: &'b [u8]) -> Ctx<'b> {
243 Ctx::new(self.meta, self.stage, bytes, self.dst, self.sink)
244 }
245}
246
247impl Pipeline {
248 #[must_use]
249 pub fn new(meta: PipelineMeta, stages: Vec<Box<dyn Plugin>>) -> Self {
250 let names = stages.iter().map(|s| s.name().to_string()).collect();
251 Self::with_names(meta, stages, names)
252 }
253
254 #[must_use]
257 pub fn with_names(
258 meta: PipelineMeta,
259 stages: Vec<Box<dyn Plugin>>,
260 names: Vec<String>,
261 ) -> Self {
262 debug_assert_eq!(stages.len(), names.len());
263
264 let start = Instant::now();
269 let ticks = stages
270 .iter()
271 .enumerate()
272 .filter_map(|(stage, plugin)| {
273 let period = plugin.tick_interval().filter(|p| !p.is_zero())?;
274
275 Some(Schedule {
276 stage,
277 period,
278 next: start + period,
279 })
280 })
281 .collect();
282
283 Self {
284 meta,
285 stages,
286 names,
287 bufs: Buffers::default(),
288 ticks,
289 }
290 }
291
292 #[must_use]
293 pub fn meta(&self) -> &PipelineMeta {
294 &self.meta
295 }
296
297 #[must_use]
298 pub fn is_empty(&self) -> bool {
299 self.stages.is_empty()
300 }
301
302 #[must_use]
303 pub fn len(&self) -> usize {
304 self.stages.len()
305 }
306
307 pub fn stage_names(&self) -> impl Iterator<Item = &str> {
308 self.names.iter().map(String::as_str)
309 }
310
311 #[must_use]
318 pub fn tick_interval(&self) -> Option<Duration> {
319 self.ticks.iter().map(|schedule| schedule.period).min()
320 }
321
322 fn due(&mut self, now: Instant) -> Option<usize> {
324 let schedule = self
325 .ticks
326 .iter_mut()
327 .find(|schedule| schedule.next <= now)?;
328
329 schedule.next += schedule.period;
330
331 if schedule.next <= now {
335 schedule.next = now + schedule.period;
336 }
337
338 Some(schedule.stage)
339 }
340
341 fn rearm(&mut self, stage: usize) {
353 if let Some(schedule) = self.ticks.iter_mut().find(|s| s.stage == stage) {
354 schedule.next = Instant::now() + schedule.period;
355 }
356 }
357
358 pub fn tick<'p>(
370 &'p mut self,
371 now: Instant,
372 sink: &mut dyn EffectSink,
373 ) -> Result<Option<Emitted<'p>>> {
374 let Some(index) = self.due(now) else {
375 return Ok(None);
376 };
377
378 run_tick(
379 &mut self.stages[index],
380 &self.meta,
381 &self.names[index],
382 &mut self.bufs.a,
383 sink,
384 )?;
385
386 if self.bufs.a.rearm_requested() {
389 self.rearm(index);
390 }
391
392 if self.bufs.a.bytes().is_empty() {
395 return Ok(Some(Emitted::empty()));
396 }
397
398 self.drive(EMPTY, index + 1, Slot::A, false, sink).map(Some)
399 }
400
401 #[must_use]
403 pub fn datagram_hazard(&self) -> Option<&str> {
404 self.stages
405 .iter()
406 .zip(&self.names)
407 .find(|(stage, _)| !stage.boundaries().preserves_messages())
408 .map(|(_, name)| name.as_str())
409 }
410
411 pub fn declarations(&self) -> impl Iterator<Item = Declaration<'_>> {
416 self.stages
417 .iter()
418 .zip(&self.names)
419 .map(|(stage, name)| Declaration {
420 stage: name.as_str(),
421 boundaries: stage.boundaries(),
422 needs: stage.needs(),
423 })
424 }
425
426 pub fn process<'p>(
431 &'p mut self,
432 input: &'p [u8],
433 sink: &mut dyn EffectSink,
434 ) -> Result<Emitted<'p>> {
435 self.drive(input, 0, Slot::Input, false, sink)
436 }
437
438 pub fn finish<'p>(&'p mut self, sink: &mut dyn EffectSink) -> Result<Emitted<'p>> {
440 self.drive(EMPTY, 0, Slot::Input, true, sink)
441 }
442
443 fn drive<'p>(
449 &'p mut self,
450 input: &'p [u8],
451 from: usize,
452 live: Slot,
453 eof: bool,
454 sink: &mut dyn EffectSink,
455 ) -> Result<Emitted<'p>> {
456 let mut live = live;
457
458 for index in from..self.stages.len() {
459 let (slot, src, src_bounds, dst) = self.bufs.borrow(live, input);
460
461 let mut wiring = Wiring {
462 meta: &self.meta,
463 stage: &self.names[index],
464 dst,
465 sink,
466 };
467
468 let chunk = Input {
469 bytes: src,
470 bounds: src_bounds,
471 eof,
472 };
473
474 run(&mut *self.stages[index], &mut wiring, chunk)?;
475
476 let emitted = dst.emit();
477 let rearm = dst.rearm_requested();
478
479 if rearm {
480 self.rearm(index);
481 }
482
483 if emitted != Emit::Passthrough {
484 live = slot;
485 }
486
487 if !eof && self.bufs.live(live, input).0.is_empty() {
491 return Ok(Emitted::empty());
492 }
493 }
494
495 let (bytes, bounds) = self.bufs.live(live, input);
496
497 Ok(Emitted { bytes, bounds })
498 }
499}
500
501fn run(plugin: &mut dyn Plugin, wiring: &mut Wiring, input: Input) -> Result<()> {
507 wiring.dst.reset();
508
509 if input.bounds.is_empty() {
510 {
511 let mut ctx = wiring.ctx(input.bytes);
512
513 if input.eof {
514 if !input.bytes.is_empty() {
517 plugin.on_bytes(&mut ctx, input.bytes)?;
518 }
519
520 plugin.on_eof(&mut ctx)?;
521 } else {
522 plugin.on_bytes(&mut ctx, input.bytes)?;
523 }
524 }
525
526 if !wiring.dst.bounds().is_empty() {
529 wiring.dst.close();
530 }
531
532 return Ok(());
533 }
534
535 let mut copied = false;
536
537 for (index, unit) in units(input.bytes, input.bounds).enumerate() {
538 wiring.dst.next_unit();
539
540 {
541 let mut ctx = wiring.ctx(unit);
542 plugin.on_bytes(&mut ctx, unit)?;
543 }
544
545 if !copied {
546 if wiring.dst.emit() == Emit::Passthrough {
547 continue;
550 }
551
552 materialise(input.bytes, input.bounds, index, wiring.dst);
555 copied = true;
556 }
557
558 if wiring.dst.emit() == Emit::Passthrough {
559 wiring.dst.out.extend_from_slice(unit);
560 }
561
562 wiring.dst.close();
563 }
564
565 if input.eof {
566 wiring.dst.next_unit();
567
568 {
569 let mut ctx = wiring.ctx(EMPTY);
570 plugin.on_eof(&mut ctx)?;
571 }
572
573 if !copied && !wiring.dst.bytes().is_empty() {
576 materialise(input.bytes, input.bounds, input.bounds.len(), wiring.dst);
577 copied = true;
578 }
579
580 if copied {
581 wiring.dst.close();
582 }
583 }
584
585 wiring.dst.emit = if copied {
586 Emit::Buffered
587 } else {
588 Emit::Passthrough
589 };
590
591 Ok(())
592}
593
594fn run_tick(
597 plugin: &mut Box<dyn Plugin>,
598 meta: &PipelineMeta,
599 stage: &str,
600 dst: &mut Emission,
601 sink: &mut dyn EffectSink,
602) -> Result<()> {
603 dst.reset();
604
605 {
606 let mut ctx = Ctx::new(meta, stage, EMPTY, dst, sink);
607 plugin.on_tick(&mut ctx)?;
608 }
609
610 if !dst.bounds().is_empty() {
611 dst.close();
612 }
613
614 Ok(())
615}
616
617fn units<'a>(bytes: &'a [u8], bounds: &'a [usize]) -> impl Iterator<Item = &'a [u8]> {
619 let mut start = 0;
620
621 bounds.iter().map(move |&end| {
622 let unit = &bytes[start..end];
623 start = end;
624 unit
625 })
626}
627
628fn materialise(input: &[u8], in_bounds: &[usize], done: usize, dst: &mut Emission) {
636 let prefix = if done == 0 { 0 } else { in_bounds[done - 1] };
637
638 if prefix == 0 {
639 return;
640 }
641
642 dst.out.splice(0..0, input[..prefix].iter().copied());
643
644 for bound in dst.bounds.iter_mut() {
647 *bound += prefix;
648 }
649
650 dst.bounds.splice(0..0, in_bounds[..done].iter().copied());
651}
652
653impl fmt::Debug for Pipeline {
654 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
655 f.debug_struct("Pipeline")
656 .field("direction", &self.meta.direction)
657 .field("stages", &self.stage_names().collect::<Vec<_>>())
658 .finish()
659 }
660}
661
662#[expect(
665 clippy::large_enum_variant,
666 reason = "destructured once per segment per connection"
667)]
668#[derive(Debug)]
669pub enum Segment {
670 Inline(Pipeline),
671 Process(ExternalStage),
672}
673
674#[derive(Debug, Clone, Copy, PartialEq, Eq)]
676pub struct Declaration<'a> {
677 pub stage: &'a str,
678 pub boundaries: Boundaries,
679 pub needs: Needs,
680}
681
682#[derive(Debug, Clone, Copy, PartialEq, Eq)]
684pub enum Side {
685 Upstream,
687 Downstream,
689}
690
691impl Side {
692 #[must_use]
694 pub fn endpoint_role(self) -> &'static str {
695 match self {
696 Self::Upstream => "source",
697 Self::Downstream => "destination",
698 }
699 }
700}
701
702#[derive(Debug, Clone, Copy, PartialEq, Eq)]
707pub struct BoundaryFault<'a> {
708 pub stage: &'a str,
709 pub side: Side,
710 pub cause: Option<&'a str>,
711}
712
713#[derive(Debug)]
720pub struct Chain {
721 meta: PipelineMeta,
722 segments: Vec<Segment>,
723}
724
725impl Chain {
726 #[must_use]
727 pub fn new(meta: PipelineMeta, segments: Vec<Segment>) -> Self {
728 Self { meta, segments }
729 }
730
731 #[must_use]
732 pub fn meta(&self) -> &PipelineMeta {
733 &self.meta
734 }
735
736 #[must_use]
738 pub fn is_empty(&self) -> bool {
739 self.segments.is_empty()
740 }
741
742 #[must_use]
743 pub fn segments(&self) -> &[Segment] {
744 &self.segments
745 }
746
747 #[must_use]
748 pub fn into_segments(self) -> Vec<Segment> {
749 self.segments
750 }
751
752 #[must_use]
757 pub fn datagram_hazard(&self) -> Option<&str> {
758 self.segments().iter().find_map(|segment| match segment {
759 Segment::Inline(pipeline) => pipeline.datagram_hazard(),
760 Segment::Process(external) => Some(external.name.as_str()),
761 })
762 }
763
764 #[must_use]
769 pub fn declarations(&self) -> Vec<Declaration<'_>> {
770 self.segments
771 .iter()
772 .flat_map(|segment| match segment {
773 Segment::Inline(pipeline) => pipeline.declarations().collect::<Vec<_>>(),
774 Segment::Process(external) => vec![Declaration {
775 stage: external.name.as_str(),
776 boundaries: Boundaries::Fuse,
777 needs: Needs::Nothing,
778 }],
779 })
780 .collect()
781 }
782
783 #[must_use]
796 pub fn boundary_faults(
797 &self,
798 upstream_datagram: bool,
799 downstream_datagram: bool,
800 ) -> Vec<BoundaryFault<'_>> {
801 let declarations = self.declarations();
802 let mut faults = Vec::new();
803
804 for (index, declaration) in declarations.iter().enumerate() {
805 if declaration.needs.downstream() {
806 let cause = declarations[index + 1..]
807 .iter()
808 .find(|below| !below.boundaries.passes_downstream())
809 .map(|below| {
810 if below.boundaries.satisfies_downstream() {
811 None
812 } else {
813 Some(below.stage)
814 }
815 });
816
817 match cause {
818 None if !downstream_datagram => faults.push(BoundaryFault {
820 stage: declaration.stage,
821 side: Side::Downstream,
822 cause: None,
823 }),
824 Some(Some(stage)) => faults.push(BoundaryFault {
825 stage: declaration.stage,
826 side: Side::Downstream,
827 cause: Some(stage),
828 }),
829 _ => {}
830 }
831 }
832
833 if declaration.needs.upstream() {
834 let cause = declarations[..index]
835 .iter()
836 .rev()
837 .find(|above| !above.boundaries.passes_upstream())
838 .map(|above| {
839 if above.boundaries.satisfies_upstream() {
840 None
841 } else {
842 Some(above.stage)
843 }
844 });
845
846 match cause {
847 None if !upstream_datagram => faults.push(BoundaryFault {
848 stage: declaration.stage,
849 side: Side::Upstream,
850 cause: None,
851 }),
852 Some(Some(stage)) => faults.push(BoundaryFault {
853 stage: declaration.stage,
854 side: Side::Upstream,
855 cause: Some(stage),
856 }),
857 _ => {}
858 }
859 }
860 }
861
862 faults
863 }
864
865 #[must_use]
866 pub fn stage_names(&self) -> Vec<&str> {
867 self.segments
868 .iter()
869 .flat_map(|segment| match segment {
870 Segment::Inline(pipeline) => pipeline.stage_names().collect::<Vec<_>>(),
871 Segment::Process(external) => vec![external.name.as_str()],
872 })
873 .collect()
874 }
875}
876
877#[derive(Default)]
879pub struct Registry {
880 factories: BTreeMap<String, Arc<dyn PluginFactory>>,
881}
882
883impl Registry {
884 #[must_use]
885 pub fn new() -> Self {
886 Self::default()
887 }
888
889 pub fn register(&mut self, factory: impl PluginFactory) -> &mut Self {
890 self.register_arc(Arc::new(factory))
891 }
892
893 pub fn register_arc(&mut self, factory: Arc<dyn PluginFactory>) -> &mut Self {
894 self.factories.insert(normalize(factory.name()), factory);
895 self
896 }
897
898 #[must_use]
899 pub fn get(&self, name: &str) -> Option<&Arc<dyn PluginFactory>> {
900 self.factories.get(&normalize(name))
901 }
902
903 pub fn iter(&self) -> impl Iterator<Item = &Arc<dyn PluginFactory>> {
904 self.factories.values()
905 }
906
907 pub fn names(&self) -> impl Iterator<Item = &str> {
911 self.factories.values().map(|f| f.name())
913 }
914
915 pub fn build(
921 &self,
922 specs: &[PluginSpec],
923 meta: &PipelineMeta,
924 host: &mut dyn HostBuilder,
925 ) -> Result<Chain> {
926 let mut selected: Vec<&PluginSpec> = specs
927 .iter()
928 .filter(|spec| spec.direction.contains(meta.direction))
929 .collect();
930
931 if meta.direction == Direction::SinkToSource {
932 selected.reverse();
933 }
934
935 let display = display_names(&selected);
936
937 let mut labels = Vec::with_capacity(display.len() + 2);
940 labels.push(meta.upstream().to_string());
941 labels.extend(display.iter().cloned());
942 labels.push(meta.downstream().to_string());
943
944 let total = selected.len();
945 let mut segments: Vec<Segment> = Vec::new();
946 let mut draft: Option<SegmentDraft> = None;
947
948 for (index, spec) in selected.iter().enumerate() {
949 let factory = self
950 .get(&spec.name)
951 .ok_or_else(|| PluginError::unknown(&spec.name, self.names()))?
952 .clone();
953
954 let execution = match spec.detach {
955 Some(true) => Execution::Detached,
956 Some(false) => Execution::Inline,
957 None => factory.execution(),
958 };
959
960 let stage_info = StageInfo {
961 index,
962 total,
963 name: &display[index],
964 upstream: &labels[index],
965 downstream: &labels[index + 2],
966 };
967
968 let mut ctx = BuildCtx::new(&spec.name, &spec.config, meta, stage_info, host);
969
970 match factory.build(&mut ctx)? {
971 Stage::Filter(plugin) => {
972 if draft.is_none() || execution == Execution::Detached {
975 if let Some(ready) = draft.take() {
976 segments.push(Segment::Inline(ready.into_pipeline(meta.clone())));
977 }
978 draft = Some(SegmentDraft::default());
979 }
980
981 draft
982 .as_mut()
983 .expect("a draft was just ensured")
984 .push(plugin, display[index].clone());
985 }
986 Stage::External(external) => {
987 if spec.detach == Some(false) {
988 return Err(PluginError::config(
989 &spec.name,
990 "runs as a subprocess and always has its own task; `detach = false` \
991 cannot be honoured",
992 ));
993 }
994
995 if let Some(ready) = draft.take() {
996 segments.push(Segment::Inline(ready.into_pipeline(meta.clone())));
997 }
998
999 segments.push(Segment::Process(external));
1000 }
1001 }
1002 }
1003
1004 if let Some(ready) = draft.take() {
1005 segments.push(Segment::Inline(ready.into_pipeline(meta.clone())));
1006 }
1007
1008 Ok(Chain::new(meta.clone(), segments))
1009 }
1010
1011 pub fn build_pair(
1017 &self,
1018 specs: &[PluginSpec],
1019 source: &str,
1020 sink: &str,
1021 peer: Option<&str>,
1022 host: &mut dyn HostBuilder,
1023 ) -> Result<(Chain, Chain)> {
1024 let forward = PipelineMeta::new(Direction::SourceToSink, source, sink).with_peer(peer);
1025 let reverse = PipelineMeta {
1026 direction: Direction::SinkToSource,
1027 ..forward.clone()
1028 };
1029
1030 Ok((
1031 self.build(specs, &forward, host)?,
1032 self.build(specs, &reverse, host)?,
1033 ))
1034 }
1035}
1036
1037#[derive(Default)]
1040struct SegmentDraft {
1041 stages: Vec<Box<dyn Plugin>>,
1042 names: Vec<String>,
1043}
1044
1045impl SegmentDraft {
1046 fn push(&mut self, plugin: Box<dyn Plugin>, name: String) {
1047 self.stages.push(plugin);
1048 self.names.push(name);
1049 }
1050
1051 fn into_pipeline(self, meta: PipelineMeta) -> Pipeline {
1052 Pipeline::with_names(meta, self.stages, self.names)
1053 }
1054}
1055
1056fn display_names(specs: &[&PluginSpec]) -> Vec<String> {
1059 let base: Vec<&str> = specs
1060 .iter()
1061 .map(|spec| spec.alias.as_deref().unwrap_or(spec.name.as_str()))
1062 .collect();
1063
1064 let mut seen: BTreeMap<&str, usize> = BTreeMap::new();
1065 for name in &base {
1066 *seen.entry(name).or_insert(0) += 1;
1067 }
1068
1069 let mut used: BTreeMap<&str, usize> = BTreeMap::new();
1070 base.iter()
1071 .map(|name| {
1072 if seen.get(name).copied().unwrap_or(0) > 1 {
1073 let n = used.entry(name).or_insert(0);
1074 *n += 1;
1075 format!("{name}#{n}")
1076 } else {
1077 (*name).to_string()
1078 }
1079 })
1080 .collect()
1081}
1082
1083impl fmt::Debug for Registry {
1084 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1085 f.debug_struct("Registry")
1086 .field("plugins", &self.names().collect::<Vec<_>>())
1087 .finish()
1088 }
1089}
1090
1091#[cfg(test)]
1092mod tests {
1093 use super::*;
1094 use crate::{
1095 ChannelId, DirectionSpec,
1096 plugin::{LogLevel, StderrMode},
1097 };
1098
1099 #[derive(Default)]
1100 struct Recorder {
1101 writes: Vec<(ChannelId, Vec<u8>)>,
1102 logs: Vec<String>,
1103 }
1104
1105 impl EffectSink for Recorder {
1106 fn write(&mut self, channel: ChannelId, bytes: &[u8]) {
1107 self.writes.push((channel, bytes.to_vec()));
1108 }
1109
1110 fn log(&mut self, _level: LogLevel, stage: &str, message: &str) {
1111 self.logs.push(format!("{stage}: {message}"));
1112 }
1113 }
1114
1115 struct Observer;
1117
1118 impl Plugin for Observer {
1119 fn name(&self) -> &str {
1120 "observer"
1121 }
1122
1123 fn on_bytes(&mut self, ctx: &mut Ctx<'_>, input: &[u8]) -> Result<()> {
1124 ctx.side_write(ChannelId(0), input);
1125 ctx.pass_through();
1126 Ok(())
1127 }
1128 }
1129
1130 struct Upper;
1131
1132 impl Plugin for Upper {
1133 fn name(&self) -> &str {
1134 "upper"
1135 }
1136
1137 fn on_bytes(&mut self, ctx: &mut Ctx<'_>, input: &[u8]) -> Result<()> {
1138 let upper: Vec<u8> = input.iter().map(u8::to_ascii_uppercase).collect();
1139 ctx.forward(&upper);
1140 Ok(())
1141 }
1142 }
1143
1144 struct Declares {
1147 name: &'static str,
1148 boundaries: Boundaries,
1149 needs: Needs,
1150 }
1151
1152 impl Declares {
1153 fn boxed(name: &'static str, boundaries: Boundaries, needs: Needs) -> Box<dyn Plugin> {
1154 Box::new(Self {
1155 name,
1156 boundaries,
1157 needs,
1158 })
1159 }
1160 }
1161
1162 impl Plugin for Declares {
1163 fn name(&self) -> &str {
1164 self.name
1165 }
1166
1167 fn on_bytes(&mut self, ctx: &mut Ctx<'_>, _input: &[u8]) -> Result<()> {
1168 ctx.pass_through();
1169 Ok(())
1170 }
1171
1172 fn boundaries(&self) -> Boundaries {
1173 self.boundaries
1174 }
1175
1176 fn needs(&self) -> Needs {
1177 self.needs
1178 }
1179 }
1180
1181 fn declaring(stages: Vec<Box<dyn Plugin>>) -> Chain {
1183 let names = stages.iter().map(|s| s.name().to_owned()).collect();
1184
1185 Chain::new(
1186 meta(),
1187 vec![Segment::Inline(Pipeline::with_names(meta(), stages, names))],
1188 )
1189 }
1190
1191 #[derive(Default)]
1193 struct Reverse(Vec<u8>);
1194
1195 impl Plugin for Reverse {
1196 fn name(&self) -> &str {
1197 "reverse"
1198 }
1199
1200 fn on_bytes(&mut self, _ctx: &mut Ctx<'_>, input: &[u8]) -> Result<()> {
1201 self.0.extend_from_slice(input);
1202 Ok(())
1203 }
1204
1205 fn on_eof(&mut self, ctx: &mut Ctx<'_>) -> Result<()> {
1206 let mut buf = std::mem::take(&mut self.0);
1207 buf.reverse();
1208 ctx.forward(&buf);
1209 Ok(())
1210 }
1211 }
1212
1213 struct Beacon(Duration);
1215
1216 impl Plugin for Beacon {
1217 fn name(&self) -> &str {
1218 "beacon"
1219 }
1220
1221 fn on_bytes(&mut self, ctx: &mut Ctx<'_>, _input: &[u8]) -> Result<()> {
1222 ctx.pass_through();
1223 Ok(())
1224 }
1225
1226 fn tick_interval(&self) -> Option<Duration> {
1227 Some(self.0)
1228 }
1229
1230 fn on_tick(&mut self, ctx: &mut Ctx<'_>) -> Result<()> {
1231 ctx.forward(b"ping");
1232 Ok(())
1233 }
1234 }
1235
1236 struct Quiet(Duration);
1239
1240 impl Plugin for Quiet {
1241 fn name(&self) -> &str {
1242 "quiet"
1243 }
1244
1245 fn on_bytes(&mut self, ctx: &mut Ctx<'_>, _input: &[u8]) -> Result<()> {
1246 ctx.pass_through();
1247 Ok(())
1248 }
1249
1250 fn tick_interval(&self) -> Option<Duration> {
1251 Some(self.0)
1252 }
1253
1254 fn on_tick(&mut self, ctx: &mut Ctx<'_>) -> Result<()> {
1255 ctx.log(LogLevel::Info, "still here");
1256 Ok(())
1257 }
1258 }
1259
1260 struct Chop {
1263 size: usize,
1264 held: Vec<u8>,
1265 }
1266
1267 impl Chop {
1268 fn new(size: usize) -> Self {
1269 Self {
1270 size,
1271 held: Vec::new(),
1272 }
1273 }
1274 }
1275
1276 impl Plugin for Chop {
1277 fn name(&self) -> &str {
1278 "chop"
1279 }
1280
1281 fn on_bytes(&mut self, ctx: &mut Ctx<'_>, input: &[u8]) -> Result<()> {
1282 self.held.extend_from_slice(input);
1283
1284 while self.held.len() >= self.size {
1285 let rest = self.held.split_off(self.size);
1286 ctx.forward(&self.held);
1287 ctx.boundary();
1288 self.held = rest;
1289 }
1290
1291 Ok(())
1292 }
1293
1294 fn on_eof(&mut self, ctx: &mut Ctx<'_>) -> Result<()> {
1295 if !self.held.is_empty() {
1296 let held = std::mem::take(&mut self.held);
1297 ctx.forward(&held);
1298 ctx.boundary();
1299 }
1300
1301 Ok(())
1302 }
1303 }
1304
1305 struct Sieve(u8);
1308
1309 impl Plugin for Sieve {
1310 fn name(&self) -> &str {
1311 "sieve"
1312 }
1313
1314 fn on_bytes(&mut self, ctx: &mut Ctx<'_>, input: &[u8]) -> Result<()> {
1315 if input.first() == Some(&self.0) {
1316 ctx.drop_chunk();
1317 } else {
1318 ctx.pass_through();
1319 }
1320
1321 Ok(())
1322 }
1323 }
1324
1325 struct Trailer(&'static [u8]);
1328
1329 impl Plugin for Trailer {
1330 fn name(&self) -> &str {
1331 "trailer"
1332 }
1333
1334 fn on_bytes(&mut self, ctx: &mut Ctx<'_>, _input: &[u8]) -> Result<()> {
1335 ctx.pass_through();
1336 Ok(())
1337 }
1338
1339 fn on_eof(&mut self, ctx: &mut Ctx<'_>) -> Result<()> {
1340 ctx.forward(self.0);
1341 Ok(())
1342 }
1343 }
1344
1345 struct Restart(Option<Duration>);
1348
1349 impl Plugin for Restart {
1350 fn name(&self) -> &str {
1351 "restart"
1352 }
1353
1354 fn on_bytes(&mut self, ctx: &mut Ctx<'_>, _input: &[u8]) -> Result<()> {
1355 ctx.rearm();
1356 ctx.pass_through();
1357 Ok(())
1358 }
1359
1360 fn tick_interval(&self) -> Option<Duration> {
1361 self.0
1362 }
1363 }
1364
1365 fn meta() -> PipelineMeta {
1366 PipelineMeta::new(Direction::SourceToSink, "src", "sink")
1367 }
1368
1369 fn later() -> Instant {
1371 Instant::now() + Duration::from_secs(3600)
1372 }
1373
1374 fn parts<'a>(emitted: &Emitted<'a>) -> Vec<&'a [u8]> {
1376 emitted.units().collect()
1377 }
1378
1379 #[test]
1380 fn empty_pipeline_returns_the_input_slice() {
1381 let mut p = Pipeline::new(meta(), Vec::new());
1382 let mut sink = Recorder::default();
1383 let input = b"hello";
1384
1385 let out = p.process(input, &mut sink).unwrap();
1386 assert!(std::ptr::eq(out.bytes().as_ptr(), input.as_ptr()));
1387 }
1388
1389 #[test]
1390 fn observers_never_copy_the_payload() {
1391 let mut p = Pipeline::new(meta(), vec![Box::new(Observer), Box::new(Observer)]);
1392 let mut sink = Recorder::default();
1393 let input = b"payload";
1394
1395 let out = p.process(input, &mut sink).unwrap();
1396
1397 assert!(
1398 std::ptr::eq(out.bytes().as_ptr(), input.as_ptr()),
1399 "a chain of observers must hand the original buffer downstream",
1400 );
1401 assert_eq!(sink.writes.len(), 2);
1402 }
1403
1404 #[test]
1405 fn stages_chain_in_order() {
1406 let mut p = Pipeline::new(meta(), vec![Box::new(Upper), Box::new(Reverse::default())]);
1407 let mut sink = Recorder::default();
1408
1409 assert!(p.process(b"ab", &mut sink).unwrap().is_empty());
1410 assert!(p.process(b"cd", &mut sink).unwrap().is_empty());
1411 assert_eq!(p.finish(&mut sink).unwrap().bytes(), b"DCBA");
1412 }
1413
1414 #[test]
1415 fn repeated_plugins_get_distinct_display_names() {
1416 let specs = [
1417 PluginSpec::new("tee", DirectionSpec::Both),
1418 PluginSpec::new("tee", DirectionSpec::Both).named("audit"),
1419 PluginSpec::new("tee", DirectionSpec::Both),
1420 ];
1421 let refs: Vec<&PluginSpec> = specs.iter().collect();
1422
1423 assert_eq!(display_names(&refs), ["tee#1", "audit", "tee#2"]);
1424 }
1425
1426 #[test]
1427 fn a_pipeline_with_nothing_ticking_has_no_schedule() {
1428 let mut p = Pipeline::new(meta(), vec![Box::new(Observer)]);
1429 let mut sink = Recorder::default();
1430
1431 assert_eq!(p.tick_interval(), None, "so the host builds no timer");
1432 assert!(p.tick(later(), &mut sink).unwrap().is_none());
1433 }
1434
1435 #[test]
1438 fn the_schedule_is_the_shortest_period_asked_for() {
1439 let p = Pipeline::new(
1440 meta(),
1441 vec![
1442 Box::new(Quiet(Duration::from_secs(30))),
1443 Box::new(Beacon(Duration::from_secs(5))),
1444 ],
1445 );
1446
1447 assert_eq!(p.tick_interval(), Some(Duration::from_secs(5)));
1448 }
1449
1450 #[test]
1451 fn a_tick_cascades_through_the_stages_below_it() {
1452 let mut p = Pipeline::new(
1453 meta(),
1454 vec![Box::new(Beacon(Duration::from_secs(60))), Box::new(Upper)],
1455 );
1456 let mut sink = Recorder::default();
1457
1458 assert!(
1459 p.tick(Instant::now(), &mut sink).unwrap().is_none(),
1460 "not due yet",
1461 );
1462
1463 let now = later();
1464 assert_eq!(p.tick(now, &mut sink).unwrap().unwrap().bytes(), b"PING");
1465 assert!(
1466 p.tick(now, &mut sink).unwrap().is_none(),
1467 "one turn per stage per wakeup, however far behind the schedule is",
1468 );
1469 }
1470
1471 #[test]
1474 fn a_silent_tick_does_not_disturb_the_stages_below() {
1475 let mut p = Pipeline::new(
1476 meta(),
1477 vec![Box::new(Quiet(Duration::from_secs(60))), Box::new(Observer)],
1478 );
1479 let mut sink = Recorder::default();
1480
1481 assert!(p.tick(later(), &mut sink).unwrap().unwrap().is_empty());
1482 assert!(sink.writes.is_empty(), "the observer below never ran");
1483 assert_eq!(sink.logs, ["quiet: still here"]);
1484 }
1485
1486 #[test]
1489 fn ticks_and_chunks_do_not_interfere() {
1490 let mut p = Pipeline::new(
1491 meta(),
1492 vec![
1493 Box::new(Observer),
1494 Box::new(Beacon(Duration::from_secs(60))),
1495 ],
1496 );
1497 let mut sink = Recorder::default();
1498
1499 assert_eq!(
1500 p.tick(later(), &mut sink).unwrap().unwrap().bytes(),
1501 b"ping"
1502 );
1503 assert!(
1504 sink.writes.is_empty(),
1505 "the observer sits above the beacon and saw nothing",
1506 );
1507
1508 assert_eq!(
1509 p.process(b"payload", &mut sink).unwrap().bytes(),
1510 b"payload"
1511 );
1512 assert_eq!(sink.writes, [(ChannelId(0), b"payload".to_vec())]);
1513 }
1514
1515 #[test]
1516 fn two_stages_due_at_once_each_get_a_turn() {
1517 let mut p = Pipeline::new(
1518 meta(),
1519 vec![
1520 Box::new(Beacon(Duration::from_secs(60))),
1521 Box::new(Quiet(Duration::from_secs(60))),
1522 ],
1523 );
1524 let mut sink = Recorder::default();
1525 let now = later();
1526
1527 assert_eq!(p.tick(now, &mut sink).unwrap().unwrap().bytes(), b"ping");
1528 assert!(p.tick(now, &mut sink).unwrap().unwrap().is_empty());
1529 assert!(p.tick(now, &mut sink).unwrap().is_none());
1530 }
1531
1532 #[test]
1533 fn transform_then_observe_keeps_the_transformed_bytes() {
1534 let mut p = Pipeline::new(meta(), vec![Box::new(Upper), Box::new(Observer)]);
1535 let mut sink = Recorder::default();
1536
1537 assert_eq!(p.process(b"hi", &mut sink).unwrap().bytes(), b"HI");
1538 assert_eq!(sink.writes[0].1, b"HI".to_vec());
1539 }
1540
1541 #[test]
1543 fn an_unframed_emission_is_one_unit() {
1544 let mut p = Pipeline::new(meta(), vec![Box::new(Upper)]);
1545 let mut sink = Recorder::default();
1546
1547 let out = p.process(b"hi", &mut sink).unwrap();
1548 assert_eq!(parts(&out), [b"HI".as_slice()]);
1549 }
1550
1551 #[test]
1552 fn an_empty_emission_has_no_units() {
1553 assert!(Emitted::empty().units().next().is_none());
1554 }
1555
1556 #[test]
1559 fn a_stage_can_emit_several_units_from_one_chunk() {
1560 let mut p = Pipeline::new(meta(), vec![Box::new(Chop::new(2))]);
1561 let mut sink = Recorder::default();
1562
1563 let out = p.process(b"abcdef", &mut sink).unwrap();
1564
1565 assert_eq!(out.bytes(), b"abcdef", "the bytes are still the bytes");
1566 assert_eq!(parts(&out), [b"ab".as_slice(), b"cd", b"ef"]);
1567 }
1568
1569 #[test]
1572 fn framing_survives_a_stage_that_rewrites_it() {
1573 let mut p = Pipeline::new(meta(), vec![Box::new(Chop::new(2)), Box::new(Upper)]);
1574 let mut sink = Recorder::default();
1575
1576 let out = p.process(b"abcdef", &mut sink).unwrap();
1577 assert_eq!(parts(&out), [b"AB".as_slice(), b"CD", b"EF"]);
1578 }
1579
1580 #[test]
1583 fn an_observer_under_a_framing_stage_still_copies_nothing() {
1584 let mut p = Pipeline::new(meta(), vec![Box::new(Chop::new(2)), Box::new(Observer)]);
1585 let mut sink = Recorder::default();
1586
1587 let out = p.process(b"abcdef", &mut sink).unwrap();
1588
1589 assert_eq!(parts(&out), [b"ab".as_slice(), b"cd", b"ef"]);
1590 assert_eq!(
1591 sink.writes.len(),
1592 3,
1593 "the observer was called once per unit, not once per chunk",
1594 );
1595 }
1596
1597 #[test]
1600 fn units_passed_through_before_a_drop_are_kept() {
1601 let mut p = Pipeline::new(meta(), vec![Box::new(Chop::new(2)), Box::new(Sieve(b'c'))]);
1602 let mut sink = Recorder::default();
1603
1604 let out = p.process(b"abcdef", &mut sink).unwrap();
1605
1606 assert_eq!(out.bytes(), b"abef");
1607 assert_eq!(parts(&out), [b"ab".as_slice(), b"ef"]);
1608 }
1609
1610 #[test]
1613 fn dropping_the_first_unit_keeps_the_rest() {
1614 let mut p = Pipeline::new(meta(), vec![Box::new(Chop::new(2)), Box::new(Sieve(b'a'))]);
1615 let mut sink = Recorder::default();
1616
1617 let out = p.process(b"abcdef", &mut sink).unwrap();
1618
1619 assert_eq!(parts(&out), [b"cd".as_slice(), b"ef"]);
1620 }
1621
1622 #[test]
1625 fn an_epilogue_after_a_run_of_passthroughs_keeps_both() {
1626 let mut p = Pipeline::new(
1627 meta(),
1628 vec![Box::new(Chop::new(4)), Box::new(Trailer(b"!"))],
1629 );
1630 let mut sink = Recorder::default();
1631
1632 let out = p.process(b"abcdef", &mut sink).unwrap();
1633 assert_eq!(parts(&out), [b"abcd".as_slice()]);
1634
1635 let out = p.finish(&mut sink).unwrap();
1636 assert_eq!(out.bytes(), b"ef!");
1637 assert_eq!(parts(&out), [b"ef".as_slice(), b"!"]);
1638 }
1639
1640 #[test]
1642 fn a_short_final_unit_is_emitted_at_end_of_stream() {
1643 let mut p = Pipeline::new(meta(), vec![Box::new(Chop::new(4))]);
1644 let mut sink = Recorder::default();
1645
1646 let out = p.process(b"abcdef", &mut sink).unwrap();
1647 assert_eq!(parts(&out), [b"abcd".as_slice()]);
1648
1649 let out = p.finish(&mut sink).unwrap();
1650 assert_eq!(parts(&out), [b"ef".as_slice()]);
1651 }
1652
1653 #[test]
1656 fn framing_stages_compose() {
1657 let mut p = Pipeline::new(meta(), vec![Box::new(Chop::new(4)), Box::new(Chop::new(2))]);
1658 let mut sink = Recorder::default();
1659
1660 let out = p.process(b"abcdefgh", &mut sink).unwrap();
1661 assert_eq!(parts(&out), [b"ab".as_slice(), b"cd", b"ef", b"gh"]);
1662 }
1663
1664 #[test]
1668 fn a_stage_can_restart_its_own_schedule() {
1669 let period = Duration::from_secs(600);
1670 let mut p = Pipeline::new(meta(), vec![Box::new(Restart(Some(period)))]);
1671 let mut sink = Recorder::default();
1672 let start = Instant::now();
1673
1674 assert!(
1676 p.tick(start + period + Duration::from_secs(100), &mut sink)
1677 .unwrap()
1678 .is_some(),
1679 );
1680 assert!(
1681 p.tick(start + period + Duration::from_secs(200), &mut sink)
1682 .unwrap()
1683 .is_none(),
1684 "the cadence has moved past this",
1685 );
1686
1687 p.process(b"payload", &mut sink).unwrap();
1688
1689 assert!(
1690 p.tick(start + period + Duration::from_secs(300), &mut sink)
1691 .unwrap()
1692 .is_some(),
1693 "the chunk restarted the schedule, so a period from now is due \
1694 again well before the cadence would have come round",
1695 );
1696 }
1697
1698 #[test]
1699 fn rearming_a_stage_that_asked_for_no_ticks_does_nothing() {
1700 let mut p = Pipeline::new(meta(), vec![Box::new(Restart(None))]);
1701 let mut sink = Recorder::default();
1702
1703 assert_eq!(
1704 p.process(b"payload", &mut sink).unwrap().bytes(),
1705 b"payload"
1706 );
1707 assert!(p.tick(later(), &mut sink).unwrap().is_none());
1708 }
1709
1710 #[test]
1713 fn a_buffering_stage_under_a_framing_stage_holds_across_units() {
1714 let mut p = Pipeline::new(
1715 meta(),
1716 vec![Box::new(Chop::new(2)), Box::new(Reverse::default())],
1717 );
1718 let mut sink = Recorder::default();
1719
1720 assert!(p.process(b"abcd", &mut sink).unwrap().is_empty());
1721 assert_eq!(p.finish(&mut sink).unwrap().bytes(), b"dcba");
1722 }
1723 fn seal() -> Box<dyn Plugin> {
1733 Declares::boxed("frame", Boundaries::Seal, Needs::Nothing)
1734 }
1735
1736 fn split() -> Box<dyn Plugin> {
1737 Declares::boxed("unframe", Boundaries::Split, Needs::Nothing)
1738 }
1739
1740 fn fuse() -> Box<dyn Plugin> {
1741 Declares::boxed("compress", Boundaries::Fuse, Needs::Nothing)
1742 }
1743
1744 fn preserve() -> Box<dyn Plugin> {
1745 Declares::boxed("hash", Boundaries::Preserve, Needs::Nothing)
1746 }
1747
1748 fn needs_below() -> Box<dyn Plugin> {
1749 Declares::boxed("needs-below", Boundaries::Preserve, Needs::Downstream)
1750 }
1751
1752 fn needs_above() -> Box<dyn Plugin> {
1753 Declares::boxed("needs-above", Boundaries::Preserve, Needs::Upstream)
1754 }
1755
1756 #[test]
1757 fn a_datagram_endpoint_answers_a_requirement_by_itself() {
1758 assert!(
1759 declaring(vec![needs_below()])
1760 .boundary_faults(false, true)
1761 .is_empty(),
1762 "a datagram sink carries whatever units it was handed",
1763 );
1764 assert!(
1765 declaring(vec![needs_above()])
1766 .boundary_faults(true, false)
1767 .is_empty(),
1768 "a datagram source delivers whole messages",
1769 );
1770 }
1771
1772 #[test]
1773 fn a_byte_endpoint_does_not_and_says_which_one() {
1774 let binding = declaring(vec![needs_below()]);
1775 let faults = binding.boundary_faults(true, false);
1776
1777 assert_eq!(faults.len(), 1);
1778 assert_eq!(faults[0].stage, "needs-below");
1779 assert_eq!(faults[0].side, Side::Downstream);
1780 assert_eq!(faults[0].cause, None, "no stage broke it; the endpoint did");
1781
1782 let binding = declaring(vec![needs_above()]);
1783 let faults = binding.boundary_faults(false, true);
1784
1785 assert_eq!(faults.len(), 1);
1786 assert_eq!(faults[0].side, Side::Upstream);
1787 }
1788
1789 #[test]
1792 fn framing_satisfies_a_requirement_a_stream_endpoint_would_not() {
1793 let chain = declaring(vec![needs_below(), seal()]);
1794 assert!(chain.boundary_faults(false, false).is_empty());
1795
1796 let chain = declaring(vec![split(), needs_above()]);
1797 assert!(chain.boundary_faults(false, false).is_empty());
1798 }
1799
1800 #[test]
1803 fn a_seal_covers_a_fuse_beneath_it() {
1804 let chain = declaring(vec![needs_below(), seal(), fuse()]);
1805
1806 assert!(chain.boundary_faults(false, false).is_empty());
1807 }
1808
1809 #[test]
1812 fn a_fuse_before_the_seal_is_a_fault_that_names_it() {
1813 let binding = declaring(vec![needs_below(), fuse(), seal()]);
1814 let faults = binding.boundary_faults(false, false);
1815
1816 assert_eq!(faults.len(), 1);
1817 assert_eq!(faults[0].stage, "needs-below");
1818 assert_eq!(faults[0].cause, Some("compress"));
1819 }
1820
1821 #[test]
1823 fn preserving_stages_do_not_settle_a_scan_either_way() {
1824 let chain = declaring(vec![split(), preserve(), needs_above(), preserve(), seal()]);
1825
1826 assert!(chain.boundary_faults(false, false).is_empty());
1827 }
1828
1829 #[test]
1832 fn a_split_below_does_not_carry_units_from_above() {
1833 let binding = declaring(vec![needs_below(), split()]);
1834 let faults = binding.boundary_faults(false, false);
1835
1836 assert_eq!(faults.len(), 1);
1837 assert_eq!(faults[0].cause, Some("unframe"));
1838 }
1839
1840 #[test]
1843 fn a_seal_above_is_transparent_to_an_upstream_scan() {
1844 let chain = declaring(vec![split(), seal(), needs_above()]);
1845 assert!(chain.boundary_faults(false, false).is_empty());
1846
1847 let binding = declaring(vec![seal(), needs_above()]);
1848 let faults = binding.boundary_faults(false, false);
1849 assert_eq!(
1850 faults.len(),
1851 1,
1852 "nothing above the seal supplies boundaries"
1853 );
1854 assert_eq!(faults[0].cause, None);
1855 }
1856
1857 #[test]
1859 fn a_detached_process_fuses() {
1860 let chain = Chain::new(
1861 meta(),
1862 vec![
1863 Segment::Inline(Pipeline::with_names(
1864 meta(),
1865 vec![needs_below()],
1866 vec!["needs-below".to_owned()],
1867 )),
1868 Segment::Process(ExternalStage {
1869 argv: vec!["cat".to_owned()],
1870 shell: false,
1871 stderr: StderrMode::Log,
1872 name: "process".to_owned(),
1873 }),
1874 ],
1875 );
1876
1877 let faults = chain.boundary_faults(true, true);
1878
1879 assert_eq!(faults.len(), 1);
1880 assert_eq!(faults[0].cause, Some("process"));
1881 }
1882
1883 #[test]
1886 fn only_fusing_and_splitting_are_datagram_hazards() {
1887 assert_eq!(declaring(vec![preserve()]).datagram_hazard(), None);
1888 assert_eq!(declaring(vec![seal()]).datagram_hazard(), None);
1889 assert_eq!(declaring(vec![fuse()]).datagram_hazard(), Some("compress"));
1890 assert_eq!(declaring(vec![split()]).datagram_hazard(), Some("unframe"));
1891 }
1892}