use std::net::SocketAddr;
use async_trait::async_trait;
use plaza::agent::{Agent, AgentId};
use plaza_wire::frame::{self, Goodbye, Kind, ProtocolVersion};
use tracing::trace;
use crate::codec::WireCodec;
use crate::manager::OutboundFrame;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Farewell {
pub code: u16,
pub detail: Option<Vec<u8>>,
}
impl Farewell {
pub fn new(code: u16) -> Self {
Self { code, detail: None }
}
pub fn with_detail(mut self, detail: impl Into<Vec<u8>>) -> Self {
self.detail = Some(detail.into());
self
}
pub fn credential_expected() -> Self {
Self::new(Goodbye::CREDENTIAL_EXPECTED)
}
pub fn credential_timeout() -> Self {
Self::new(Goodbye::CREDENTIAL_TIMEOUT)
}
pub fn encode<C: WireCodec>(&self, codec: &C) -> OutboundFrame {
let goodbye = Goodbye {
code: self.code,
detail: self.detail.clone(),
};
OutboundFrame::from(frame::encode_goodbye(codec, &goodbye).unwrap_or_else(|_| vec![Kind::Goodbye.as_byte()]))
}
}
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct Peer {
pub addr: Option<SocketAddr>,
}
impl Peer {
pub fn new(addr: Option<SocketAddr>) -> Self {
Self { addr }
}
}
#[derive(Debug, Clone)]
pub enum ConnectionAdmission<ID: AgentId> {
Admitted(Agent<ID>),
Refused(Farewell),
}
#[async_trait]
pub trait ConnectionAdmitter<ID: AgentId>: Send + Sync {
async fn admit(&self, credential: &[u8], peer: &Peer) -> ConnectionAdmission<ID>;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Presented {
Nothing,
Credential(Vec<u8>),
Refuse(Farewell),
}
#[derive(Debug)]
pub struct Pending {
declared: Option<ProtocolVersion>,
max_credential_bytes: usize,
}
impl Pending {
pub fn new(max_credential_bytes: usize) -> Self {
Self {
declared: None,
max_credential_bytes,
}
}
pub fn declared(&self) -> Option<ProtocolVersion> {
self.declared
}
pub fn on_frame<C: WireCodec>(&mut self, codec: &C, bytes: &[u8]) -> Presented {
let Some((tag, body)) = frame::split(bytes) else {
return Presented::Nothing;
};
match Kind::from_byte(tag) {
Some(Kind::Hello) => {
if let Ok(theirs) = codec.decode::<ProtocolVersion>(body) {
self.declared = Some(theirs);
}
Presented::Nothing
}
Some(Kind::Credential) => {
if body.len() > self.max_credential_bytes {
Presented::Refuse(Farewell::new(MESSAGE_TOO_BIG))
} else {
Presented::Credential(body.to_vec())
}
}
Some(Kind::Ops) => Presented::Refuse(Farewell::credential_expected()),
Some(Kind::Ping) | Some(Kind::Pong) | Some(Kind::Goodbye) => Presented::Nothing,
_ => {
trace!(kind = tag, "Skipping a frame of unknown kind before admission.");
Presented::Nothing
}
}
}
}
pub const MESSAGE_TOO_BIG: u16 = 1009;
pub const POLICY_VIOLATION: u16 = 1008;
#[cfg(test)]
mod tests {
use super::*;
use crate::codec::JsonCodec;
fn framed(kind: Kind, body: &[u8]) -> Vec<u8> {
let mut buf = Vec::new();
frame::begin(kind, &mut buf);
buf.extend_from_slice(body);
buf
}
#[test]
fn a_hello_is_kept_and_the_credential_is_handed_over_as_it_arrived() {
let mut pending = Pending::new(64);
let mut hello = Vec::new();
frame::begin(Kind::Hello, &mut hello);
JsonCodec.encode_into(&ProtocolVersion(7), &mut hello).unwrap();
assert_eq!(pending.on_frame(&JsonCodec, &hello), Presented::Nothing);
assert_eq!(pending.declared(), Some(ProtocolVersion(7)));
assert_eq!(
pending.on_frame(&JsonCodec, &framed(Kind::Credential, b"tok3n")),
Presented::Credential(b"tok3n".to_vec())
);
}
#[test]
fn ops_before_the_credential_close_the_socket_and_probes_are_ignored() {
let mut pending = Pending::new(64);
assert_eq!(pending.on_frame(&JsonCodec, &framed(Kind::Ping, b"{}")), Presented::Nothing);
assert_eq!(pending.on_frame(&JsonCodec, &framed(Kind::Pong, b"{}")), Presented::Nothing);
assert_eq!(pending.on_frame(&JsonCodec, &[200, 1, 2]), Presented::Nothing, "unknown kinds are still skipped");
assert_eq!(pending.on_frame(&JsonCodec, &[]), Presented::Nothing);
assert_eq!(
pending.on_frame(&JsonCodec, &framed(Kind::Ops, b"[]")),
Presented::Refuse(Farewell::credential_expected())
);
}
#[test]
fn a_credential_over_the_cap_is_refused_as_too_big() {
let mut pending = Pending::new(4);
assert_eq!(
pending.on_frame(&JsonCodec, &framed(Kind::Credential, b"12345")),
Presented::Refuse(Farewell::new(MESSAGE_TOO_BIG))
);
}
#[test]
fn a_farewell_encodes_as_a_goodbye_frame() {
let farewell = Farewell::new(4403).with_detail(b"banned".to_vec());
let encoded = farewell.encode(&JsonCodec);
let goodbye = frame::decode_goodbye(&JsonCodec, &encoded).unwrap();
assert_eq!(goodbye.code, 4403);
assert_eq!(goodbye.detail.as_deref(), Some(&b"banned"[..]));
assert_eq!(Farewell::credential_timeout().code, 4408);
assert_eq!(Farewell::credential_expected().code, 4401);
}
}