use crate::protocol::schema::{self, ArkToHost, HostToArk, ark_to_host, host_to_ark};
use prost::Message as ProtobufMessage;
use prost::bytes::Bytes;
use super::session::SessionInner;
use super::{Error, Message};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Weak};
#[derive(Clone, Copy, PartialEq, Eq)]
pub(super) enum Side {
Client,
Server,
}
impl Side {
pub(super) fn decode_header(&self, bytes: Bytes) -> Result<Header, Error> {
let length = bytes.len();
let invalid = |error| self.malformed(None, length, "envelope", error);
let (id, failed, payload) = match self {
Self::Client => {
let envelope = opaque::ArkToHost::decode(bytes).map_err(invalid)?;
(
envelope.id,
envelope.err.is_some(),
envelope.content.as_ref().map(|content| content.name()),
)
}
Self::Server => {
let envelope = opaque::HostToArk::decode(bytes).map_err(invalid)?;
(
envelope.id,
envelope.err.is_some(),
envelope.content.as_ref().map(|content| content.name()),
)
}
};
let header = Header {
id,
failed,
payload: payload.or(failed.then_some("err")),
};
if payload.is_some() == failed {
return Err(self.malformed(
Some(header),
length,
"envelope",
if failed {
"envelope contains both content and error"
} else {
"envelope has neither content nor error"
},
));
}
Ok(header)
}
pub(super) fn encode(
&self,
id: u64,
body: Result<Message, schema::Error>,
) -> Result<Vec<u8>, Error> {
match self {
Self::Client => encode::<HostToArk>(id, body),
Self::Server => encode::<ArkToHost>(id, body),
}
}
pub(super) fn decode(
&self,
bytes: &[u8],
) -> Result<(u64, Result<Message, schema::Error>), DecodeError> {
match self {
Self::Client => decode::<ArkToHost>(bytes),
Self::Server => decode::<HostToArk>(bytes),
}
}
pub(super) fn malformed(
&self,
header: Option<Header>,
length: usize,
stage: &'static str,
reason: impl std::fmt::Display,
) -> Error {
if let Some(header) = header {
let kind = match MessageKind::from_id(header.id, (*self).into()) {
MessageKind::Request => "request",
MessageKind::Response => "response",
};
tracing::warn!(
"malformed protocol {} (id: {}, kind: {}, payload: {}, length: {}): {}",
stage,
header.id,
kind,
header.payload.unwrap_or("none"),
length,
reason,
);
} else {
tracing::warn!(
"malformed protocol {} (length: {}): {}",
stage,
length,
reason
);
}
Error::Malformed
}
}
#[derive(Clone, Copy)]
pub(super) struct Header {
pub(super) id: u64,
pub(super) failed: bool,
pub(super) payload: Option<&'static str>,
}
pub(super) struct IncomingEnvelope {
bytes: Bytes,
header: Header,
charge: ByteCharge,
side: Side,
session: Weak<SessionInner>,
}
impl IncomingEnvelope {
pub(super) fn new(
bytes: Bytes,
header: Header,
retained_bytes: &Arc<AtomicUsize>,
limit: usize,
side: Side,
session: Weak<SessionInner>,
) -> Result<Self, Error> {
let charge = ByteCharge::reserve(retained_bytes, bytes.len(), limit)?;
Ok(Self {
bytes,
header,
charge,
side,
session,
})
}
pub(super) fn decode(self) -> Result<Message, Error> {
let Self {
bytes,
header,
charge,
side,
session,
} = self;
drop(charge);
match side.decode(&bytes) {
Ok((_, body)) => body.map_err(Error::Remote),
Err(error) => {
let error = side.malformed(Some(header), bytes.len(), "payload", error);
if let Some(session) = session.upgrade() {
session.close(error.clone());
}
Err(error)
}
}
}
}
struct ByteCharge {
used: Arc<AtomicUsize>,
bytes: usize,
}
impl ByteCharge {
fn reserve(used: &Arc<AtomicUsize>, bytes: usize, limit: usize) -> Result<Self, Error> {
used.try_update(Ordering::Relaxed, Ordering::Relaxed, |used| {
used.checked_add(bytes).filter(|total| *total <= limit)
})
.map_err(|used| {
tracing::warn!(
"inbound byte limit exceeded (used: {}, incoming: {}, limit: {})",
used,
bytes,
limit
);
Error::InboundByteLimitExceeded(limit)
})?;
Ok(Self {
used: used.clone(),
bytes,
})
}
}
impl Drop for ByteCharge {
fn drop(&mut self) {
let previous = self.used.fetch_sub(self.bytes, Ordering::Relaxed);
debug_assert!(previous >= self.bytes, "incoming byte charge underflow");
}
}
fn encode<E: Envelope>(id: u64, body: Result<Message, schema::Error>) -> Result<Vec<u8>, Error>
where
E::Content: TryFrom<Message, Error = Error>,
{
let envelope = match body {
Ok(body) => E::from_parts(id, None, Some(body.try_into()?)),
Err(error) => E::from_parts(id, Some(error), None),
};
let size = envelope.encoded_len();
if size > crate::transport::MAX_MESSAGE_SIZE {
return Err(Error::TooLarge(size));
}
Ok(envelope.encode_to_vec())
}
fn decode<E: Envelope>(bytes: &[u8]) -> Result<(u64, Result<Message, schema::Error>), DecodeError>
where
Message: From<E::Content>,
{
let (id, error, content) = E::decode(bytes)?.into_parts();
let body = match (content, error) {
(Some(content), None) => Ok(content.into()),
(None, Some(error)) => Err(error),
_ => return Err(DecodeError::Body),
};
Ok((id, body))
}
#[derive(Debug, thiserror::Error)]
pub(super) enum DecodeError {
#[error("{0}")]
Protobuf(#[from] prost::DecodeError),
#[error("expected exactly one of content or error")]
Body,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum Parity {
Odd,
Even,
}
impl Parity {
fn of(id: u64) -> Self {
if id % 2 == 1 { Self::Odd } else { Self::Even }
}
pub(super) fn first(self) -> u64 {
match self {
Self::Odd => 1,
Self::Even => 2,
}
}
}
impl From<Side> for Parity {
fn from(side: Side) -> Self {
match side {
Side::Client => Self::Odd,
Side::Server => Self::Even,
}
}
}
trait Envelope: ProtobufMessage + Default {
type Content;
fn from_parts(id: u64, err: Option<schema::Error>, content: Option<Self::Content>) -> Self;
fn into_parts(self) -> (u64, Option<schema::Error>, Option<Self::Content>);
}
impl Envelope for HostToArk {
type Content = host_to_ark::Content;
fn from_parts(id: u64, err: Option<schema::Error>, content: Option<Self::Content>) -> Self {
Self { id, err, content }
}
fn into_parts(self) -> (u64, Option<schema::Error>, Option<Self::Content>) {
(self.id, self.err, self.content)
}
}
impl Envelope for ArkToHost {
type Content = ark_to_host::Content;
fn from_parts(id: u64, err: Option<schema::Error>, content: Option<Self::Content>) -> Self {
Self { id, err, content }
}
fn into_parts(self) -> (u64, Option<schema::Error>, Option<Self::Content>) {
(self.id, self.err, self.content)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum MessageKind {
Request,
Response,
}
impl MessageKind {
pub(super) fn from_id(id: u64, parity: Parity) -> Self {
if Parity::of(id) == parity {
Self::Response
} else {
Self::Request
}
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::*;
#[test]
fn test_kinds() {
struct TestCase {
id: u64,
parity: Parity,
kind: MessageKind,
}
let tests = [
TestCase {
id: 1,
parity: Parity::Odd,
kind: MessageKind::Response,
},
TestCase {
id: 2,
parity: Parity::Odd,
kind: MessageKind::Request,
},
TestCase {
id: 1,
parity: Parity::Even,
kind: MessageKind::Request,
},
TestCase {
id: 2,
parity: Parity::Even,
kind: MessageKind::Response,
},
TestCase {
id: 0,
parity: Parity::Odd,
kind: MessageKind::Request,
},
TestCase {
id: 0,
parity: Parity::Even,
kind: MessageKind::Response,
},
TestCase {
id: u64::MAX,
parity: Parity::Even,
kind: MessageKind::Request,
},
];
for (i, tt) in tests.iter().enumerate() {
assert_eq!(MessageKind::from_id(tt.id, tt.parity), tt.kind, "test {i}");
}
}
}
#[allow(clippy::all)]
#[allow(rustdoc::broken_intra_doc_links)]
pub(super) mod opaque {
include!(concat!(env!("OUT_DIR"), "/darkbio.wire.opaque.rs"));
}
include!(concat!(env!("OUT_DIR"), "/darkbio.wire.names.rs"));