use super::{
PeerRpcStreamCancelV2, PeerRpcStreamChunkV2, PeerRpcStreamCommitV2, PeerRpcStreamFrameV2,
PeerRpcStreamOpenV2, PeerRpcStreamPullV2, PeerRpcStreamReplyV2,
};
use postcard::ser_flavors::Size;
use serde::{Deserialize, Serialize};
const MAGIC: [u8; 8] = *b"APCRPC2B";
const HEADER_BYTES: usize = 14;
const FRAME_KIND: u8 = 1;
const REPLY_KIND: u8 = 2;
pub const PEER_RPC_BINARY_CODEC_VERSION_V2: u8 = 1;
pub const PEER_RPC_BINARY_CONTENT_TYPE_V2: &str = "application/vnd.appcore.peer-rpc.v2+postcard";
pub const MAX_PEER_RPC_BINARY_FRAME_BYTES_V2: usize = 256 * 1024;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PeerRpcStreamCodecV2 {
Json,
Binary,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum PeerRpcBinaryCodecErrorV2 {
#[error("peer RPC binary codec limit is invalid")]
InvalidLimit,
#[error("peer RPC binary frame is too large")]
FrameTooLarge,
#[error("peer RPC binary frame marker is invalid")]
InvalidMagic,
#[error("peer RPC binary codec version is unsupported")]
UnsupportedVersion,
#[error("peer RPC binary message kind is invalid")]
InvalidMessageKind,
#[error("peer RPC binary frame length is invalid")]
InvalidLength,
#[error("peer RPC binary payload is invalid")]
InvalidPayload,
}
pub fn encode_binary_frame_v2(
frame: &PeerRpcStreamFrameV2,
max_bytes: usize,
) -> Result<Vec<u8>, PeerRpcBinaryCodecErrorV2> {
encode_message(&BinaryFrameRef::from(frame), FRAME_KIND, max_bytes)
}
pub fn decode_binary_frame_v2(
body: &[u8],
max_bytes: usize,
) -> Result<PeerRpcStreamFrameV2, PeerRpcBinaryCodecErrorV2> {
decode_message::<BinaryFrameOwned>(body, FRAME_KIND, max_bytes).map(Into::into)
}
pub fn encode_binary_reply_v2(
reply: &PeerRpcStreamReplyV2,
max_bytes: usize,
) -> Result<Vec<u8>, PeerRpcBinaryCodecErrorV2> {
encode_message(&BinaryReplyRef::from(reply), REPLY_KIND, max_bytes)
}
pub fn decode_binary_reply_v2(
body: &[u8],
max_bytes: usize,
) -> Result<PeerRpcStreamReplyV2, PeerRpcBinaryCodecErrorV2> {
decode_message::<BinaryReplyOwned>(body, REPLY_KIND, max_bytes).map(Into::into)
}
fn encode_message<T>(
value: &T,
message_kind: u8,
max_bytes: usize,
) -> Result<Vec<u8>, PeerRpcBinaryCodecErrorV2>
where
T: Serialize + ?Sized,
{
let limit = effective_limit(max_bytes)?;
let payload_bytes = postcard::serialize_with_flavor::<T, Size, usize>(value, Size::default())
.map_err(|_| PeerRpcBinaryCodecErrorV2::InvalidPayload)?;
let total_bytes = HEADER_BYTES
.checked_add(payload_bytes)
.ok_or(PeerRpcBinaryCodecErrorV2::FrameTooLarge)?;
if total_bytes > limit || payload_bytes > u32::MAX as usize {
return Err(PeerRpcBinaryCodecErrorV2::FrameTooLarge);
}
let mut body = vec![0; total_bytes];
body[..8].copy_from_slice(&MAGIC);
body[8] = PEER_RPC_BINARY_CODEC_VERSION_V2;
body[9] = message_kind;
body[10..HEADER_BYTES].copy_from_slice(&(payload_bytes as u32).to_be_bytes());
let encoded = postcard::to_slice(value, &mut body[HEADER_BYTES..])
.map_err(|_| PeerRpcBinaryCodecErrorV2::InvalidPayload)?;
if encoded.len() != payload_bytes {
return Err(PeerRpcBinaryCodecErrorV2::InvalidPayload);
}
Ok(body)
}
fn decode_message<T>(
body: &[u8],
expected_kind: u8,
max_bytes: usize,
) -> Result<T, PeerRpcBinaryCodecErrorV2>
where
T: for<'de> Deserialize<'de>,
{
let limit = effective_limit(max_bytes)?;
if body.len() > limit {
return Err(PeerRpcBinaryCodecErrorV2::FrameTooLarge);
}
if body.len() < HEADER_BYTES {
return Err(PeerRpcBinaryCodecErrorV2::InvalidLength);
}
if body[..8] != MAGIC {
return Err(PeerRpcBinaryCodecErrorV2::InvalidMagic);
}
if body[8] != PEER_RPC_BINARY_CODEC_VERSION_V2 {
return Err(PeerRpcBinaryCodecErrorV2::UnsupportedVersion);
}
if body[9] != expected_kind {
return Err(PeerRpcBinaryCodecErrorV2::InvalidMessageKind);
}
let declared = u32::from_be_bytes(
body[10..HEADER_BYTES]
.try_into()
.map_err(|_| PeerRpcBinaryCodecErrorV2::InvalidLength)?,
) as usize;
if HEADER_BYTES.checked_add(declared) != Some(body.len()) {
return Err(PeerRpcBinaryCodecErrorV2::InvalidLength);
}
postcard::from_bytes(&body[HEADER_BYTES..])
.map_err(|_| PeerRpcBinaryCodecErrorV2::InvalidPayload)
}
fn effective_limit(max_bytes: usize) -> Result<usize, PeerRpcBinaryCodecErrorV2> {
if max_bytes == 0 {
return Err(PeerRpcBinaryCodecErrorV2::InvalidLimit);
}
Ok(max_bytes.min(MAX_PEER_RPC_BINARY_FRAME_BYTES_V2))
}
#[derive(Serialize)]
enum BinaryFrameRef<'a> {
Open(&'a PeerRpcStreamOpenV2),
Chunk(&'a PeerRpcStreamChunkV2),
Commit(&'a PeerRpcStreamCommitV2),
Cancel(&'a PeerRpcStreamCancelV2),
Pull(&'a PeerRpcStreamPullV2),
}
impl<'a> From<&'a PeerRpcStreamFrameV2> for BinaryFrameRef<'a> {
fn from(frame: &'a PeerRpcStreamFrameV2) -> Self {
match frame {
PeerRpcStreamFrameV2::Open(value) => Self::Open(value),
PeerRpcStreamFrameV2::Chunk(value) => Self::Chunk(value),
PeerRpcStreamFrameV2::Commit(value) => Self::Commit(value),
PeerRpcStreamFrameV2::Cancel(value) => Self::Cancel(value),
PeerRpcStreamFrameV2::Pull(value) => Self::Pull(value),
}
}
}
#[derive(Deserialize)]
enum BinaryFrameOwned {
Open(Box<PeerRpcStreamOpenV2>),
Chunk(PeerRpcStreamChunkV2),
Commit(PeerRpcStreamCommitV2),
Cancel(PeerRpcStreamCancelV2),
Pull(PeerRpcStreamPullV2),
}
impl From<BinaryFrameOwned> for PeerRpcStreamFrameV2 {
fn from(frame: BinaryFrameOwned) -> Self {
match frame {
BinaryFrameOwned::Open(value) => Self::Open(value),
BinaryFrameOwned::Chunk(value) => Self::Chunk(value),
BinaryFrameOwned::Commit(value) => Self::Commit(value),
BinaryFrameOwned::Cancel(value) => Self::Cancel(value),
BinaryFrameOwned::Pull(value) => Self::Pull(value),
}
}
}
#[derive(Serialize)]
struct BinaryReplyRef<'a> {
request_id: &'a str,
stream_id: &'a str,
next_sequence: u32,
received_bytes: u64,
response_frame: Option<BinaryFrameRef<'a>>,
complete: bool,
}
impl<'a> From<&'a PeerRpcStreamReplyV2> for BinaryReplyRef<'a> {
fn from(reply: &'a PeerRpcStreamReplyV2) -> Self {
Self {
request_id: &reply.request_id,
stream_id: &reply.stream_id,
next_sequence: reply.next_sequence,
received_bytes: reply.received_bytes,
response_frame: reply.response_frame.as_deref().map(Into::into),
complete: reply.complete,
}
}
}
#[derive(Deserialize)]
struct BinaryReplyOwned {
request_id: String,
stream_id: String,
next_sequence: u32,
received_bytes: u64,
response_frame: Option<BinaryFrameOwned>,
complete: bool,
}
impl From<BinaryReplyOwned> for PeerRpcStreamReplyV2 {
fn from(reply: BinaryReplyOwned) -> Self {
Self {
request_id: reply.request_id,
stream_id: reply.stream_id,
next_sequence: reply.next_sequence,
received_bytes: reply.received_bytes,
response_frame: reply.response_frame.map(|frame| Box::new(frame.into())),
complete: reply.complete,
}
}
}