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 inner: Arc<Mutex<Inner>>,
164 journal: Arc<dyn NodeJournal>,
165 peers: UplinkPeers,
166}
167
168impl Restreamer {
169 pub fn new(
170 buffer_limits: super::buffer::BufferLimits,
171 journal: Arc<dyn NodeJournal>,
172 core_token: impl Into<String>,
173 keys: Option<RestreamerKeys>,
174 ) -> Self {
175 let _ = core_token; Self {
177 inner: Arc::new(Mutex::new(Inner {
178 buffer: ReplayBuffer::new(buffer_limits).with_default_credit(64),
179 channels: BTreeMap::new(),
180 keys,
181 pushed_to_core: false,
182 })),
183 journal,
184 peers: UplinkPeers::new(),
185 }
186 }
187
188 pub fn set_keys(&self, keys: RestreamerKeys) {
189 self.inner.lock().expect("lock").keys = Some(keys);
190 }
191
192 pub fn peers(&self) -> &UplinkPeers {
193 &self.peers
194 }
195
196 pub fn upstream_public_keys(&self) -> Option<(super::crypto::PublicKeyBytes, super::crypto::PublicKeyBytes)> {
197 self.inner
198 .lock()
199 .expect("lock")
200 .keys
201 .as_ref()
202 .map(|k| k.upstream_public_keys())
203 }
204
205 pub fn ever_pushed_to_core(&self) -> bool {
207 self.inner.lock().expect("lock").pushed_to_core
208 }
209
210 pub fn admit_from_node(
213 &self,
214 node_id: &str,
215 channel_id: u32,
216 payload: Vec<u8>,
217 ) -> Result<EnqueueOutcome, RestreamerError> {
218 if payload.len() > MAX_DATA_PAYLOAD {
219 return Err(RestreamerError::Frame(FrameError::PayloadTooLarge(payload.len())));
220 }
221 let mut guard = self.inner.lock().expect("lock");
222 let opaque = if let Some(keys) = guard.keys.clone() {
223 c2_strip_outer(&keys, &payload).map_err(|e| {
224 RestreamerError::Frame(FrameError::Io(format!("outer layer: {e}")))
225 })?
226 } else {
227 payload
228 };
229 let outcome = guard
231 .buffer
232 .enqueue(node_id, channel_id, opaque.clone(), Instant::now());
233 if let EnqueueOutcome::Accepted { .. } = outcome {
234 let seq = guard.buffer.next_seq(node_id, channel_id) - 1;
235 drop(guard);
236 let _ = (seq, opaque);
239 }
240 Ok(outcome)
241 }
242
243 pub fn open_channel(&self, node_id: &str, channel_id: u32, kind: ChannelKind) {
244 self.inner
245 .lock()
246 .expect("lock")
247 .channels
248 .insert((node_id.to_owned(), channel_id), kind);
249 }
250
251 pub fn ack(&self, node_id: &str, channel_id: u32, seq: u64) {
252 self.inner
253 .lock()
254 .expect("lock")
255 .buffer
256 .ack(node_id, channel_id, seq);
257 }
258
259 pub fn window_credit(&self, node_id: &str, channel_id: u32) -> u32 {
260 let key = StreamKey {
261 node_id: node_id.to_owned(),
262 channel_id,
263 };
264 self.inner.lock().expect("lock").buffer.credit(&key)
265 }
266
267 pub fn resume(
269 &self,
270 node_id: &str,
271 channel_id: u32,
272 after: u64,
273 ) -> Result<Vec<DrainEnvelope>, RestreamerError> {
274 let (gap, oldest, buffered) = {
275 let guard = self.inner.lock().expect("lock");
276 let gap = guard.buffer.gap_before_buffer(node_id, channel_id, after);
277 let oldest = guard.buffer.oldest_seq(node_id, channel_id);
278 let buffered = guard.buffer.events_after(node_id, channel_id, after);
279 (gap, oldest, buffered)
280 };
281 let mut out = Vec::new();
282 if gap {
283 let until = oldest;
284 let recovered = recover_gap(
285 self.journal.as_ref(),
286 node_id,
287 channel_id,
288 after,
289 until,
290 1024,
291 )
292 .map_err(|e| RestreamerError::Frame(FrameError::Io(e.to_string())))?;
293 for ev in recovered {
294 out.push(DrainEnvelope {
295 node_id: ev.node_id.clone(),
296 frame: Frame::data(ev.channel_id, ev.seq, ev.payload)
297 .map_err(RestreamerError::Frame)?,
298 });
299 }
300 }
301 for ev in buffered {
302 if out.iter().any(|e| e.frame.seq == ev.seq) {
304 continue;
305 }
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 Ok(out)
313 }
314
315 pub async fn resume_with_uplink(
317 &self,
318 node_id: &str,
319 channel_id: u32,
320 after: u64,
321 ) -> Result<Vec<DrainEnvelope>, RestreamerError> {
322 let (gap, oldest, buffered) = {
323 let guard = self.inner.lock().expect("lock");
324 let gap = guard.buffer.gap_before_buffer(node_id, channel_id, after);
325 let oldest = guard.buffer.oldest_seq(node_id, channel_id);
326 let buffered = guard.buffer.events_after(node_id, channel_id, after);
327 (gap, oldest, buffered)
328 };
329 let mut out = Vec::new();
330 if gap {
331 let until = oldest;
332 let recovered = if self.peers.is_registered(node_id) {
333 let body = self
334 .peers
335 .request_journal(
336 node_id,
337 channel_id,
338 after,
339 1024,
340 std::time::Duration::from_secs(5),
341 )
342 .await?;
343 body.entries
344 .into_iter()
345 .filter(|e| until.map(|u| e.seq < u).unwrap_or(true))
346 .map(|e| super::buffer::BufferedEvent {
347 seq: e.seq,
348 node_id: node_id.to_owned(),
349 channel_id,
350 payload: e.payload,
351 enqueued_at: Instant::now(),
352 })
353 .collect()
354 } else {
355 recover_gap(
356 self.journal.as_ref(),
357 node_id,
358 channel_id,
359 after,
360 until,
361 1024,
362 )
363 .map_err(|e| RestreamerError::Frame(FrameError::Io(e.to_string())))?
364 };
365 for ev in recovered {
366 out.push(DrainEnvelope {
367 node_id: ev.node_id.clone(),
368 frame: Frame::data(ev.channel_id, ev.seq, ev.payload)
369 .map_err(RestreamerError::Frame)?,
370 });
371 }
372 }
373 for ev in buffered {
374 if out.iter().any(|e| e.frame.seq == ev.seq) {
375 continue;
376 }
377 out.push(DrainEnvelope {
378 node_id: ev.node_id.clone(),
379 frame: Frame::data(ev.channel_id, ev.seq, ev.payload)
380 .map_err(RestreamerError::Frame)?,
381 });
382 }
383 Ok(out)
384 }
385
386 pub fn buffered_after(&self, node_id: &str, channel_id: u32, after: u64) -> Vec<DrainEnvelope> {
388 let guard = self.inner.lock().expect("lock");
389 guard
390 .buffer
391 .events_after(node_id, channel_id, after)
392 .into_iter()
393 .filter_map(|ev| {
394 Frame::data(ev.channel_id, ev.seq, ev.payload)
395 .ok()
396 .map(|frame| DrainEnvelope {
397 node_id: ev.node_id,
398 frame,
399 })
400 })
401 .collect()
402 }
403}
404
405pub async fn serve(
407 config: RestreamerConfig,
408 restreamer: Restreamer,
409 mut shutdown: watch::Receiver<bool>,
410) -> Result<(), RestreamerError> {
411 if config.node_listen == config.drain_listen {
412 return Err(RestreamerError::Config(
413 "node_listen and drain_listen must differ",
414 ));
415 }
416 if config.core_token.len() < 16 {
417 return Err(RestreamerError::Config("core_token too short"));
418 }
419 let node_listener = TcpListener::bind(config.node_listen).await?;
420 let drain_listener = TcpListener::bind(config.drain_listen).await?;
421 loop {
422 tokio::select! {
423 _ = shutdown.changed() => {
424 if *shutdown.borrow() { break; }
425 }
426 accepted = node_listener.accept() => {
427 let (stream, _) = accepted?;
428 let rs = restreamer.clone();
429 tokio::spawn(async move {
430 if let Err(err) = handle_node_uplink(stream, rs).await {
431 tracing::debug!(error = %err, "node uplink closed");
432 }
433 });
434 }
435 accepted = drain_listener.accept() => {
436 let (stream, _) = accepted?;
437 let rs = restreamer.clone();
438 let token = config.core_token.clone();
439 tokio::spawn(async move {
440 if let Err(err) = handle_core_drain(stream, rs, &token).await {
441 tracing::debug!(error = %err, "core drain closed");
442 }
443 });
444 }
445 }
446 }
447 Ok(())
448}
449
450async fn handle_node_uplink(mut stream: TcpStream, rs: Restreamer) -> Result<(), RestreamerError> {
451 let mut buf = vec![0u8; 64 * 1024];
452 let mut filled = 0usize;
453 let mut node_id: Option<String> = None;
454 let (out_tx, mut out_rx) = tokio::sync::mpsc::channel::<Frame>(16);
455 loop {
456 tokio::select! {
457 biased;
458 maybe_out = out_rx.recv() => {
459 match maybe_out {
460 Some(frame) => {
461 stream
462 .write_all(&frame.encode().map_err(RestreamerError::Frame)?)
463 .await?;
464 }
465 None => break,
466 }
467 }
468 read = stream.read(&mut buf[filled..]) => {
469 let n = read?;
470 if n == 0 {
471 break;
472 }
473 filled += n;
474 while filled > 0 {
475 match Frame::decode(&buf[..filled]) {
476 Ok((frame, consumed)) => {
477 buf.copy_within(consumed..filled, 0);
478 filled -= consumed;
479 match frame.frame_type {
480 FrameType::Open => {
481 let open = frame.parse_open().map_err(RestreamerError::Frame)?;
482 if let Some(prev) = node_id.replace(open.node_id.clone()) {
483 rs.peers().unregister(&prev);
484 }
485 rs.peers().register(open.node_id.clone(), out_tx.clone());
486 rs.open_channel(&open.node_id, frame.channel_id, open.channel_kind);
487 let credit = rs.window_credit(&open.node_id, frame.channel_id);
488 let win = Frame::window(frame.channel_id, credit);
489 stream
490 .write_all(&win.encode().map_err(RestreamerError::Frame)?)
491 .await?;
492 }
493 FrameType::Data => {
494 let Some(ref nid) = node_id else {
495 let go = Frame::goaway(1);
496 stream
497 .write_all(&go.encode().map_err(RestreamerError::Frame)?)
498 .await?;
499 return Ok(());
500 };
501 match rs.admit_from_node(nid, frame.channel_id, frame.payload)? {
502 EnqueueOutcome::Accepted { window_credit } => {
503 let win = Frame::window(frame.channel_id, window_credit);
504 stream
505 .write_all(
506 &win.encode().map_err(RestreamerError::Frame)?,
507 )
508 .await?;
509 }
510 EnqueueOutcome::Backpressure => {
511 let win = Frame::window(frame.channel_id, 0);
512 stream
513 .write_all(
514 &win.encode().map_err(RestreamerError::Frame)?,
515 )
516 .await?;
517 }
518 }
519 }
520 FrameType::JournalReply => {
521 if let Some(ref nid) = node_id {
522 let body = JournalReplyBody::decode(&frame.payload)
523 .map_err(RestreamerError::Frame)?;
524 rs.peers().deliver_reply(nid, body);
525 }
526 }
527 FrameType::Ping => {
528 let pong = Frame::pong(frame.seq);
529 stream
530 .write_all(&pong.encode().map_err(RestreamerError::Frame)?)
531 .await?;
532 }
533 FrameType::Close | FrameType::GoAway => {
534 if let Some(ref nid) = node_id {
535 rs.peers().unregister(nid);
536 }
537 return Ok(());
538 }
539 _ => {}
540 }
541 }
542 Err(FrameError::Truncated) => break,
543 Err(err) => return Err(RestreamerError::Frame(err)),
544 }
545 }
546 if filled == buf.len() {
547 return Err(RestreamerError::Frame(FrameError::PayloadTooLarge(filled)));
548 }
549 }
550 }
551 }
552 if let Some(ref nid) = node_id {
553 rs.peers().unregister(nid);
554 }
555 Ok(())
556}
557
558async fn handle_core_drain(
559 mut stream: TcpStream,
560 rs: Restreamer,
561 expected_token: &str,
562) -> Result<(), RestreamerError> {
563 let mut auth = Vec::with_capacity(128);
565 loop {
566 let mut byte = [0u8; 1];
567 let n = stream.read(&mut byte).await?;
568 if n == 0 {
569 return Err(RestreamerError::Config("drain auth closed"));
570 }
571 if byte[0] == b'\n' {
572 break;
573 }
574 auth.push(byte[0]);
575 if auth.len() > 512 {
576 return Err(RestreamerError::Config("drain auth too long"));
577 }
578 }
579 if !tokens_equal(&auth, expected_token.as_bytes()) {
580 let go = Frame::goaway(403);
581 let _ = stream.write_all(&go.encode().unwrap_or_default()).await;
582 return Err(RestreamerError::Config("drain auth refused"));
583 }
584
585 let mut buf = vec![0u8; 64 * 1024];
586 let mut filled = 0usize;
587 let mut selected: Option<(String, u32)> = None;
589 let mut sent_through: u64 = 0;
591 let mut resumed = false;
593 loop {
594 if resumed {
596 if let Some((ref nid, ch)) = selected {
597 let envelopes = rs.buffered_after(nid, ch, sent_through);
598 for env in envelopes {
599 stream
600 .write_all(&env.encode().map_err(RestreamerError::Frame)?)
601 .await?;
602 sent_through = sent_through.max(env.frame.seq);
603 }
604 }
605 }
606
607 let read = tokio::time::timeout(
608 std::time::Duration::from_millis(50),
609 stream.read(&mut buf[filled..]),
610 )
611 .await;
612 match read {
613 Ok(Ok(0)) => break,
614 Ok(Ok(n)) => {
615 filled += n;
616 while filled > 0 {
617 match Frame::decode(&buf[..filled]) {
618 Ok((frame, consumed)) => {
619 buf.copy_within(consumed..filled, 0);
620 filled -= consumed;
621 match frame.frame_type {
622 FrameType::Open => {
623 let open =
624 frame.parse_open().map_err(RestreamerError::Frame)?;
625 selected = Some((open.node_id, frame.channel_id));
626 sent_through = 0;
627 resumed = false;
628 }
629 FrameType::Ack => {
630 if let Some((ref nid, ch)) = selected {
631 rs.ack(nid, ch, frame.seq);
632 }
633 }
634 FrameType::Resume => {
635 if let Some((ref nid, ch)) = selected {
636 let after = frame.seq;
637 let envelopes = rs.resume_with_uplink(nid, ch, after).await?;
638 for env in envelopes {
639 stream
640 .write_all(
641 &env.encode().map_err(RestreamerError::Frame)?,
642 )
643 .await?;
644 sent_through = sent_through.max(env.frame.seq);
645 }
646 sent_through = sent_through.max(after);
648 resumed = true;
649 }
650 }
651 FrameType::Data => {
652 if let Some((ref nid, ch)) = selected {
654 let down = Frame::data(ch, frame.seq, frame.payload)
655 .map_err(RestreamerError::Frame)?;
656 if let Err(err) = rs.peers().send_to(nid, down).await {
657 tracing::debug!(error = %err, "downlink dropped");
658 }
659 }
660 }
661 FrameType::Ping => {
662 let pong = Frame::pong(frame.seq);
663 stream
664 .write_all(
665 &pong.encode().map_err(RestreamerError::Frame)?,
666 )
667 .await?;
668 }
669 FrameType::Close | FrameType::GoAway => return Ok(()),
670 _ => {}
671 }
672 }
673 Err(FrameError::Truncated) => break,
674 Err(err) => return Err(RestreamerError::Frame(err)),
675 }
676 }
677 }
678 Ok(Err(e)) => return Err(e.into()),
679 Err(_) => {
680 }
682 }
683 }
684 Ok(())
685}
686
687fn tokens_equal(a: &[u8], b: &[u8]) -> bool {
688 if a.len() != b.len() {
689 return false;
690 }
691 let mut diff = 0u8;
692 for (x, y) in a.iter().zip(b.iter()) {
693 diff |= x ^ y;
694 }
695 diff == 0
696}
697
698pub struct DrainClient {
700 addr: SocketAddr,
701 token: String,
702}
703
704impl DrainClient {
705 pub fn new(addr: SocketAddr, token: impl Into<String>) -> Self {
706 Self {
707 addr,
708 token: token.into(),
709 }
710 }
711
712 pub async fn connect(&self) -> Result<DrainSession, RestreamerError> {
713 let mut stream = TcpStream::connect(self.addr).await?;
714 stream.write_all(self.token.as_bytes()).await?;
715 stream.write_all(b"\n").await?;
716 Ok(DrainSession { stream })
717 }
718}
719
720pub struct DrainSession {
721 stream: TcpStream,
722}
723
724impl DrainSession {
725 pub async fn open(&mut self, node_id: &str, channel_id: u32, kind: ChannelKind) -> Result<(), RestreamerError> {
726 let frame = Frame::open(
727 channel_id,
728 &OpenPayload {
729 channel_kind: kind,
730 node_id: node_id.to_owned(),
731 },
732 )
733 .map_err(RestreamerError::Frame)?;
734 self.stream
735 .write_all(&frame.encode().map_err(RestreamerError::Frame)?)
736 .await?;
737 Ok(())
738 }
739
740 pub async fn resume(&mut self, after: u64) -> Result<(), RestreamerError> {
741 let frame = Frame::resume(after);
742 self.stream
743 .write_all(&frame.encode().map_err(RestreamerError::Frame)?)
744 .await?;
745 Ok(())
746 }
747
748 pub async fn ack(&mut self, seq: u64) -> Result<(), RestreamerError> {
749 let frame = Frame::ack(seq);
750 self.stream
751 .write_all(&frame.encode().map_err(RestreamerError::Frame)?)
752 .await?;
753 Ok(())
754 }
755
756 pub async fn recv_envelope(
757 &mut self,
758 deadline: std::time::Duration,
759 ) -> Result<Option<DrainEnvelope>, RestreamerError> {
760 let mut buf = vec![0u8; 64 * 1024];
761 let n = match tokio::time::timeout(deadline, self.stream.read(&mut buf)).await {
762 Ok(Ok(0)) => return Ok(None),
763 Ok(Ok(n)) => n,
764 Ok(Err(e)) => return Err(e.into()),
765 Err(_) => return Ok(None),
766 };
767 let (env, _) = DrainEnvelope::decode(&buf[..n]).map_err(RestreamerError::Frame)?;
768 Ok(Some(env))
769 }
770}
771
772#[cfg(test)]
773mod tests {
774 use super::*;
775 use crate::restreamer::crypto::{core_open_inner, noise_session_seal, HqRecipientSecret};
776 use crate::restreamer::journal::MemoryJournal;
777 use hatchery_core::ChannelKind;
778 use std::time::Duration;
779
780 fn token() -> String {
781 "test-core-token-32bytes-minimum!".to_owned()
782 }
783
784 #[tokio::test]
785 async fn core_drains_and_acks_without_c2_push() {
786 let journal = Arc::new(MemoryJournal::new());
787 let rs = Restreamer::new(
788 super::super::buffer::BufferLimits {
789 max_events: 100,
790 max_bytes: 1_000_000,
791 max_age: Duration::from_secs(60),
792 },
793 journal,
794 token(),
795 None,
796 );
797 rs.open_channel("node-a", 1, ChannelKind::M4a);
798 assert!(matches!(
799 rs.admit_from_node("node-a", 1, b"cipher-1".to_vec()).unwrap(),
800 EnqueueOutcome::Accepted { .. }
801 ));
802 assert!(matches!(
803 rs.admit_from_node("node-a", 1, b"cipher-2".to_vec()).unwrap(),
804 EnqueueOutcome::Accepted { .. }
805 ));
806 let batch = rs.buffered_after("node-a", 1, 0);
807 assert_eq!(batch.len(), 2);
808 assert_eq!(batch[0].frame.payload, b"cipher-1");
809 rs.ack("node-a", 1, 1);
810 let batch = rs.buffered_after("node-a", 1, 0);
811 assert_eq!(batch.len(), 1);
812 assert_eq!(batch[0].frame.seq, 2);
813 assert!(!rs.ever_pushed_to_core());
814 }
815
816 #[tokio::test]
817 async fn live_drain_session_resume_and_ack() {
818 let journal = Arc::new(MemoryJournal::new());
819 for seq in 1..=5u64 {
821 journal.append("node-a", 1, seq, format!("j{seq}").into_bytes());
822 }
823 let rs = Restreamer::new(
824 super::super::buffer::BufferLimits {
825 max_events: 2,
826 max_bytes: 1024,
827 max_age: Duration::from_secs(60),
828 },
829 journal.clone(),
830 token(),
831 None,
832 );
833 rs.admit_from_node("node-a", 1, b"b1".to_vec()).unwrap();
836 rs.admit_from_node("node-a", 1, b"b2".to_vec()).unwrap();
837 rs.ack("node-a", 1, 2);
839 rs.admit_from_node("node-a", 1, b"b3".to_vec()).unwrap();
841 rs.admit_from_node("node-a", 1, b"b4".to_vec()).unwrap();
842
843 let journal2 = Arc::new(MemoryJournal::new());
847 for seq in 1..=4u64 {
848 journal2.append("node-b", 1, seq, format!("evt-{seq}").into_bytes());
849 }
850 let rs2 = Restreamer::new(
851 super::super::buffer::BufferLimits {
852 max_events: 10,
853 max_bytes: 10_000,
854 max_age: Duration::from_secs(60),
855 },
856 journal2,
857 token(),
858 None,
859 );
860 for i in 1..=4u8 {
862 rs2.admit_from_node("node-b", 1, vec![i]).unwrap();
863 }
864 rs2.ack("node-b", 1, 4);
865 assert!(rs2.buffered_after("node-b", 1, 0).is_empty());
866 let recovered = rs2.resume("node-b", 1, 0).unwrap();
867 assert_eq!(recovered.len(), 4);
868 assert_eq!(recovered[0].frame.payload, b"evt-1");
869 assert_eq!(recovered[3].frame.seq, 4);
870 assert!(!rs2.ever_pushed_to_core());
871 }
872
873 #[tokio::test]
874 async fn tcp_drain_end_to_end() {
875 let journal = Arc::new(MemoryJournal::new());
876 let rs = Restreamer::new(
877 super::super::buffer::BufferLimits::default(),
878 journal,
879 token(),
880 None,
881 );
882 let node_l = TcpListener::bind("127.0.0.1:0").await.unwrap();
883 let drain_l = TcpListener::bind("127.0.0.1:0").await.unwrap();
884 let node_addr = node_l.local_addr().unwrap();
885 let drain_addr = drain_l.local_addr().unwrap();
886 drop(node_l);
887 drop(drain_l);
888 let (shutdown_tx, shutdown_rx) = watch::channel(false);
889 let cfg = RestreamerConfig {
890 node_listen: node_addr,
891 drain_listen: drain_addr,
892 buffer: super::super::buffer::BufferLimits::default(),
893 core_token: token(),
894 };
895 let serve_rs = rs.clone();
896 let handle = tokio::spawn(async move { serve(cfg, serve_rs, shutdown_rx).await });
897
898 tokio::time::sleep(Duration::from_millis(20)).await;
900
901 let mut node = TcpStream::connect(node_addr).await.unwrap();
903 let open = Frame::open(
904 1,
905 &OpenPayload {
906 channel_kind: ChannelKind::G4aEvents,
907 node_id: "node-a".into(),
908 },
909 )
910 .unwrap();
911 node.write_all(&open.encode().unwrap()).await.unwrap();
912 let mut rb = [0u8; 64];
914 let _ = node.read(&mut rb).await.unwrap();
915 let data = Frame::data(1, 0, b"opaque-event".to_vec()).unwrap();
916 node.write_all(&data.encode().unwrap()).await.unwrap();
918 let _ = node.read(&mut rb).await.unwrap();
919
920 let client = DrainClient::new(drain_addr, token());
922 let mut session = client.connect().await.unwrap();
923 session
924 .open("node-a", 1, ChannelKind::G4aEvents)
925 .await
926 .unwrap();
927 session.resume(0).await.unwrap();
928 let env = session
929 .recv_envelope(Duration::from_secs(2))
930 .await
931 .unwrap()
932 .expect("envelope");
933 assert_eq!(env.node_id, "node-a");
934 assert_eq!(env.frame.payload, b"opaque-event");
935 session.ack(env.frame.seq).await.unwrap();
936
937 let _ = shutdown_tx.send(true);
938 let _ = handle.await;
939 assert!(!rs.ever_pushed_to_core());
940 }
941
942 #[test]
943 fn nested_crypto_on_admit_path() {
944 let hq = HqRecipientSecret::from_seed([11u8; 32]);
945 let keys = RestreamerKeys::from_seeds([13u8; 32], hq.public_bytes().0);
946 let journal = Arc::new(MemoryJournal::new());
947 let rs = Restreamer::new(
948 super::super::buffer::BufferLimits::default(),
949 journal,
950 token(),
951 Some(keys.clone()),
952 );
953 let sealed = noise_session_seal(&keys, &hq, b"visible-only-to-core").unwrap();
954 rs.admit_from_node("node-a", 1, sealed.outer).unwrap();
955 let env = &rs.buffered_after("node-a", 1, 0)[0];
956 assert!(!env
957 .frame
958 .payload
959 .windows(b"visible-only-to-core".len())
960 .any(|w| w == b"visible-only-to-core"));
961 let mut hq_tx = sealed.hq_transport;
962 let plain = core_open_inner(&mut hq_tx, &env.frame.payload).unwrap();
963 assert_eq!(plain, b"visible-only-to-core");
964 }
965
966 #[tokio::test]
967 async fn uplink_journal_req_served_from_file_journal() {
968 use crate::restreamer::FileJournal;
969 use hatchery_core::FrameType;
970
971 let dir = std::env::temp_dir().join(format!("upj-{}", std::process::id()));
972 let _ = std::fs::remove_dir_all(&dir);
973 let fj = FileJournal::open(dir.join("n.journal")).unwrap();
974 for i in 0..5u8 {
975 fj.append(1, &[i]).unwrap();
976 }
977 let journal = Arc::new(MemoryJournal::new());
978 let rs = Restreamer::new(
979 super::super::buffer::BufferLimits::default(),
980 journal,
981 token(),
982 None,
983 );
984 let node_l = TcpListener::bind("127.0.0.1:0").await.unwrap();
985 let drain_l = TcpListener::bind("127.0.0.1:0").await.unwrap();
986 let node_addr = node_l.local_addr().unwrap();
987 let drain_addr = drain_l.local_addr().unwrap();
988 drop(node_l);
989 drop(drain_l);
990 let (shutdown_tx, shutdown_rx) = watch::channel(false);
991 let cfg = RestreamerConfig {
992 node_listen: node_addr,
993 drain_listen: drain_addr,
994 buffer: super::super::buffer::BufferLimits::default(),
995 core_token: token(),
996 };
997 let serve_rs = rs.clone();
998 let handle = tokio::spawn(async move { serve(cfg, serve_rs, shutdown_rx).await });
999 tokio::time::sleep(Duration::from_millis(20)).await;
1000
1001 let fj_node = fj.clone();
1002 let node_task = tokio::spawn(async move {
1003 let mut node = TcpStream::connect(node_addr).await.unwrap();
1004 let open = Frame::open(
1005 1,
1006 &OpenPayload {
1007 channel_kind: ChannelKind::G4aEvents,
1008 node_id: "node-a".into(),
1009 },
1010 )
1011 .unwrap();
1012 node.write_all(&open.encode().unwrap()).await.unwrap();
1013 let mut rb = vec![0u8; 64 * 1024];
1014 let mut filled = 0usize;
1015 loop {
1016 let n = node.read(&mut rb[filled..]).await.unwrap();
1017 if n == 0 {
1018 break;
1019 }
1020 filled += n;
1021 while filled > 0 {
1022 match Frame::decode(&rb[..filled]) {
1023 Ok((frame, consumed)) => {
1024 rb.copy_within(consumed..filled, 0);
1025 filled -= consumed;
1026 if frame.frame_type == FrameType::JournalReq {
1027 let limit = frame.journal_req_limit().unwrap_or(64) as usize;
1028 let body = fj_node
1029 .reply_body(frame.channel_id, frame.seq, limit)
1030 .unwrap();
1031 let reply = Frame::journal_reply(frame.channel_id, &body).unwrap();
1032 node.write_all(&reply.encode().unwrap()).await.unwrap();
1033 }
1034 }
1035 Err(FrameError::Truncated) => break,
1036 Err(e) => panic!("{e}"),
1037 }
1038 }
1039 }
1040 });
1041
1042 for _ in 0..50 {
1043 if rs.peers().is_registered("node-a") {
1044 break;
1045 }
1046 tokio::time::sleep(Duration::from_millis(10)).await;
1047 }
1048 assert!(rs.peers().is_registered("node-a"));
1049 let body = rs
1050 .peers()
1051 .request_journal("node-a", 1, 2, 10, Duration::from_secs(2))
1052 .await
1053 .unwrap();
1054 assert_eq!(
1055 body.entries.iter().map(|e| e.seq).collect::<Vec<_>>(),
1056 vec![3, 4, 5]
1057 );
1058 assert_eq!(body.entries[0].payload, &[2]);
1059
1060 let _ = shutdown_tx.send(true);
1061 node_task.abort();
1062 let _ = handle.await;
1063 let _ = std::fs::remove_dir_all(&dir);
1064 }
1065}