use std::{collections::HashMap, fmt, future::Future, net::IpAddr, pin::Pin};
use thiserror::Error;
use tokio_util::sync::CancellationToken;
use super::{CloseCause, FramedRecv, FramedSend};
use crate::{
zakura::{ServicePeerDirection, ZakuraConnId, ZakuraPeerId},
BoxError,
};
use super::Frame;
pub type BoxRunFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum StreamMode {
Ordered,
RequestResponse,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Stream {
pub kind: u16,
pub version: u16,
pub frame_cap: u32,
pub capability: u64,
pub mode: StreamMode,
}
#[derive(Debug)]
pub(crate) struct ServiceStream {
pub(crate) session_id: u64,
pub(crate) recv: FramedRecv,
pub(crate) send: FramedSend,
pub(crate) cancel_token: CancellationToken,
}
impl ServiceStream {
pub(crate) fn new(
session_id: u64,
recv: FramedRecv,
send: FramedSend,
cancel_token: CancellationToken,
) -> Self {
Self {
session_id,
recv,
send,
cancel_token,
}
}
}
#[derive(Debug)]
pub struct Peer {
pub id: ZakuraPeerId,
pub conn_id: ZakuraConnId,
pub remote_ip: Option<IpAddr>,
pub negotiated: u64,
pub direction: ServicePeerDirection,
streams: HashMap<u16, ServiceStream>,
cancel_token: CancellationToken,
service_cancel_token: CancellationToken,
close_cause: CloseCause,
}
impl Peer {
pub fn new(
id: ZakuraPeerId,
remote_ip: Option<IpAddr>,
negotiated: u64,
streams: HashMap<u16, (FramedRecv, FramedSend)>,
cancel_token: CancellationToken,
) -> Self {
Self::new_with_conn_id_and_direction(
0,
id,
remote_ip,
negotiated,
ServicePeerDirection::Inbound,
streams,
cancel_token,
)
}
pub fn new_with_direction(
id: ZakuraPeerId,
remote_ip: Option<IpAddr>,
negotiated: u64,
direction: ServicePeerDirection,
streams: HashMap<u16, (FramedRecv, FramedSend)>,
cancel_token: CancellationToken,
) -> Self {
Self::new_with_conn_id_and_direction(
0,
id,
remote_ip,
negotiated,
direction,
streams,
cancel_token,
)
}
pub(crate) fn new_with_conn_id_and_direction(
conn_id: ZakuraConnId,
id: ZakuraPeerId,
remote_ip: Option<IpAddr>,
negotiated: u64,
direction: ServicePeerDirection,
streams: HashMap<u16, (FramedRecv, FramedSend)>,
cancel_token: CancellationToken,
) -> Self {
Self::new_with_conn_id_and_direction_and_close_cause(
conn_id,
id,
remote_ip,
negotiated,
direction,
streams,
cancel_token,
CloseCause::new(),
)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn new_with_conn_id_and_direction_and_close_cause(
conn_id: ZakuraConnId,
id: ZakuraPeerId,
remote_ip: Option<IpAddr>,
negotiated: u64,
direction: ServicePeerDirection,
streams: HashMap<u16, (FramedRecv, FramedSend)>,
cancel_token: CancellationToken,
close_cause: CloseCause,
) -> Self {
let streams = streams
.into_iter()
.map(|(kind, (recv, send))| {
(
kind,
ServiceStream::new(0, recv, send, cancel_token.child_token()),
)
})
.collect::<HashMap<_, _>>();
Self::new_with_service_streams(
conn_id,
id,
remote_ip,
negotiated,
direction,
streams,
cancel_token,
close_cause,
)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn new_with_service_streams(
conn_id: ZakuraConnId,
id: ZakuraPeerId,
remote_ip: Option<IpAddr>,
negotiated: u64,
direction: ServicePeerDirection,
streams: HashMap<u16, ServiceStream>,
cancel_token: CancellationToken,
close_cause: CloseCause,
) -> Self {
let service_cancel_token = streams
.values()
.next()
.map(|stream| stream.cancel_token.clone())
.unwrap_or_else(|| cancel_token.child_token());
Self::new_with_service_cancel_token(
conn_id,
id,
remote_ip,
negotiated,
direction,
streams,
cancel_token,
service_cancel_token,
close_cause,
)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn new_with_service_cancel_token(
conn_id: ZakuraConnId,
id: ZakuraPeerId,
remote_ip: Option<IpAddr>,
negotiated: u64,
direction: ServicePeerDirection,
streams: HashMap<u16, ServiceStream>,
cancel_token: CancellationToken,
service_cancel_token: CancellationToken,
close_cause: CloseCause,
) -> Self {
Self {
id,
conn_id,
remote_ip,
negotiated,
direction,
streams,
cancel_token,
service_cancel_token,
close_cause,
}
}
pub fn take_stream(&mut self, kind: u16) -> Option<(FramedRecv, FramedSend)> {
self.streams
.remove(&kind)
.map(|stream| (stream.recv, stream.send))
}
pub fn take_stream_with_session_id(
&mut self,
kind: u16,
) -> Option<(u64, FramedRecv, FramedSend)> {
self.streams
.remove(&kind)
.map(|stream| (stream.session_id, stream.recv, stream.send))
}
pub fn cancel_token(&self) -> CancellationToken {
self.cancel_token.clone()
}
pub fn service_cancel_token(&self) -> CancellationToken {
self.service_cancel_token.clone()
}
pub(crate) fn close_cause(&self) -> CloseCause {
self.close_cause.clone()
}
pub(crate) fn into_parts(
self,
) -> (
ZakuraPeerId,
ZakuraConnId,
Option<IpAddr>,
u64,
ServicePeerDirection,
HashMap<u16, ServiceStream>,
CancellationToken,
CloseCause,
) {
(
self.id,
self.conn_id,
self.remote_ip,
self.negotiated,
self.direction,
self.streams,
self.cancel_token,
self.close_cause,
)
}
}
pub trait Service: fmt::Debug + Send + Sync + 'static {
fn name(&self) -> &'static str;
fn streams(&self) -> &[Stream];
fn wants_peer(
&self,
_peer: &ZakuraPeerId,
_negotiated: u64,
_direction: ServicePeerDirection,
) -> bool {
true
}
fn add_peer(&self, peer: Peer);
fn remove_peer(&self, peer: &ZakuraPeerId, conn_id: ZakuraConnId);
fn deliver_frame(
&self,
_peer_id: ZakuraPeerId,
_stream_kind: u16,
_frame: Frame,
) -> Result<(), SinkReject> {
Err(SinkReject::protocol(
"service does not accept inbound frames",
))
}
fn as_request_response(&self) -> Option<&dyn RequestResponseService> {
None
}
}
pub trait RequestResponseService: Service {
fn request_frame<'a>(
&'a self,
peer_id: ZakuraPeerId,
stream_kind: u16,
request_id: u64,
max_frame_bytes: u32,
max_message_bytes: u32,
frame: Frame,
) -> BoxRunFuture<'a, Result<Vec<Frame>, SinkReject>>;
}
pub trait Sink: Send + 'static {
fn run(self: Box<Self>, recv: FramedRecv) -> BoxRunFuture<'static, Result<(), SinkReject>>;
}
pub trait Source: Send + 'static {
fn run(self: Box<Self>, send: FramedSend) -> BoxRunFuture<'static, ()>;
}
#[derive(Debug, Error)]
pub enum SinkReject {
#[error("inbound sink rejected protocol-invalid frame: {0}")]
Protocol(#[source] BoxError),
#[error("inbound sink could not accept frame locally: {0}")]
Local(#[source] BoxError),
}
impl SinkReject {
pub fn protocol(error: impl Into<BoxError>) -> Self {
Self::Protocol(error.into())
}
pub fn local(error: impl Into<BoxError>) -> Self {
Self::Local(error.into())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sink_reject_constructors_preserve_protocol_and_local_contract() {
let protocol = SinkReject::protocol("bad frame");
let local = SinkReject::local("closed queue");
assert!(matches!(protocol, SinkReject::Protocol(_)));
assert!(matches!(local, SinkReject::Local(_)));
assert!(protocol.to_string().contains("protocol-invalid"));
assert!(local.to_string().contains("locally"));
}
}