1use std::collections::BTreeMap;
7use std::net::SocketAddr;
8use std::sync::{Arc, Mutex};
9use std::time::Instant;
10
11use hatchery_core::{
12 ChannelKind, DrainEnvelope, Frame, FrameError, FrameType, JournalReplyBody, OpenPayload,
13 MAX_DATA_PAYLOAD,
14};
15use tokio::io::{AsyncReadExt, AsyncWriteExt};
16use tokio::net::{TcpListener, TcpStream};
17use tokio::sync::watch;
18
19use super::buffer::{EnqueueOutcome, ReplayBuffer, StreamKey};
20use super::crypto::{c2_strip_outer, RestreamerKeys};
21use super::journal::{recover_gap, NodeJournal};
22
23#[derive(Clone, Debug)]
24pub struct RestreamerConfig {
25 pub node_listen: SocketAddr,
27 pub drain_listen: SocketAddr,
29 pub buffer: super::buffer::BufferLimits,
30 pub core_token: String,
33}
34
35#[derive(Debug)]
36pub enum RestreamerError {
37 Io(std::io::Error),
38 Config(&'static str),
39 Frame(FrameError),
40}
41
42impl std::fmt::Display for RestreamerError {
43 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44 match self {
45 Self::Io(e) => write!(f, "{e}"),
46 Self::Config(m) => write!(f, "{m}"),
47 Self::Frame(e) => write!(f, "{e}"),
48 }
49 }
50}
51
52impl std::error::Error for RestreamerError {}
53
54impl From<std::io::Error> for RestreamerError {
55 fn from(value: std::io::Error) -> Self {
56 Self::Io(value)
57 }
58}
59
60#[derive(Clone, Default)]
62pub struct UplinkPeers {
63 inner: Arc<Mutex<BTreeMap<String, PeerSlot>>>,
64}
65
66struct PeerSlot {
67 out: tokio::sync::mpsc::Sender<Frame>,
68 pending: Option<tokio::sync::oneshot::Sender<JournalReplyBody>>,
69}
70
71impl UplinkPeers {
72 pub fn new() -> Self {
73 Self::default()
74 }
75
76 pub fn register(&self, node_id: String, tx: tokio::sync::mpsc::Sender<Frame>) {
77 self.inner.lock().expect("peers").insert(
78 node_id,
79 PeerSlot {
80 out: tx,
81 pending: None,
82 },
83 );
84 }
85
86 pub fn unregister(&self, node_id: &str) {
87 self.inner.lock().expect("peers").remove(node_id);
88 }
89
90 pub fn deliver_reply(&self, node_id: &str, body: JournalReplyBody) {
91 let mut guard = self.inner.lock().expect("peers");
92 if let Some(slot) = guard.get_mut(node_id) {
93 if let Some(tx) = slot.pending.take() {
94 let _ = tx.send(body);
95 }
96 }
97 }
98
99 pub async fn send_to(&self, node_id: &str, frame: Frame) -> Result<(), RestreamerError> {
101 let out = {
102 let guard = self.inner.lock().expect("peers");
103 guard.get(node_id).map(|slot| slot.out.clone())
104 };
105 let Some(out) = out else {
106 return Err(RestreamerError::Frame(FrameError::Io("node uplink absent".into())));
107 };
108 out.send(frame)
109 .await
110 .map_err(|_| RestreamerError::Frame(FrameError::Io("node uplink closed".into())))
111 }
112
113 pub fn is_registered(&self, node_id: &str) -> bool {
114 self.inner.lock().expect("peers").contains_key(node_id)
115 }
116
117 pub async fn request_journal(
118 &self,
119 node_id: &str,
120 channel_id: u32,
121 after: u64,
122 limit: u32,
123 timeout: std::time::Duration,
124 ) -> Result<JournalReplyBody, RestreamerError> {
125 let (tx, rx) = tokio::sync::oneshot::channel();
126 let out = {
127 let mut guard = self.inner.lock().expect("peers");
128 let Some(slot) = guard.get_mut(node_id) else {
129 return Err(RestreamerError::Frame(FrameError::Io(
130 "node uplink absent".into(),
131 )));
132 };
133 slot.pending = Some(tx);
134 slot.out.clone()
135 };
136 out.send(Frame::journal_req(channel_id, after, limit))
137 .await
138 .map_err(|_| RestreamerError::Frame(FrameError::Io("node uplink closed".into())))?;
139 match tokio::time::timeout(timeout, rx).await {
140 Ok(Ok(body)) => Ok(body),
141 Ok(Err(_)) => Err(RestreamerError::Frame(FrameError::Io(
142 "journal reply dropped".into(),
143 ))),
144 Err(_) => Err(RestreamerError::Frame(FrameError::Io(
145 "journal reply timeout".into(),
146 ))),
147 }
148 }
149}
150
151struct Inner {
152 buffer: ReplayBuffer,
153 channels: BTreeMap<(String, u32), ChannelKind>,
155 keys: Option<RestreamerKeys>,
156 pushed_to_core: bool,
158}
159
160#[derive(Clone)]
162pub struct Restreamer {
163 uplink_ready: Arc<tokio::sync::Notify>,
165 inner: Arc<Mutex<Inner>>,
166 journal: Arc<dyn NodeJournal>,
167 peers: UplinkPeers,
168}
169
170impl Restreamer {
171 pub fn new(
172 buffer_limits: super::buffer::BufferLimits,
173 journal: Arc<dyn NodeJournal>,
174 core_token: impl Into<String>,
175 keys: Option<RestreamerKeys>,
176 ) -> Self {
177 let _ = core_token; Self {
179 uplink_ready: Arc::new(tokio::sync::Notify::new()),
180 inner: Arc::new(Mutex::new(Inner {
181 buffer: ReplayBuffer::new(buffer_limits).with_default_credit(1024),
186 channels: BTreeMap::new(),
187 keys,
188 pushed_to_core: false,
189 })),
190 journal,
191 peers: UplinkPeers::new(),
192 }
193 }
194
195 pub fn set_keys(&self, keys: RestreamerKeys) {
196 self.inner.lock().expect("lock").keys = Some(keys);
197 }
198
199 pub fn uplink_notify(&self) -> Arc<tokio::sync::Notify> {
200 self.uplink_ready.clone()
201 }
202
203 pub fn peers(&self) -> &UplinkPeers {
204 &self.peers
205 }
206
207 pub fn upstream_public_keys(&self) -> Option<(super::crypto::PublicKeyBytes, super::crypto::PublicKeyBytes)> {
208 self.inner
209 .lock()
210 .expect("lock")
211 .keys
212 .as_ref()
213 .map(|k| k.upstream_public_keys())
214 }
215
216 pub fn ever_pushed_to_core(&self) -> bool {
218 self.inner.lock().expect("lock").pushed_to_core
219 }
220
221 pub fn admit_from_node(
224 &self,
225 node_id: &str,
226 channel_id: u32,
227 payload: Vec<u8>,
228 ) -> Result<EnqueueOutcome, RestreamerError> {
229 if payload.len() > MAX_DATA_PAYLOAD {
230 return Err(RestreamerError::Frame(FrameError::PayloadTooLarge(payload.len())));
231 }
232 let mut guard = self.inner.lock().expect("lock");
233 let opaque = if let Some(keys) = guard.keys.clone() {
234 c2_strip_outer(&keys, &payload).map_err(|e| {
235 RestreamerError::Frame(FrameError::Io(format!("outer layer: {e}")))
236 })?
237 } else {
238 payload
239 };
240 let outcome = guard
242 .buffer
243 .enqueue(node_id, channel_id, opaque.clone(), Instant::now());
244 if let EnqueueOutcome::Accepted { .. } = outcome {
245 let seq = guard.buffer.next_seq(node_id, channel_id) - 1;
246 drop(guard);
247 let _ = (seq, opaque);
250 self.uplink_ready.notify_waiters();
251 }
252 Ok(outcome)
253 }
254
255 pub fn open_channel(&self, node_id: &str, channel_id: u32, kind: ChannelKind) {
256 self.inner
257 .lock()
258 .expect("lock")
259 .channels
260 .insert((node_id.to_owned(), channel_id), kind);
261 }
262
263 pub fn ack(&self, node_id: &str, channel_id: u32, seq: u64) {
264 self.inner
265 .lock()
266 .expect("lock")
267 .buffer
268 .ack(node_id, channel_id, seq);
269 }
270
271 pub fn window_credit(&self, node_id: &str, channel_id: u32) -> u32 {
272 let key = StreamKey {
273 node_id: node_id.to_owned(),
274 channel_id,
275 };
276 self.inner.lock().expect("lock").buffer.credit(&key)
277 }
278
279 pub fn resume(
281 &self,
282 node_id: &str,
283 channel_id: u32,
284 after: u64,
285 ) -> Result<Vec<DrainEnvelope>, RestreamerError> {
286 let (gap, oldest, buffered) = {
287 let guard = self.inner.lock().expect("lock");
288 let gap = guard.buffer.gap_before_buffer(node_id, channel_id, after);
289 let oldest = guard.buffer.oldest_seq(node_id, channel_id);
290 let buffered = guard.buffer.events_after(node_id, channel_id, after);
291 (gap, oldest, buffered)
292 };
293 let mut out = Vec::new();
294 if gap {
295 let until = oldest;
296 let recovered = recover_gap(
297 self.journal.as_ref(),
298 node_id,
299 channel_id,
300 after,
301 until,
302 1024,
303 )
304 .map_err(|e| RestreamerError::Frame(FrameError::Io(e.to_string())))?;
305 for ev in recovered {
306 out.push(DrainEnvelope {
307 node_id: ev.node_id.clone(),
308 frame: Frame::data(ev.channel_id, ev.seq, ev.payload)
309 .map_err(RestreamerError::Frame)?,
310 });
311 }
312 }
313 for ev in buffered {
314 if out.iter().any(|e| e.frame.seq == ev.seq) {
316 continue;
317 }
318 out.push(DrainEnvelope {
319 node_id: ev.node_id.clone(),
320 frame: Frame::data(ev.channel_id, ev.seq, ev.payload)
321 .map_err(RestreamerError::Frame)?,
322 });
323 }
324 Ok(out)
325 }
326
327 pub async fn resume_with_uplink(
329 &self,
330 node_id: &str,
331 channel_id: u32,
332 after: u64,
333 ) -> Result<Vec<DrainEnvelope>, RestreamerError> {
334 let (gap, oldest, buffered) = {
335 let guard = self.inner.lock().expect("lock");
336 let gap = guard.buffer.gap_before_buffer(node_id, channel_id, after);
337 let oldest = guard.buffer.oldest_seq(node_id, channel_id);
338 let buffered = guard.buffer.events_after(node_id, channel_id, after);
339 (gap, oldest, buffered)
340 };
341 let mut out = Vec::new();
342 if gap {
343 let until = oldest;
344 let recovered = if self.peers.is_registered(node_id) {
345 let body = self
346 .peers
347 .request_journal(
348 node_id,
349 channel_id,
350 after,
351 1024,
352 std::time::Duration::from_secs(5),
353 )
354 .await?;
355 body.entries
356 .into_iter()
357 .filter(|e| until.map(|u| e.seq < u).unwrap_or(true))
358 .map(|e| super::buffer::BufferedEvent {
359 seq: e.seq,
360 node_id: node_id.to_owned(),
361 channel_id,
362 payload: e.payload,
363 enqueued_at: Instant::now(),
364 })
365 .collect()
366 } else {
367 recover_gap(
368 self.journal.as_ref(),
369 node_id,
370 channel_id,
371 after,
372 until,
373 1024,
374 )
375 .map_err(|e| RestreamerError::Frame(FrameError::Io(e.to_string())))?
376 };
377 for ev in recovered {
378 out.push(DrainEnvelope {
379 node_id: ev.node_id.clone(),
380 frame: Frame::data(ev.channel_id, ev.seq, ev.payload)
381 .map_err(RestreamerError::Frame)?,
382 });
383 }
384 }
385 for ev in buffered {
386 if out.iter().any(|e| e.frame.seq == ev.seq) {
387 continue;
388 }
389 out.push(DrainEnvelope {
390 node_id: ev.node_id.clone(),
391 frame: Frame::data(ev.channel_id, ev.seq, ev.payload)
392 .map_err(RestreamerError::Frame)?,
393 });
394 }
395 Ok(out)
396 }
397
398 pub fn buffered_after(&self, node_id: &str, channel_id: u32, after: u64) -> Vec<DrainEnvelope> {
400 let guard = self.inner.lock().expect("lock");
401 guard
402 .buffer
403 .events_after(node_id, channel_id, after)
404 .into_iter()
405 .filter_map(|ev| {
406 Frame::data(ev.channel_id, ev.seq, ev.payload)
407 .ok()
408 .map(|frame| DrainEnvelope {
409 node_id: ev.node_id,
410 frame,
411 })
412 })
413 .collect()
414 }
415}
416
417pub async fn serve(
419 config: RestreamerConfig,
420 restreamer: Restreamer,
421 mut shutdown: watch::Receiver<bool>,
422) -> Result<(), RestreamerError> {
423 if config.node_listen == config.drain_listen {
424 return Err(RestreamerError::Config(
425 "node_listen and drain_listen must differ",
426 ));
427 }
428 if config.core_token.len() < 16 {
429 return Err(RestreamerError::Config("core_token too short"));
430 }
431 let node_listener = TcpListener::bind(config.node_listen).await?;
432 let drain_listener = TcpListener::bind(config.drain_listen).await?;
433 loop {
434 tokio::select! {
435 _ = shutdown.changed() => {
436 if *shutdown.borrow() { break; }
437 }
438 accepted = node_listener.accept() => {
439 let (stream, _) = accepted?;
440 let _ = stream.set_nodelay(true);
441 let rs = restreamer.clone();
442 tokio::spawn(async move {
443 if let Err(err) = handle_node_uplink(stream, rs).await {
444 tracing::debug!(error = %err, "node uplink closed");
445 }
446 });
447 }
448 accepted = drain_listener.accept() => {
449 let (stream, _) = accepted?;
450 let _ = stream.set_nodelay(true);
451 let rs = restreamer.clone();
452 let token = config.core_token.clone();
453 tokio::spawn(async move {
454 if let Err(err) = handle_core_drain(stream, rs, &token).await {
455 tracing::debug!(error = %err, "core drain closed");
456 }
457 });
458 }
459 }
460 }
461 Ok(())
462}
463
464async fn handle_node_uplink(mut stream: TcpStream, rs: Restreamer) -> Result<(), RestreamerError> {
465 let mut buf = vec![0u8; 64 * 1024];
466 let mut filled = 0usize;
467 let mut node_id: Option<String> = None;
468 let (out_tx, mut out_rx) = tokio::sync::mpsc::channel::<Frame>(16);
469 loop {
470 tokio::select! {
471 biased;
472 maybe_out = out_rx.recv() => {
473 match maybe_out {
474 Some(frame) => {
475 stream
476 .write_all(&frame.encode().map_err(RestreamerError::Frame)?)
477 .await?;
478 }
479 None => break,
480 }
481 }
482 read = stream.read(&mut buf[filled..]) => {
483 let n = read?;
484 if n == 0 {
485 break;
486 }
487 filled += n;
488 while filled > 0 {
489 match Frame::decode(&buf[..filled]) {
490 Ok((frame, consumed)) => {
491 buf.copy_within(consumed..filled, 0);
492 filled -= consumed;
493 match frame.frame_type {
494 FrameType::Open => {
495 let open = frame.parse_open().map_err(RestreamerError::Frame)?;
496 if let Some(prev) = node_id.replace(open.node_id.clone()) {
497 rs.peers().unregister(&prev);
498 }
499 rs.peers().register(open.node_id.clone(), out_tx.clone());
500 rs.open_channel(&open.node_id, frame.channel_id, open.channel_kind);
501 let credit = rs.window_credit(&open.node_id, frame.channel_id);
502 let win = Frame::window(frame.channel_id, credit);
503 stream
504 .write_all(&win.encode().map_err(RestreamerError::Frame)?)
505 .await?;
506 }
507 FrameType::Data => {
508 let Some(ref nid) = node_id else {
509 let go = Frame::goaway(1);
510 stream
511 .write_all(&go.encode().map_err(RestreamerError::Frame)?)
512 .await?;
513 return Ok(());
514 };
515 match rs.admit_from_node(nid, frame.channel_id, frame.payload)? {
516 EnqueueOutcome::Accepted { window_credit } => {
517 let win = Frame::window(frame.channel_id, window_credit);
518 stream
519 .write_all(
520 &win.encode().map_err(RestreamerError::Frame)?,
521 )
522 .await?;
523 }
524 EnqueueOutcome::Backpressure => {
525 tracing::warn!(
530 target: "restreamer",
531 node = %nid,
532 channel = frame.channel_id,
533 "restreamer credit exhausted; dropping node→core frame (Noise may desync)"
534 );
535 let win = Frame::window(frame.channel_id, 0);
536 stream
537 .write_all(
538 &win.encode().map_err(RestreamerError::Frame)?,
539 )
540 .await?;
541 }
542 }
543 }
544 FrameType::JournalReply => {
545 if let Some(ref nid) = node_id {
546 let body = JournalReplyBody::decode(&frame.payload)
547 .map_err(RestreamerError::Frame)?;
548 rs.peers().deliver_reply(nid, body);
549 }
550 }
551 FrameType::Ping => {
552 let pong = Frame::pong(frame.seq);
553 stream
554 .write_all(&pong.encode().map_err(RestreamerError::Frame)?)
555 .await?;
556 }
557 FrameType::Close | FrameType::GoAway => {
558 if let Some(ref nid) = node_id {
559 rs.peers().unregister(nid);
560 }
561 return Ok(());
562 }
563 _ => {}
564 }
565 }
566 Err(FrameError::Truncated) => break,
567 Err(err) => return Err(RestreamerError::Frame(err)),
568 }
569 }
570 if filled == buf.len() {
571 return Err(RestreamerError::Frame(FrameError::PayloadTooLarge(filled)));
572 }
573 }
574 }
575 }
576 if let Some(ref nid) = node_id {
577 rs.peers().unregister(nid);
578 }
579 Ok(())
580}
581
582async fn handle_core_drain(
583 mut stream: TcpStream,
584 rs: Restreamer,
585 expected_token: &str,
586) -> Result<(), RestreamerError> {
587 let mut auth = Vec::with_capacity(128);
589 loop {
590 let mut byte = [0u8; 1];
591 let n = stream.read(&mut byte).await?;
592 if n == 0 {
593 return Err(RestreamerError::Config("drain auth closed"));
594 }
595 if byte[0] == b'\n' {
596 break;
597 }
598 auth.push(byte[0]);
599 if auth.len() > 512 {
600 return Err(RestreamerError::Config("drain auth too long"));
601 }
602 }
603 if !tokens_equal(&auth, expected_token.as_bytes()) {
604 let go = Frame::goaway(403);
605 let _ = stream.write_all(&go.encode().unwrap_or_default()).await;
606 return Err(RestreamerError::Config("drain auth refused"));
607 }
608
609 let mut buf = vec![0u8; 64 * 1024];
610 let mut filled = 0usize;
611 let mut selected: Option<(String, u32)> = None;
613 let mut sent_through: u64 = 0;
615 let mut resumed = false;
617 let uplink_ready = rs.uplink_notify();
618 loop {
619 let woken = uplink_ready.notified();
621 tokio::pin!(woken);
622 woken.as_mut().enable();
623 if resumed {
625 if let Some((ref nid, ch)) = selected {
626 let envelopes = rs.buffered_after(nid, ch, sent_through);
627 for env in envelopes {
628 stream
629 .write_all(&env.encode().map_err(RestreamerError::Frame)?)
630 .await?;
631 sent_through = sent_through.max(env.frame.seq);
632 }
633 }
634 }
635
636 let read = tokio::select! {
639 r = stream.read(&mut buf[filled..]) => Ok(r),
640 _ = &mut woken => continue,
641 _ = tokio::time::sleep(std::time::Duration::from_millis(50)) => Err(()),
642 };
643 match read {
644 Ok(Ok(0)) => break,
645 Ok(Ok(n)) => {
646 filled += n;
647 while filled > 0 {
648 match Frame::decode(&buf[..filled]) {
649 Ok((frame, consumed)) => {
650 buf.copy_within(consumed..filled, 0);
651 filled -= consumed;
652 match frame.frame_type {
653 FrameType::Open => {
654 let open =
655 frame.parse_open().map_err(RestreamerError::Frame)?;
656 selected = Some((open.node_id, frame.channel_id));
657 sent_through = 0;
658 resumed = false;
659 }
660 FrameType::Ack => {
661 if let Some((ref nid, ch)) = selected {
662 rs.ack(nid, ch, frame.seq);
663 }
664 }
665 FrameType::Resume => {
666 if let Some((ref nid, ch)) = selected {
667 let after = frame.seq;
668 let envelopes = rs.resume_with_uplink(nid, ch, after).await?;
669 for env in envelopes {
670 stream
671 .write_all(
672 &env.encode().map_err(RestreamerError::Frame)?,
673 )
674 .await?;
675 sent_through = sent_through.max(env.frame.seq);
676 }
677 sent_through = sent_through.max(after);
679 resumed = true;
680 }
681 }
682 FrameType::Data => {
683 if let Some((ref nid, ch)) = selected {
685 let down = Frame::data(ch, frame.seq, frame.payload)
686 .map_err(RestreamerError::Frame)?;
687 if let Err(err) = rs.peers().send_to(nid, down).await {
688 tracing::debug!(error = %err, "downlink dropped");
689 }
690 }
691 }
692 FrameType::Ping => {
693 let pong = Frame::pong(frame.seq);
694 stream
695 .write_all(
696 &pong.encode().map_err(RestreamerError::Frame)?,
697 )
698 .await?;
699 }
700 FrameType::Close | FrameType::GoAway => return Ok(()),
701 _ => {}
702 }
703 }
704 Err(FrameError::Truncated) => break,
705 Err(err) => return Err(RestreamerError::Frame(err)),
706 }
707 }
708 }
709 Ok(Err(e)) => return Err(e.into()),
710 Err(_) => {
711 }
713 }
714 }
715 Ok(())
716}
717
718fn tokens_equal(a: &[u8], b: &[u8]) -> bool {
719 if a.len() != b.len() {
720 return false;
721 }
722 let mut diff = 0u8;
723 for (x, y) in a.iter().zip(b.iter()) {
724 diff |= x ^ y;
725 }
726 diff == 0
727}
728
729pub struct DrainClient {
731 addr: SocketAddr,
732 token: String,
733}
734
735impl DrainClient {
736 pub fn new(addr: SocketAddr, token: impl Into<String>) -> Self {
737 Self {
738 addr,
739 token: token.into(),
740 }
741 }
742
743 pub async fn connect(&self) -> Result<DrainSession, RestreamerError> {
744 let mut stream = TcpStream::connect(self.addr).await?;
745 let _ = stream.set_nodelay(true);
746 stream.write_all(self.token.as_bytes()).await?;
747 stream.write_all(b"\n").await?;
748 Ok(DrainSession { stream })
749 }
750}
751
752pub struct DrainSession {
753 stream: TcpStream,
754}
755
756impl DrainSession {
757 pub async fn open(&mut self, node_id: &str, channel_id: u32, kind: ChannelKind) -> Result<(), RestreamerError> {
758 let frame = Frame::open(
759 channel_id,
760 &OpenPayload {
761 channel_kind: kind,
762 node_id: node_id.to_owned(),
763 },
764 )
765 .map_err(RestreamerError::Frame)?;
766 self.stream
767 .write_all(&frame.encode().map_err(RestreamerError::Frame)?)
768 .await?;
769 Ok(())
770 }
771
772 pub async fn resume(&mut self, after: u64) -> Result<(), RestreamerError> {
773 let frame = Frame::resume(after);
774 self.stream
775 .write_all(&frame.encode().map_err(RestreamerError::Frame)?)
776 .await?;
777 Ok(())
778 }
779
780 pub async fn ack(&mut self, seq: u64) -> Result<(), RestreamerError> {
781 let frame = Frame::ack(seq);
782 self.stream
783 .write_all(&frame.encode().map_err(RestreamerError::Frame)?)
784 .await?;
785 Ok(())
786 }
787
788 pub async fn recv_envelope(
789 &mut self,
790 deadline: std::time::Duration,
791 ) -> Result<Option<DrainEnvelope>, RestreamerError> {
792 let mut buf = vec![0u8; 64 * 1024];
793 let n = match tokio::time::timeout(deadline, self.stream.read(&mut buf)).await {
794 Ok(Ok(0)) => return Ok(None),
795 Ok(Ok(n)) => n,
796 Ok(Err(e)) => return Err(e.into()),
797 Err(_) => return Ok(None),
798 };
799 let (env, _) = DrainEnvelope::decode(&buf[..n]).map_err(RestreamerError::Frame)?;
800 Ok(Some(env))
801 }
802}
803
804#[cfg(test)]
805mod tests {
806 use super::*;
807 use crate::restreamer::crypto::{core_open_inner, noise_session_seal, HqRecipientSecret};
808 use crate::restreamer::journal::MemoryJournal;
809 use hatchery_core::ChannelKind;
810 use std::time::Duration;
811
812 fn token() -> String {
813 "test-core-token-32bytes-minimum!".to_owned()
814 }
815
816 #[tokio::test]
817 async fn core_drains_and_acks_without_c2_push() {
818 let journal = Arc::new(MemoryJournal::new());
819 let rs = Restreamer::new(
820 super::super::buffer::BufferLimits {
821 max_events: 100,
822 max_bytes: 1_000_000,
823 max_age: Duration::from_secs(60),
824 },
825 journal,
826 token(),
827 None,
828 );
829 rs.open_channel("node-a", 1, ChannelKind::M4a);
830 assert!(matches!(
831 rs.admit_from_node("node-a", 1, b"cipher-1".to_vec()).unwrap(),
832 EnqueueOutcome::Accepted { .. }
833 ));
834 assert!(matches!(
835 rs.admit_from_node("node-a", 1, b"cipher-2".to_vec()).unwrap(),
836 EnqueueOutcome::Accepted { .. }
837 ));
838 let batch = rs.buffered_after("node-a", 1, 0);
839 assert_eq!(batch.len(), 2);
840 assert_eq!(batch[0].frame.payload, b"cipher-1");
841 rs.ack("node-a", 1, 1);
842 let batch = rs.buffered_after("node-a", 1, 0);
843 assert_eq!(batch.len(), 1);
844 assert_eq!(batch[0].frame.seq, 2);
845 assert!(!rs.ever_pushed_to_core());
846 }
847
848 #[tokio::test]
849 async fn live_drain_session_resume_and_ack() {
850 let journal = Arc::new(MemoryJournal::new());
851 for seq in 1..=5u64 {
853 journal.append("node-a", 1, seq, format!("j{seq}").into_bytes());
854 }
855 let rs = Restreamer::new(
856 super::super::buffer::BufferLimits {
857 max_events: 2,
858 max_bytes: 1024,
859 max_age: Duration::from_secs(60),
860 },
861 journal.clone(),
862 token(),
863 None,
864 );
865 rs.admit_from_node("node-a", 1, b"b1".to_vec()).unwrap();
868 rs.admit_from_node("node-a", 1, b"b2".to_vec()).unwrap();
869 rs.ack("node-a", 1, 2);
871 rs.admit_from_node("node-a", 1, b"b3".to_vec()).unwrap();
873 rs.admit_from_node("node-a", 1, b"b4".to_vec()).unwrap();
874
875 let journal2 = Arc::new(MemoryJournal::new());
879 for seq in 1..=4u64 {
880 journal2.append("node-b", 1, seq, format!("evt-{seq}").into_bytes());
881 }
882 let rs2 = Restreamer::new(
883 super::super::buffer::BufferLimits {
884 max_events: 10,
885 max_bytes: 10_000,
886 max_age: Duration::from_secs(60),
887 },
888 journal2,
889 token(),
890 None,
891 );
892 for i in 1..=4u8 {
894 rs2.admit_from_node("node-b", 1, vec![i]).unwrap();
895 }
896 rs2.ack("node-b", 1, 4);
897 assert!(rs2.buffered_after("node-b", 1, 0).is_empty());
898 let recovered = rs2.resume("node-b", 1, 0).unwrap();
899 assert_eq!(recovered.len(), 4);
900 assert_eq!(recovered[0].frame.payload, b"evt-1");
901 assert_eq!(recovered[3].frame.seq, 4);
902 assert!(!rs2.ever_pushed_to_core());
903 }
904
905 #[tokio::test]
906 async fn tcp_drain_end_to_end() {
907 let journal = Arc::new(MemoryJournal::new());
908 let rs = Restreamer::new(
909 super::super::buffer::BufferLimits::default(),
910 journal,
911 token(),
912 None,
913 );
914 let node_l = TcpListener::bind("127.0.0.1:0").await.unwrap();
915 let drain_l = TcpListener::bind("127.0.0.1:0").await.unwrap();
916 let node_addr = node_l.local_addr().unwrap();
917 let drain_addr = drain_l.local_addr().unwrap();
918 drop(node_l);
919 drop(drain_l);
920 let (shutdown_tx, shutdown_rx) = watch::channel(false);
921 let cfg = RestreamerConfig {
922 node_listen: node_addr,
923 drain_listen: drain_addr,
924 buffer: super::super::buffer::BufferLimits::default(),
925 core_token: token(),
926 };
927 let serve_rs = rs.clone();
928 let handle = tokio::spawn(async move { serve(cfg, serve_rs, shutdown_rx).await });
929
930 tokio::time::sleep(Duration::from_millis(20)).await;
932
933 let mut node = TcpStream::connect(node_addr).await.unwrap();
935 let open = Frame::open(
936 1,
937 &OpenPayload {
938 channel_kind: ChannelKind::G4aEvents,
939 node_id: "node-a".into(),
940 },
941 )
942 .unwrap();
943 node.write_all(&open.encode().unwrap()).await.unwrap();
944 let mut rb = [0u8; 64];
946 let _ = node.read(&mut rb).await.unwrap();
947 let data = Frame::data(1, 0, b"opaque-event".to_vec()).unwrap();
948 node.write_all(&data.encode().unwrap()).await.unwrap();
950 let _ = node.read(&mut rb).await.unwrap();
951
952 let client = DrainClient::new(drain_addr, token());
954 let mut session = client.connect().await.unwrap();
955 session
956 .open("node-a", 1, ChannelKind::G4aEvents)
957 .await
958 .unwrap();
959 session.resume(0).await.unwrap();
960 let env = session
961 .recv_envelope(Duration::from_secs(2))
962 .await
963 .unwrap()
964 .expect("envelope");
965 assert_eq!(env.node_id, "node-a");
966 assert_eq!(env.frame.payload, b"opaque-event");
967 session.ack(env.frame.seq).await.unwrap();
968
969 let _ = shutdown_tx.send(true);
970 let _ = handle.await;
971 assert!(!rs.ever_pushed_to_core());
972 }
973
974 #[test]
975 fn nested_crypto_on_admit_path() {
976 let hq = HqRecipientSecret::from_seed([11u8; 32]);
977 let keys = RestreamerKeys::from_seeds([13u8; 32], hq.public_bytes().0);
978 let journal = Arc::new(MemoryJournal::new());
979 let rs = Restreamer::new(
980 super::super::buffer::BufferLimits::default(),
981 journal,
982 token(),
983 Some(keys.clone()),
984 );
985 let sealed = noise_session_seal(&keys, &hq, b"visible-only-to-core").unwrap();
986 rs.admit_from_node("node-a", 1, sealed.outer).unwrap();
987 let env = &rs.buffered_after("node-a", 1, 0)[0];
988 assert!(!env
989 .frame
990 .payload
991 .windows(b"visible-only-to-core".len())
992 .any(|w| w == b"visible-only-to-core"));
993 let mut hq_tx = sealed.hq_transport;
994 let plain = core_open_inner(&mut hq_tx, &env.frame.payload).unwrap();
995 assert_eq!(plain, b"visible-only-to-core");
996 }
997
998 #[tokio::test]
999 async fn uplink_journal_req_served_from_file_journal() {
1000 use crate::restreamer::FileJournal;
1001 use hatchery_core::FrameType;
1002
1003 let dir = std::env::temp_dir().join(format!("upj-{}", std::process::id()));
1004 let _ = std::fs::remove_dir_all(&dir);
1005 let fj = FileJournal::open(dir.join("n.journal")).unwrap();
1006 for i in 0..5u8 {
1007 fj.append(1, &[i]).unwrap();
1008 }
1009 let journal = Arc::new(MemoryJournal::new());
1010 let rs = Restreamer::new(
1011 super::super::buffer::BufferLimits::default(),
1012 journal,
1013 token(),
1014 None,
1015 );
1016 let node_l = TcpListener::bind("127.0.0.1:0").await.unwrap();
1017 let drain_l = TcpListener::bind("127.0.0.1:0").await.unwrap();
1018 let node_addr = node_l.local_addr().unwrap();
1019 let drain_addr = drain_l.local_addr().unwrap();
1020 drop(node_l);
1021 drop(drain_l);
1022 let (shutdown_tx, shutdown_rx) = watch::channel(false);
1023 let cfg = RestreamerConfig {
1024 node_listen: node_addr,
1025 drain_listen: drain_addr,
1026 buffer: super::super::buffer::BufferLimits::default(),
1027 core_token: token(),
1028 };
1029 let serve_rs = rs.clone();
1030 let handle = tokio::spawn(async move { serve(cfg, serve_rs, shutdown_rx).await });
1031 tokio::time::sleep(Duration::from_millis(20)).await;
1032
1033 let fj_node = fj.clone();
1034 let node_task = tokio::spawn(async move {
1035 let mut node = TcpStream::connect(node_addr).await.unwrap();
1036 let open = Frame::open(
1037 1,
1038 &OpenPayload {
1039 channel_kind: ChannelKind::G4aEvents,
1040 node_id: "node-a".into(),
1041 },
1042 )
1043 .unwrap();
1044 node.write_all(&open.encode().unwrap()).await.unwrap();
1045 let mut rb = vec![0u8; 64 * 1024];
1046 let mut filled = 0usize;
1047 loop {
1048 let n = node.read(&mut rb[filled..]).await.unwrap();
1049 if n == 0 {
1050 break;
1051 }
1052 filled += n;
1053 while filled > 0 {
1054 match Frame::decode(&rb[..filled]) {
1055 Ok((frame, consumed)) => {
1056 rb.copy_within(consumed..filled, 0);
1057 filled -= consumed;
1058 if frame.frame_type == FrameType::JournalReq {
1059 let limit = frame.journal_req_limit().unwrap_or(64) as usize;
1060 let body = fj_node
1061 .reply_body(frame.channel_id, frame.seq, limit)
1062 .unwrap();
1063 let reply = Frame::journal_reply(frame.channel_id, &body).unwrap();
1064 node.write_all(&reply.encode().unwrap()).await.unwrap();
1065 }
1066 }
1067 Err(FrameError::Truncated) => break,
1068 Err(e) => panic!("{e}"),
1069 }
1070 }
1071 }
1072 });
1073
1074 for _ in 0..50 {
1075 if rs.peers().is_registered("node-a") {
1076 break;
1077 }
1078 tokio::time::sleep(Duration::from_millis(10)).await;
1079 }
1080 assert!(rs.peers().is_registered("node-a"));
1081 let body = rs
1082 .peers()
1083 .request_journal("node-a", 1, 2, 10, Duration::from_secs(2))
1084 .await
1085 .unwrap();
1086 assert_eq!(
1087 body.entries.iter().map(|e| e.seq).collect::<Vec<_>>(),
1088 vec![3, 4, 5]
1089 );
1090 assert_eq!(body.entries[0].payload, &[2]);
1091
1092 let _ = shutdown_tx.send(true);
1093 node_task.abort();
1094 let _ = handle.await;
1095 let _ = std::fs::remove_dir_all(&dir);
1096 }
1097}