use alloc::boxed::Box;
use alloc::string::{String, ToString};
use alloc::sync::Arc;
use alloc::vec::Vec;
use async_channel::Sender;
use futures_channel::oneshot;
use futures_util::stream::{FuturesUnordered, StreamExt};
use futures_util::{select_biased, FutureExt};
use hashbrown::HashMap;
use super::{
BoxFut, BoxStream, Connection, Dispatch, EnvelopeCodec, Inbound, Listener, Outbound, Payload,
RpcError, SessionLimits, SubUpdate,
};
#[derive(Debug, Clone, Default)]
pub struct SessionConfig {
pub limits: SessionLimits,
pub reads_hello: bool,
pub acks_subscribe: bool,
}
const EVENT_BUFFER: usize = 256;
struct SubEvent {
sub: String,
seq: u64,
topic: Option<Arc<str>>,
data: Payload,
}
enum Step {
Frame(Vec<u8>),
Closed,
Event(SubEvent),
SubDrained(String),
}
pub async fn run_session<C, D>(
mut conn: Box<dyn Connection>,
codec: &C,
dispatch: &D,
config: &SessionConfig,
) where
C: EnvelopeCodec + ?Sized,
D: Dispatch + ?Sized,
{
let first = if config.reads_hello {
match conn.recv().await {
Ok(Some(frame)) => Some(frame),
_ => return,
}
} else {
None
};
let ctx = match dispatch.authenticate(conn.peer(), first.as_deref()).await {
Ok(ctx) => ctx,
Err(_e) => {
log_warn!("session authenticate rejected: {:?}", _e);
return;
}
};
let mut session = dispatch.open(&ctx);
let (event_tx, event_rx) = async_channel::bounded::<SubEvent>(EVENT_BUFFER);
let mut subs: FuturesUnordered<BoxFut<'static, String>> = FuturesUnordered::new();
let mut cancels: HashMap<String, oneshot::Sender<()>> = HashMap::new();
let mut out = Vec::new();
loop {
let step = {
let mut recv = conn.recv().fuse();
let mut event = core::pin::pin!(event_rx.recv().fuse());
select_biased! {
r = recv => match r {
Ok(Some(frame)) => Step::Frame(frame),
Ok(None) | Err(_) => Step::Closed, },
ev = event => match ev {
Ok(ev) => Step::Event(ev),
Err(_) => Step::Closed,
},
sub_id = subs.select_next_some() => Step::SubDrained(sub_id),
}
};
match step {
Step::Closed => break,
Step::SubDrained(sub_id) => {
cancels.remove(&sub_id);
}
Step::Event(ev) => {
out.clear();
let encoded = codec
.encode(
Outbound::Event {
sub: &ev.sub,
seq: ev.seq,
topic: ev.topic.as_deref(),
data: ev.data,
},
&mut out,
)
.is_ok();
if encoded && conn.send(&out).await.is_err() {
break;
}
}
Step::Frame(frame) => {
let msg = match codec.decode(&frame) {
Ok(msg) => msg,
Err(_e) => continue, };
match msg {
Inbound::Request { id, method, params } => {
let result = session.call(&method, params).await;
out.clear();
if codec
.encode(Outbound::Reply { id, result }, &mut out)
.is_err()
{
continue;
}
if conn.send(&out).await.is_err() {
break;
}
}
Inbound::Subscribe { id, topic } => {
let sub_id = id.to_string();
if cancels.len() >= config.limits.max_subs_per_connection {
send_reply_err(&mut conn, codec, &mut out, id, RpcError::Denied).await;
continue;
}
match session.subscribe(&topic).await {
Ok(stream) => {
if config.acks_subscribe {
out.clear();
if codec
.encode(Outbound::Subscribed { sub: &sub_id }, &mut out)
.is_ok()
&& conn.send(&out).await.is_err()
{
break;
}
}
let snaps = session.snapshots(&topic);
let total = snaps.len();
let mut seq: u64 = 0;
let mut send_failed = false;
for (i, (snap_topic, data)) in snaps.into_iter().enumerate() {
seq += 1;
out.clear();
let encoded = codec.encode(
Outbound::Snapshot {
sub: &sub_id,
seq,
last: i + 1 == total,
topic: &snap_topic,
data,
},
&mut out,
);
if let Err(_e) = encoded {
log_warn!(
"snapshot encode failed, skipping: sub={} topic={} err={:?}",
sub_id,
snap_topic,
_e
);
continue;
}
if conn.send(&out).await.is_err() {
send_failed = true;
break;
}
}
if send_failed {
break;
}
let (cancel_tx, cancel_rx) = oneshot::channel();
cancels.insert(sub_id.clone(), cancel_tx);
subs.push(Box::pin(pump_subscription(
sub_id,
stream,
event_tx.clone(),
cancel_rx,
seq,
)));
}
Err(e) => {
send_reply_err(&mut conn, codec, &mut out, id, e).await;
}
}
}
Inbound::Unsubscribe { sub } => {
cancels.remove(&sub);
}
Inbound::Write { topic, payload } => {
let _ = session.write(&topic, payload).await;
}
Inbound::Ping => {
out.clear();
if codec.encode(Outbound::Pong, &mut out).is_ok()
&& conn.send(&out).await.is_err()
{
break;
}
}
}
}
}
}
drop(subs);
}
async fn send_reply_err<C: EnvelopeCodec + ?Sized>(
conn: &mut Box<dyn Connection>,
codec: &C,
out: &mut Vec<u8>,
id: u64,
err: RpcError,
) {
out.clear();
if codec
.encode(
Outbound::Reply {
id,
result: Err(err),
},
out,
)
.is_ok()
{
let _ = conn.send(out).await;
}
}
async fn pump_subscription(
sub_id: String,
mut stream: BoxStream<'static, SubUpdate>,
tx: Sender<SubEvent>,
cancel: oneshot::Receiver<()>,
start_seq: u64,
) -> String {
let mut cancel = cancel.fuse();
let mut seq: u64 = start_seq;
loop {
let update = select_biased! {
_ = cancel => break,
next = stream.next().fuse() => match next {
Some(update) => update,
None => break, },
};
seq += update.skipped + 1;
match tx.try_send(SubEvent {
sub: sub_id.clone(),
seq,
topic: update.topic,
data: update.data,
}) {
Ok(()) => {}
Err(e) if e.is_full() => {} Err(_) => break, }
}
sub_id
}
pub async fn serve<L, C, D>(mut listener: L, codec: Arc<C>, dispatch: Arc<D>, config: SessionConfig)
where
L: Listener,
C: EnvelopeCodec + 'static,
D: Dispatch + 'static + ?Sized,
{
let mut conns: FuturesUnordered<BoxFut<'static, ()>> = FuturesUnordered::new();
loop {
let accept = {
let mut accept = listener.accept().fuse();
select_biased! {
a = accept => a,
() = conns.select_next_some() => continue,
}
};
match accept {
Ok(conn) => {
if conns.len() >= config.limits.max_connections {
log_warn!(
"max_connections={} reached, refusing client",
config.limits.max_connections
);
drop(conn);
continue;
}
let codec = codec.clone();
let dispatch = dispatch.clone();
let cfg = config.clone();
conns.push(Box::pin(async move {
run_session(conn, codec.as_ref(), dispatch.as_ref(), &cfg).await;
}));
}
Err(_e) => {
log_error!("accept failed: {:?}", _e);
}
}
}
}