use crate::{buffer::Buff, fec::FrameEncoder};
use crate::{crypt::AeadError, mux::Multiplex, runtime, StatsGatherer};
use crate::{crypt::NgAead, protocol::DataFrameV2};
use machine::RecvMachine;
use once_cell::sync::Lazy;
use parking_lot::Mutex;
use rloss::RecvLossCalc;
use smol::channel::{Receiver, Sender, TrySendError};
use smol::prelude::*;
use stats::StatsCalculator;
use std::{
sync::{
atomic::{AtomicUsize, Ordering},
Arc,
},
time::Duration,
};
use thiserror::Error;
mod machine;
mod rloss;
mod stats;
#[derive(Debug, Clone)]
pub(crate) struct SessionConfig {
pub version: u64,
pub session_key: Vec<u8>,
pub role: Role,
pub gather: Arc<StatsGatherer>,
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum Role {
Server,
Client,
}
#[derive(Error, Debug)]
pub enum SessionError {
#[error("session dropped")]
SessionDropped,
}
pub struct Session {
send_tosend: Sender<Buff>,
recv_decoded: Receiver<Buff>,
statistics: Arc<StatsGatherer>,
dropper: Vec<Box<dyn FnOnce() + Send + Sync + 'static>>,
_task: smol::Task<()>,
}
static TOTAL_SESSIONS: AtomicUsize = AtomicUsize::new(0);
impl Drop for Session {
fn drop(&mut self) {
TOTAL_SESSIONS.fetch_sub(1, Ordering::Relaxed);
for v in self.dropper.drain(0..) {
v()
}
}
}
impl Session {
pub(crate) fn new(cfg: SessionConfig) -> (Self, SessionBack) {
let count = TOTAL_SESSIONS.fetch_add(1, Ordering::Relaxed);
eprintln!("***** {count} Sessions *****");
let (send_tosend, recv_tosend) = smol::channel::bounded(256);
let gather = cfg.gather.clone();
let calculator = Arc::new(StatsCalculator::new(gather.clone()));
let rloss = Arc::new(Mutex::new(RecvLossCalc::new(1.0)));
let machine = Mutex::new(RecvMachine::new(
calculator.clone(),
rloss.clone(),
&cfg.session_key,
cfg.role,
));
let (send_decoded, recv_decoded) = smol::channel::bounded(256);
let (send_outgoing, recv_outgoing) = smol::channel::bounded(256);
let session_back = SessionBack {
machine,
send_decoded,
recv_outgoing,
};
let count = TOTAL_BACKS.fetch_add(1, Ordering::Relaxed);
eprintln!("***** {count} SessionBacks *****");
let send_crypt_key = match cfg.role {
Role::Server => blake3::keyed_hash(crate::crypt::DN_KEY, &cfg.session_key),
Role::Client => blake3::keyed_hash(crate::crypt::UP_KEY, &cfg.session_key),
};
let send_crypt = NgAead::new(send_crypt_key.as_bytes());
let ctx = SessionSendCtx {
cfg,
statg: calculator,
gather: gather.clone(),
rloss,
recv_tosend,
send_crypt,
send_outgoing,
};
let task = runtime::spawn(session_send_loop(ctx));
let session = Session {
send_tosend,
recv_decoded,
statistics: gather,
dropper: Vec::new(),
_task: task,
};
(session, session_back)
}
pub fn on_drop<T: FnOnce() + Send + Sync + 'static>(&mut self, thing: T) {
self.dropper.push(Box::new(thing))
}
pub async fn send_bytes(&self, to_send: impl Into<Buff>) -> Result<(), SessionError> {
let to_send: Buff = to_send.into();
self.statistics
.increment("total_sent_bytes", to_send.len() as f32);
if let Err(TrySendError::Closed(_)) = self.send_tosend.try_send(to_send) {
Err(SessionError::SessionDropped)
} else {
Ok(())
}
}
pub async fn recv_bytes(&self) -> Result<Buff, SessionError> {
let recv = self
.recv_decoded
.recv()
.await
.map_err(|_| SessionError::SessionDropped)?;
self.statistics
.increment("total_recv_bytes", recv.len() as f32);
Ok(recv)
}
pub fn multiplex(self) -> Multiplex {
Multiplex::new(self)
}
}
static TOTAL_BACKS: AtomicUsize = AtomicUsize::new(0);
pub(crate) struct SessionBack {
machine: Mutex<RecvMachine>,
send_decoded: Sender<Buff>,
recv_outgoing: Receiver<Buff>,
}
impl Drop for SessionBack {
fn drop(&mut self) {
TOTAL_BACKS.fetch_sub(1, Ordering::Relaxed);
}
}
impl SessionBack {
pub fn inject_incoming(&self, pkt: &[u8]) -> Result<(), AeadError> {
let decoded = self.machine.lock().process(pkt)?;
if let Some(decoded) = decoded {
for decoded in decoded {
let _ = self.send_decoded.try_send(decoded.0);
}
}
Ok(())
}
pub async fn next_outgoing(&self) -> Result<Buff, SessionError> {
self.recv_outgoing
.recv()
.await
.ok()
.ok_or(SessionError::SessionDropped)
}
}
struct SessionSendCtx {
cfg: SessionConfig,
statg: Arc<StatsCalculator>,
gather: Arc<StatsGatherer>,
rloss: Arc<Mutex<RecvLossCalc>>,
recv_tosend: Receiver<Buff>,
send_crypt: NgAead,
send_outgoing: Sender<Buff>,
}
async fn session_send_loop(ctx: SessionSendCtx) {
if ctx.cfg.version == 1 {
} else {
let version = ctx.cfg.version;
session_send_loop_nextgen(ctx, version).await;
}
}
const BURST_SIZE: usize = 16;
static SOSISTAB_NO_FEC: Lazy<bool> = Lazy::new(|| std::env::var("SOSISTAB_NO_FEC").is_ok());
#[tracing::instrument(skip(ctx))]
async fn session_send_loop_nextgen(ctx: SessionSendCtx, version: u64) -> Option<()> {
enum Event {
NewPayload(Buff),
FecTimeout,
}
const FEC_TIMEOUT_MS: u64 = 20;
let mut fec_timer = smol::Timer::after(Duration::from_millis(FEC_TIMEOUT_MS));
let mut unfecked: Vec<(u64, Buff)> = Vec::new();
let mut fec_encoder = FrameEncoder::new(10); let mut frame_no = 0;
loop {
let event: Option<Event> = async {
if unfecked.is_empty() {
smol::future::pending::<()>().await;
}
if unfecked.len() < BURST_SIZE {
(&mut fec_timer).await;
}
Some(Event::FecTimeout)
}
.or(async { Some(Event::NewPayload(ctx.recv_tosend.recv().await.ok()?)) })
.await;
let loss = ctx.rloss.lock().calculate_loss();
let loss_u8 = (loss * 254.0) as u8;
ctx.gather.update("recv_loss", loss as f32);
match event? {
Event::NewPayload(send_payload) => {
let send_framed = DataFrameV2::Data {
frame_no,
high_recv_frame_no: ctx.statg.high_recv_frame_no(),
total_recv_frames: ctx.statg.total_recv_frames(),
body: send_payload.clone(),
};
let send_padded = send_framed.pad(loss_u8);
ctx.statg.ping_send(frame_no);
let send_encrypted = ctx.send_crypt.encrypt(&send_padded);
ctx.send_outgoing.send(send_encrypted).await.ok()?;
unfecked.push((frame_no, send_payload));
frame_no += 1;
fec_timer.set_after(Duration::from_millis(FEC_TIMEOUT_MS));
}
Event::FecTimeout => {
fec_timer.set_after(Duration::from_millis(FEC_TIMEOUT_MS));
if unfecked.is_empty() {
continue;
}
let measured_loss = ctx.statg.loss_u8();
if measured_loss == 0 || *SOSISTAB_NO_FEC {
unfecked.clear();
continue;
}
assert!(unfecked.len() <= BURST_SIZE);
let first_frame_no = unfecked[0].0;
let data_count = unfecked.len();
let expanded = fec_encoder.encode(
ctx.statg.loss_u8(),
&unfecked.iter().map(|v| v.1.clone()).collect::<Vec<_>>(),
);
let pad_size = unfecked.iter().map(|v| v.1.len()).max().unwrap_or_default() + 2;
let parity = &expanded[unfecked.len()..];
unfecked.clear();
tracing::trace!("FecTimeout; sending {} parities", parity.len());
let parity_count = parity.len();
for (index, parity) in parity.iter().enumerate() {
let send_framed = DataFrameV2::Parity {
data_frame_first: first_frame_no,
data_count: data_count as u8,
parity_count: parity_count as u8,
parity_index: index as u8,
body: parity.clone(),
pad_size,
};
let send_padded = send_framed.pad(loss_u8);
let send_encrypted = ctx.send_crypt.encrypt(&send_padded);
if ctx.send_outgoing.try_send(send_encrypted).is_err() {
tracing::warn!("dropping send due to backpressure");
}
}
}
}
}
}