mod http1;
#[cfg(feature = "grpc")]
mod grpc;
#[cfg(feature = "websocket")]
mod ws;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Binding {
JsonRpc,
Rest,
#[cfg(feature = "websocket")]
WebSocket,
#[cfg(feature = "grpc")]
Grpc,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Script {
Stall {
after: usize,
},
CutOff {
after: usize,
},
MisFrame {
after: usize,
},
Unauthorized,
}
#[derive(Debug, Clone, Copy)]
#[must_use]
pub struct ScriptedPeer {
binding: Binding,
script: Script,
}
impl Default for ScriptedPeer {
fn default() -> Self {
Self::new()
}
}
impl ScriptedPeer {
pub const fn new() -> Self {
Self {
binding: Binding::JsonRpc,
script: Script::Stall { after: 1 },
}
}
pub const fn on(mut self, binding: Binding) -> Self {
self.binding = binding;
self
}
pub const fn stall_after(mut self, events: usize) -> Self {
self.script = Script::Stall { after: events };
self
}
pub const fn cut_off_after(mut self, events: usize) -> Self {
self.script = Script::CutOff { after: events };
self
}
pub const fn misframe_after(mut self, events: usize) -> Self {
self.script = Script::MisFrame { after: events };
self
}
pub const fn unauthorized(mut self) -> Self {
self.script = Script::Unauthorized;
self
}
pub async fn start(self) -> std::io::Result<RunningPeer> {
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await?;
let addr = listener.local_addr()?;
let accepted = Arc::new(AtomicUsize::new(0));
let counter = Arc::clone(&accepted);
let script = self.script;
let binding = self.binding;
let task = tokio::spawn(async move {
while let Ok((stream, _)) = listener.accept().await {
counter.fetch_add(1, Ordering::SeqCst);
let _ = stream.set_nodelay(true);
tokio::spawn(serve(binding, script, stream));
}
});
let url = match binding {
#[cfg(feature = "websocket")]
Binding::WebSocket => format!("ws://{addr}"),
_ => format!("http://{addr}"),
};
Ok(RunningPeer {
url,
accepted,
task,
})
}
}
async fn serve(binding: Binding, script: Script, stream: tokio::net::TcpStream) {
match binding {
Binding::JsonRpc => http1::serve(http1::Flavour::JsonRpc, script, stream).await,
Binding::Rest => http1::serve(http1::Flavour::Rest, script, stream).await,
#[cfg(feature = "websocket")]
Binding::WebSocket => ws::serve(script, stream).await,
#[cfg(feature = "grpc")]
Binding::Grpc => grpc::serve(script, stream).await,
}
}
#[derive(Debug)]
pub struct RunningPeer {
url: String,
accepted: Arc<AtomicUsize>,
task: tokio::task::JoinHandle<()>,
}
impl RunningPeer {
#[must_use]
pub fn url(&self) -> &str {
&self.url
}
#[must_use]
pub fn connections(&self) -> usize {
self.accepted.load(Ordering::SeqCst)
}
}
impl Drop for RunningPeer {
fn drop(&mut self) {
self.task.abort();
}
}
fn working_event() -> serde_json::Value {
serde_json::json!({
"statusUpdate": {
"taskId": "t",
"contextId": "c",
"status": { "state": "TASK_STATE_WORKING" }
}
})
}
async fn hold_open() {
std::future::pending::<()>().await;
}