use std::time::Duration;
use futures::{SinkExt, StreamExt};
use nodedb_types::sync::wire::{HandshakeAckMsg, SyncFrame, SyncMessageType};
use nodedb_types::wire_version::WIRE_FORMAT_VERSION;
use tokio::net::{TcpListener, TcpStream};
use tokio_tungstenite::WebSocketStream;
use tokio_tungstenite::tungstenite::Message;
pub type OriginSocket = WebSocketStream<TcpStream>;
const WAIT_TIMEOUT: Duration = Duration::from_secs(5);
pub struct MockOrigin {
listener: TcpListener,
url: String,
}
impl MockOrigin {
pub async fn bind() -> Self {
let listener = TcpListener::bind("127.0.0.1:0")
.await
.expect("bind mock origin");
let port = listener.local_addr().expect("local addr").port();
Self {
listener,
url: format!("ws://127.0.0.1:{port}/sync"),
}
}
pub fn url(&self) -> &str {
&self.url
}
pub async fn accept_handshaked(&self) -> OriginSocket {
let (stream, _) = tokio::time::timeout(WAIT_TIMEOUT, self.listener.accept())
.await
.expect("timed out waiting for the client to connect")
.expect("accept connection");
let mut ws = tokio_tungstenite::accept_async(stream)
.await
.expect("websocket upgrade");
let frame = next_frame(&mut ws)
.await
.expect("client closed before sending its handshake");
assert_eq!(
frame.msg_type,
SyncMessageType::Handshake,
"first client frame must be the handshake"
);
let ack = HandshakeAckMsg {
success: true,
session_id: "mock-session".to_string(),
server_clock: std::collections::HashMap::new(),
error: None,
fork_detected: false,
server_wire_version: WIRE_FORMAT_VERSION,
producer_id: 1,
accepted_epoch: 1,
delta_signing_key: [0u8; 32],
};
send_frame(&mut ws, SyncMessageType::HandshakeAck, &ack).await;
ws
}
}
pub async fn send_frame<T: zerompk::ToMessagePack>(
ws: &mut OriginSocket,
msg_type: SyncMessageType,
msg: &T,
) {
let frame = SyncFrame::try_encode(msg_type, msg).expect("encode frame");
ws.send(Message::Binary(frame.to_bytes().into()))
.await
.expect("send frame to client");
}
pub async fn next_frame(ws: &mut OriginSocket) -> Option<SyncFrame> {
loop {
let msg = tokio::time::timeout(WAIT_TIMEOUT, ws.next())
.await
.expect("timed out waiting for a frame from the client")?
.expect("read from client");
match msg {
Message::Binary(bytes) => {
return Some(SyncFrame::from_bytes(&bytes).expect("client sent a malformed frame"));
}
Message::Close(_) => return None,
_ => continue,
}
}
}
pub async fn collect_frames_for(ws: &mut OriginSocket, window: Duration) -> Vec<SyncFrame> {
let mut frames = Vec::new();
let deadline = tokio::time::Instant::now() + window;
while let Some(remaining) = deadline.checked_duration_since(tokio::time::Instant::now()) {
match tokio::time::timeout(remaining, ws.next()).await {
Err(_) => break,
Ok(None) => break,
Ok(Some(msg)) => match msg.expect("read from client") {
Message::Binary(bytes) => {
let frame =
SyncFrame::from_bytes(&bytes).expect("client sent a malformed frame");
frames.push(frame);
}
Message::Close(_) => break,
_ => continue,
},
}
}
frames
}
pub async fn await_until<F, Fut>(mut cond: F, what: &str)
where
F: FnMut() -> Fut,
Fut: std::future::Future<Output = bool>,
{
let poll = async {
while !cond().await {
tokio::time::sleep(Duration::from_millis(2)).await;
}
};
if tokio::time::timeout(WAIT_TIMEOUT, poll).await.is_err() {
panic!("timed out waiting for {what}");
}
}