#![deny(missing_docs)]
use std::collections::HashMap;
use std::io::{Error, Result};
use std::sync::{Arc, Mutex, Weak};
pub use sbd_client::PubKey;
mod sodoken_crypto;
pub use sodoken_crypto::*;
pub struct Config {
pub listener: bool,
pub allow_plain_text: bool,
pub cooldown: std::time::Duration,
pub max_connections: usize,
pub max_idle: std::time::Duration,
}
impl Default for Config {
fn default() -> Self {
Self {
listener: false,
allow_plain_text: false,
cooldown: std::time::Duration::from_secs(10),
max_connections: 4096,
max_idle: std::time::Duration::from_secs(10),
}
}
}
enum Conn {
Cooldown(tokio::time::Instant),
Active {
last_active: tokio::time::Instant,
enc: sodoken_crypto::Encryptor,
dec: sodoken_crypto::Decryptor,
},
}
struct Inner {
config: Arc<Config>,
crypto: sodoken_crypto::SodokenCrypto,
client: sbd_client::SbdClient,
map: HashMap<PubKey, Conn>,
}
fn do_close_peer(pk: &PubKey, conn: &mut Conn, cooldown: std::time::Duration) {
tracing::debug!(
target: "NETAUDIT",
pub_key = ?pk,
cooldown_s = cooldown.as_secs_f64(),
m = "sbd-e2e-crypto-client",
a = "close_peer",
);
*conn = Conn::Cooldown(tokio::time::Instant::now() + cooldown);
}
impl Inner {
pub async fn close(&mut self) {
self.client.close().await;
}
pub fn close_peer(&mut self, pk: &PubKey) {
if let Some(conn) = self.map.get_mut(pk) {
do_close_peer(pk, conn, self.config.cooldown);
}
}
pub async fn assert(&mut self, pk: &PubKey) -> Result<()> {
let Self {
config,
crypto,
client,
map,
} = self;
let (conn, hdr) = Self::priv_assert_con(pk, config, crypto, map, true)?;
match conn {
Conn::Cooldown(_) => {
Err(Error::other("connection still cooling down"))
}
Conn::Active { .. } => {
if let Err(err) = async {
if let Some(hdr) = hdr {
client.send(pk, &hdr).await
} else {
Ok(())
}
}
.await
{
do_close_peer(pk, conn, config.cooldown);
Err(err)
} else {
Ok(())
}
}
}
}
pub async fn recv(
&mut self,
msg: sbd_client::Msg,
) -> Result<Option<(PubKey, Vec<u8>)>> {
let Self {
config,
crypto,
client,
map,
} = self;
let pk = msg.pub_key();
match Self::priv_assert_con(&pk, config, crypto, map, config.listener) {
Err(_) => Ok(None),
Ok((conn, hdr)) => {
if let Some(hdr) = hdr {
client.send(&pk, &hdr).await?;
}
match conn {
Conn::Cooldown(_) => Ok(None),
Conn::Active {
last_active, dec, ..
} => {
*last_active = tokio::time::Instant::now();
match dec.decrypt(msg.message()) {
Err(_) => {
do_close_peer(&pk, conn, config.cooldown);
Ok(None)
}
Ok(None) => Ok(None),
Ok(Some(msg)) => Ok(Some((pk, msg))),
}
}
}
}
}
}
pub async fn send(&mut self, pk: &PubKey, msg: &[u8]) -> Result<()> {
let Self {
config,
crypto,
client,
map,
} = self;
let (conn, hdr) = Self::priv_assert_con(pk, config, crypto, map, true)?;
match conn {
Conn::Cooldown(_) => {
Err(Error::other("connection still cooling down"))
}
Conn::Active { enc, .. } => {
if let Err(err) = async {
if let Some(hdr) = hdr {
client.send(pk, &hdr).await?;
}
let msg = enc.encrypt(msg)?;
client.send(pk, &msg).await
}
.await
{
do_close_peer(pk, conn, config.cooldown);
Err(err)
} else {
Ok(())
}
}
}
}
fn prune(config: &Config, map: &mut HashMap<PubKey, Conn>) {
let now = tokio::time::Instant::now();
map.retain(|pk, c| {
if let Conn::Active { last_active, .. } = c {
if now - *last_active > config.max_idle {
do_close_peer(pk, c, config.cooldown);
}
}
if let Conn::Cooldown(at) = c {
now < *at
} else {
true
}
})
}
fn priv_assert_con<'a>(
pk: &PubKey,
config: &Config,
crypto: &sodoken_crypto::SodokenCrypto,
map: &'a mut HashMap<PubKey, Conn>,
do_create: bool,
) -> Result<(&'a mut Conn, Option<[u8; 24]>)> {
use std::collections::hash_map::Entry;
Self::prune(config, map);
let len = map.len();
match map.entry(pk.clone()) {
Entry::Occupied(e) => Ok((e.into_mut(), None)),
Entry::Vacant(e) => {
if !do_create {
return Err(Error::other("ignore"));
}
if len >= config.max_connections {
tracing::debug!(
target: "NETAUDIT",
pub_key = ?pk,
m = "sbd-e2e-crypto-client",
"cannot open: too many connections",
);
return Err(Error::other("too many connections"));
}
tracing::debug!(
target: "NETAUDIT",
pub_key = ?pk,
m = "sbd-e2e-crypto-client",
a = "open_peer",
);
let (enc, hdr, dec) = crypto.new_enc(pk)?;
Ok((
e.insert(Conn::Active {
last_active: tokio::time::Instant::now(),
enc,
dec,
}),
Some(hdr),
))
}
}
}
}
async fn close_inner(inner: &mut Option<Inner>) {
if let Some(mut inner) = inner.take() {
inner.close().await;
}
}
pub struct MsgRecv {
inner: Weak<tokio::sync::Mutex<Option<Inner>>>,
recv: sbd_client::MsgRecv,
}
impl MsgRecv {
pub async fn recv(&mut self) -> Option<(PubKey, Vec<u8>)> {
loop {
let raw_msg = match self.recv.recv().await {
None => return None,
Some(raw_msg) => raw_msg,
};
if let Some(inner) = self.inner.upgrade() {
let mut lock = inner.lock().await;
if let Some(inner) = &mut *lock {
match inner.recv(raw_msg).await {
Err(_) => (),
Ok(None) => continue,
Ok(Some(o)) => return Some(o),
}
} else {
return None;
}
close_inner(&mut lock).await;
} else {
return None;
}
}
}
}
pub struct SbdClientCrypto {
pub_key: PubKey,
inner: Arc<tokio::sync::Mutex<Option<Inner>>>,
}
impl SbdClientCrypto {
pub async fn new(
url: &str,
config: Arc<Config>,
) -> Result<(Self, MsgRecv)> {
let client_config = sbd_client::SbdClientConfig {
allow_plain_text: config.allow_plain_text,
..Default::default()
};
let crypto = sodoken_crypto::SodokenCrypto::new()?;
use sbd_client::Crypto;
let pub_key = PubKey(Arc::new(*crypto.pub_key()));
let (client, recv) =
sbd_client::SbdClient::connect_config(url, &crypto, client_config)
.await?;
let inner = Arc::new(tokio::sync::Mutex::new(Some(Inner {
config,
crypto,
client,
map: HashMap::default(),
})));
let weak_inner = Arc::downgrade(&inner);
Ok((
Self { pub_key, inner },
MsgRecv {
inner: weak_inner,
recv,
},
))
}
pub fn pub_key(&self) -> &PubKey {
&self.pub_key
}
pub async fn assert(&self, pk: &PubKey) -> Result<()> {
let mut lock = self.inner.lock().await;
if let Some(inner) = &mut *lock {
inner.assert(pk).await
} else {
Err(Error::other("closed"))
}
}
pub async fn send(&self, pk: &PubKey, msg: &[u8]) -> Result<()> {
const SBD_MAX: usize = 20_000;
const SBD_HDR: usize = 32;
const SS_ABYTES: usize = sodoken::secretstream::ABYTES;
const MAX_MSG: usize = SBD_MAX - SBD_HDR - SS_ABYTES;
if msg.len() > MAX_MSG {
return Err(Error::other("message too long"));
}
let mut lock = self.inner.lock().await;
if let Some(inner) = &mut *lock {
inner.send(pk, msg).await
} else {
Err(Error::other("closed"))
}
}
pub async fn close_peer(&self, pk: &PubKey) {
if let Some(inner) = self.inner.lock().await.as_mut() {
inner.close_peer(pk);
}
}
pub async fn close(&self) {
close_inner(&mut *self.inner.lock().await).await;
}
}
#[cfg(test)]
mod test;