use bytes::{Buf, BufMut, Bytes, BytesMut};
use std::convert::TryFrom;
use thiserror::Error;
pub const RPC_VERSION_2: u32 = 2;
pub const MAX_AUTH_BYTES: usize = 400;
pub const MAX_FRAGMENT_LEN: u32 = 0x7fff_ffff;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Xid(pub u32);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ProgramVersion {
pub program: u32,
pub version: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Procedure(pub u32);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct VersionRange {
pub low: u32,
pub high: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AuthFlavor {
None,
Sys,
Short,
Dh,
RpcSecGss,
Unknown(u32),
}
impl AuthFlavor {
pub fn number(self) -> u32 {
match self {
Self::None => 0,
Self::Sys => 1,
Self::Short => 2,
Self::Dh => 3,
Self::RpcSecGss => 6,
Self::Unknown(value) => value,
}
}
}
impl From<u32> for AuthFlavor {
fn from(value: u32) -> Self {
match value {
0 => Self::None,
1 => Self::Sys,
2 => Self::Short,
3 => Self::Dh,
6 => Self::RpcSecGss,
other => Self::Unknown(other),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OpaqueAuth {
pub flavor: AuthFlavor,
pub body: Bytes,
}
impl OpaqueAuth {
pub fn new(flavor: AuthFlavor, body: Bytes) -> Result<Self, WireError> {
validate_auth_len(body.len())?;
Ok(Self { flavor, body })
}
pub fn none() -> Self {
Self {
flavor: AuthFlavor::None,
body: Bytes::new(),
}
}
fn encode_into(&self, output: &mut BytesMut) -> Result<(), WireError> {
validate_auth_len(self.body.len())?;
output.put_u32(self.flavor.number());
output.put_u32(self.body.len() as u32);
output.extend_from_slice(&self.body);
pad_to_xdr_alignment(output, self.body.len());
Ok(())
}
fn decode(input: &mut &[u8]) -> Result<Self, WireError> {
let flavor = AuthFlavor::from(read_u32(input)?);
let body_len = read_u32(input)? as usize;
validate_auth_len(body_len)?;
let body = read_opaque(input, body_len)?;
Ok(Self { flavor, body })
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RecordMarker {
pub last_fragment: bool,
pub payload_len: u32,
}
impl RecordMarker {
pub fn new(payload_len: u32, last_fragment: bool) -> Result<Self, WireError> {
if payload_len > MAX_FRAGMENT_LEN {
return Err(WireError::FragmentLengthTooLarge(payload_len));
}
Ok(Self {
last_fragment,
payload_len,
})
}
pub fn encode(self) -> u32 {
let final_fragment = if self.last_fragment { 1_u32 << 31 } else { 0 };
final_fragment | self.payload_len
}
pub fn encode_bytes(self) -> [u8; 4] {
self.encode().to_be_bytes()
}
pub fn decode(word: u32) -> Self {
Self {
last_fragment: (word & (1_u32 << 31)) != 0,
payload_len: word & MAX_FRAGMENT_LEN,
}
}
pub fn decode_bytes(header: [u8; 4]) -> Self {
Self::decode(u32::from_be_bytes(header))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RecordRead {
Incomplete,
Complete {
data: Bytes,
consumed: usize,
fragments: usize,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MessageType {
Call,
Reply,
}
impl MessageType {
fn number(self) -> u32 {
match self {
Self::Call => 0,
Self::Reply => 1,
}
}
}
impl TryFrom<u32> for MessageType {
type Error = WireError;
fn try_from(value: u32) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Call),
1 => Ok(Self::Reply),
other => Err(WireError::InvalidMessageType(other)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReplyStat {
MessageAccepted,
MessageDenied,
}
impl ReplyStat {
fn number(self) -> u32 {
match self {
Self::MessageAccepted => 0,
Self::MessageDenied => 1,
}
}
}
impl TryFrom<u32> for ReplyStat {
type Error = WireError;
fn try_from(value: u32) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::MessageAccepted),
1 => Ok(Self::MessageDenied),
other => Err(WireError::InvalidReplyStat(other)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AcceptStat {
Success,
ProgramUnavailable,
ProgramMismatch,
ProcedureUnavailable,
GarbageArgs,
SystemError,
}
impl AcceptStat {
fn number(self) -> u32 {
match self {
Self::Success => 0,
Self::ProgramUnavailable => 1,
Self::ProgramMismatch => 2,
Self::ProcedureUnavailable => 3,
Self::GarbageArgs => 4,
Self::SystemError => 5,
}
}
}
impl TryFrom<u32> for AcceptStat {
type Error = WireError;
fn try_from(value: u32) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Success),
1 => Ok(Self::ProgramUnavailable),
2 => Ok(Self::ProgramMismatch),
3 => Ok(Self::ProcedureUnavailable),
4 => Ok(Self::GarbageArgs),
5 => Ok(Self::SystemError),
other => Err(WireError::InvalidAcceptStat(other)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RejectStat {
RpcMismatch,
AuthError,
}
impl RejectStat {
fn number(self) -> u32 {
match self {
Self::RpcMismatch => 0,
Self::AuthError => 1,
}
}
}
impl TryFrom<u32> for RejectStat {
type Error = WireError;
fn try_from(value: u32) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::RpcMismatch),
1 => Ok(Self::AuthError),
other => Err(WireError::InvalidRejectStat(other)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AuthStat {
Ok,
BadCred,
RejectedCred,
BadVerf,
RejectedVerf,
TooWeak,
InvalidResp,
Failed,
KerbGeneric,
TimeExpire,
TicketFile,
Decode,
NetAddr,
RpcSecGssCredProblem,
RpcSecGssCtxProblem,
Unknown(u32),
}
impl AuthStat {
fn number(self) -> u32 {
match self {
Self::Ok => 0,
Self::BadCred => 1,
Self::RejectedCred => 2,
Self::BadVerf => 3,
Self::RejectedVerf => 4,
Self::TooWeak => 5,
Self::InvalidResp => 6,
Self::Failed => 7,
Self::KerbGeneric => 8,
Self::TimeExpire => 9,
Self::TicketFile => 10,
Self::Decode => 11,
Self::NetAddr => 12,
Self::RpcSecGssCredProblem => 13,
Self::RpcSecGssCtxProblem => 14,
Self::Unknown(value) => value,
}
}
}
impl From<u32> for AuthStat {
fn from(value: u32) -> Self {
match value {
0 => Self::Ok,
1 => Self::BadCred,
2 => Self::RejectedCred,
3 => Self::BadVerf,
4 => Self::RejectedVerf,
5 => Self::TooWeak,
6 => Self::InvalidResp,
7 => Self::Failed,
8 => Self::KerbGeneric,
9 => Self::TimeExpire,
10 => Self::TicketFile,
11 => Self::Decode,
12 => Self::NetAddr,
13 => Self::RpcSecGssCredProblem,
14 => Self::RpcSecGssCtxProblem,
other => Self::Unknown(other),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CallBody {
pub rpc_version: u32,
pub program: ProgramVersion,
pub procedure: Procedure,
pub credentials: OpaqueAuth,
pub verifier: OpaqueAuth,
pub payload: Bytes,
}
impl CallBody {
pub fn new(
program: ProgramVersion,
procedure: Procedure,
credentials: OpaqueAuth,
verifier: OpaqueAuth,
payload: Bytes,
) -> Self {
Self {
rpc_version: RPC_VERSION_2,
program,
procedure,
credentials,
verifier,
payload,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AcceptedStatus {
Success(Bytes),
ProgramUnavailable,
ProgramMismatch(VersionRange),
ProcedureUnavailable,
GarbageArgs,
SystemError,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AcceptedReply {
pub verifier: OpaqueAuth,
pub status: AcceptedStatus,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RejectedReply {
RpcMismatch(VersionRange),
AuthError(AuthStat),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ReplyBody {
Accepted(AcceptedReply),
Denied(RejectedReply),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MessageBody {
Call(CallBody),
Reply(ReplyBody),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RpcMessage {
pub xid: Xid,
pub body: MessageBody,
}
impl RpcMessage {
pub fn encode(&self) -> Result<Bytes, WireError> {
let mut output = BytesMut::new();
output.put_u32(self.xid.0);
match &self.body {
MessageBody::Call(call) => {
output.put_u32(MessageType::Call.number());
output.put_u32(call.rpc_version);
output.put_u32(call.program.program);
output.put_u32(call.program.version);
output.put_u32(call.procedure.0);
call.credentials.encode_into(&mut output)?;
call.verifier.encode_into(&mut output)?;
output.extend_from_slice(&call.payload);
}
MessageBody::Reply(reply) => {
output.put_u32(MessageType::Reply.number());
encode_reply_body(reply, &mut output)?;
}
}
Ok(output.freeze())
}
pub fn decode(input: &[u8]) -> Result<Self, WireError> {
let mut input = input;
let xid = Xid(read_u32(&mut input)?);
let message_type = MessageType::try_from(read_u32(&mut input)?)?;
let body = match message_type {
MessageType::Call => MessageBody::Call(decode_call_body(&mut input)?),
MessageType::Reply => MessageBody::Reply(decode_reply_body(&mut input)?),
};
Ok(Self { xid, body })
}
}
pub fn fragment_record(data: &[u8], max_fragment_len: u32) -> Result<Vec<Bytes>, WireError> {
if max_fragment_len == 0 {
return Err(WireError::ZeroFragmentLength);
}
if max_fragment_len > MAX_FRAGMENT_LEN {
return Err(WireError::FragmentLengthTooLarge(max_fragment_len));
}
let chunk_len = max_fragment_len as usize;
let fragment_count = data.len().div_ceil(chunk_len).max(1);
let mut fragments = Vec::with_capacity(fragment_count);
if data.is_empty() {
let marker = RecordMarker::new(0, true)?;
fragments.push(Bytes::copy_from_slice(&marker.encode_bytes()));
return Ok(fragments);
}
for (idx, chunk) in data.chunks(chunk_len).enumerate() {
let marker = RecordMarker::new(chunk.len() as u32, idx + 1 == fragment_count)?;
let mut fragment = BytesMut::with_capacity(4 + chunk.len());
fragment.extend_from_slice(&marker.encode_bytes());
fragment.extend_from_slice(chunk);
fragments.push(fragment.freeze());
}
Ok(fragments)
}
pub fn read_record(input: &[u8]) -> Result<RecordRead, WireError> {
let mut offset = 0_usize;
let mut fragments = 0_usize;
let mut record = BytesMut::new();
loop {
let remaining = &input[offset..];
if remaining.is_empty() && fragments == 0 {
return Ok(RecordRead::Incomplete);
}
if remaining.len() < 4 {
return Ok(RecordRead::Incomplete);
}
let marker =
RecordMarker::decode_bytes(remaining[..4].try_into().expect("slice len checked"));
let fragment_len = marker.payload_len as usize;
let fragment_end = 4 + fragment_len;
if remaining.len() < fragment_end {
return Ok(RecordRead::Incomplete);
}
record.extend_from_slice(&remaining[4..fragment_end]);
offset += fragment_end;
fragments += 1;
if marker.last_fragment {
return Ok(RecordRead::Complete {
data: record.freeze(),
consumed: offset,
fragments,
});
}
}
}
fn decode_call_body(input: &mut &[u8]) -> Result<CallBody, WireError> {
let rpc_version = read_u32(input)?;
if rpc_version != RPC_VERSION_2 {
return Err(WireError::UnsupportedRpcVersion(rpc_version));
}
let program = ProgramVersion {
program: read_u32(input)?,
version: read_u32(input)?,
};
let procedure = Procedure(read_u32(input)?);
let credentials = OpaqueAuth::decode(input)?;
let verifier = OpaqueAuth::decode(input)?;
let payload = Bytes::copy_from_slice(input);
*input = &[];
Ok(CallBody {
rpc_version,
program,
procedure,
credentials,
verifier,
payload,
})
}
fn decode_reply_body(input: &mut &[u8]) -> Result<ReplyBody, WireError> {
match ReplyStat::try_from(read_u32(input)?)? {
ReplyStat::MessageAccepted => decode_accepted_reply(input).map(ReplyBody::Accepted),
ReplyStat::MessageDenied => decode_rejected_reply(input).map(ReplyBody::Denied),
}
}
fn decode_accepted_reply(input: &mut &[u8]) -> Result<AcceptedReply, WireError> {
let verifier = OpaqueAuth::decode(input)?;
let status = match AcceptStat::try_from(read_u32(input)?)? {
AcceptStat::Success => {
let payload = Bytes::copy_from_slice(input);
*input = &[];
AcceptedStatus::Success(payload)
}
AcceptStat::ProgramUnavailable => expect_end(input, AcceptedStatus::ProgramUnavailable)?,
AcceptStat::ProgramMismatch => {
let range = VersionRange {
low: read_u32(input)?,
high: read_u32(input)?,
};
expect_end(input, AcceptedStatus::ProgramMismatch(range))?
}
AcceptStat::ProcedureUnavailable => {
expect_end(input, AcceptedStatus::ProcedureUnavailable)?
}
AcceptStat::GarbageArgs => expect_end(input, AcceptedStatus::GarbageArgs)?,
AcceptStat::SystemError => expect_end(input, AcceptedStatus::SystemError)?,
};
Ok(AcceptedReply { verifier, status })
}
fn decode_rejected_reply(input: &mut &[u8]) -> Result<RejectedReply, WireError> {
match RejectStat::try_from(read_u32(input)?)? {
RejectStat::RpcMismatch => {
let range = VersionRange {
low: read_u32(input)?,
high: read_u32(input)?,
};
expect_end(input, RejectedReply::RpcMismatch(range))
}
RejectStat::AuthError => {
let status = AuthStat::from(read_u32(input)?);
expect_end(input, RejectedReply::AuthError(status))
}
}
}
fn encode_reply_body(reply: &ReplyBody, output: &mut BytesMut) -> Result<(), WireError> {
match reply {
ReplyBody::Accepted(accepted) => {
output.put_u32(ReplyStat::MessageAccepted.number());
accepted.verifier.encode_into(output)?;
match &accepted.status {
AcceptedStatus::Success(results) => {
output.put_u32(AcceptStat::Success.number());
output.extend_from_slice(results);
}
AcceptedStatus::ProgramUnavailable => {
output.put_u32(AcceptStat::ProgramUnavailable.number());
}
AcceptedStatus::ProgramMismatch(range) => {
output.put_u32(AcceptStat::ProgramMismatch.number());
output.put_u32(range.low);
output.put_u32(range.high);
}
AcceptedStatus::ProcedureUnavailable => {
output.put_u32(AcceptStat::ProcedureUnavailable.number());
}
AcceptedStatus::GarbageArgs => {
output.put_u32(AcceptStat::GarbageArgs.number());
}
AcceptedStatus::SystemError => {
output.put_u32(AcceptStat::SystemError.number());
}
}
}
ReplyBody::Denied(rejected) => {
output.put_u32(ReplyStat::MessageDenied.number());
match rejected {
RejectedReply::RpcMismatch(range) => {
output.put_u32(RejectStat::RpcMismatch.number());
output.put_u32(range.low);
output.put_u32(range.high);
}
RejectedReply::AuthError(status) => {
output.put_u32(RejectStat::AuthError.number());
output.put_u32(status.number());
}
}
}
}
Ok(())
}
fn read_u32(input: &mut &[u8]) -> Result<u32, WireError> {
if input.len() < 4 {
return Err(WireError::UnexpectedEof {
needed: 4,
remaining: input.len(),
});
}
Ok(input.get_u32())
}
fn read_opaque(input: &mut &[u8], len: usize) -> Result<Bytes, WireError> {
if input.len() < len {
return Err(WireError::UnexpectedEof {
needed: len,
remaining: input.len(),
});
}
let body = Bytes::copy_from_slice(&input[..len]);
*input = &input[len..];
let padding = xdr_padding(len);
if input.len() < padding {
return Err(WireError::UnexpectedEof {
needed: padding,
remaining: input.len(),
});
}
if input[..padding].iter().any(|byte| *byte != 0) {
return Err(WireError::NonZeroXdrPadding);
}
*input = &input[padding..];
Ok(body)
}
fn pad_to_xdr_alignment(output: &mut BytesMut, len: usize) {
let padding = xdr_padding(len);
for _ in 0..padding {
output.put_u8(0);
}
}
fn xdr_padding(len: usize) -> usize {
(4 - (len % 4)) % 4
}
fn validate_auth_len(len: usize) -> Result<(), WireError> {
if len > MAX_AUTH_BYTES {
return Err(WireError::AuthBodyTooLong(len));
}
Ok(())
}
fn expect_end<T>(input: &mut &[u8], value: T) -> Result<T, WireError> {
if input.is_empty() {
Ok(value)
} else {
Err(WireError::TrailingBytes(input.len()))
}
}
#[derive(Debug, Clone, Error, PartialEq, Eq)]
pub enum WireError {
#[error("unexpected end of input: need {needed} bytes, have {remaining}")]
UnexpectedEof { needed: usize, remaining: usize },
#[error("record fragment length {0} exceeds the 31-bit record marking limit")]
FragmentLengthTooLarge(u32),
#[error("record fragment size cannot be zero when fragmenting a non-empty record")]
ZeroFragmentLength,
#[error("unsupported rpc version {0}")]
UnsupportedRpcVersion(u32),
#[error("invalid message type discriminant {0}")]
InvalidMessageType(u32),
#[error("invalid reply status discriminant {0}")]
InvalidReplyStat(u32),
#[error("invalid accept status discriminant {0}")]
InvalidAcceptStat(u32),
#[error("invalid reject status discriminant {0}")]
InvalidRejectStat(u32),
#[error("auth body exceeds RFC 5531 limit of 400 bytes: {0}")]
AuthBodyTooLong(usize),
#[error("encountered non-zero XDR padding bytes")]
NonZeroXdrPadding,
#[error("unexpected trailing bytes after fixed-width reply branch: {0}")]
TrailingBytes(usize),
}
#[cfg(test)]
mod tests {
use super::*;
fn auth(body: &[u8]) -> OpaqueAuth {
OpaqueAuth::new(AuthFlavor::None, Bytes::copy_from_slice(body)).expect("valid auth")
}
#[test]
fn record_marker_encodes_and_decodes() {
let marker = RecordMarker::new(1024, true).expect("valid marker");
assert_eq!(marker.encode(), 0x8000_0400);
assert_eq!(
RecordMarker::decode_bytes(marker.encode_bytes()),
RecordMarker {
last_fragment: true,
payload_len: 1024,
}
);
}
#[test]
fn fragment_record_splits_payload_into_multiple_fragments() {
let data = b"abcdefgh";
let fragments = fragment_record(data, 3).expect("fragmentation should succeed");
assert_eq!(fragments.len(), 3);
assert_eq!(&fragments[0][..4], &[0x00, 0x00, 0x00, 0x03]);
assert_eq!(&fragments[1][..4], &[0x00, 0x00, 0x00, 0x03]);
assert_eq!(&fragments[2][..4], &[0x80, 0x00, 0x00, 0x02]);
assert_eq!(&fragments[0][4..], b"abc");
assert_eq!(&fragments[1][4..], b"def");
assert_eq!(&fragments[2][4..], b"gh");
}
#[test]
fn read_record_reassembles_fragmented_message() {
let mut encoded = Vec::new();
for fragment in fragment_record(b"abcdefgh", 3).expect("fragmentation should succeed") {
encoded.extend_from_slice(&fragment);
}
let record = read_record(&encoded).expect("record read should succeed");
assert_eq!(
record,
RecordRead::Complete {
data: Bytes::from_static(b"abcdefgh"),
consumed: encoded.len(),
fragments: 3,
}
);
}
#[test]
fn read_record_reports_incomplete_header() {
let record = read_record(&[0x80, 0x00]).expect("partial header is not malformed");
assert_eq!(record, RecordRead::Incomplete);
}
#[test]
fn read_record_reports_incomplete_fragment_payload() {
let record = read_record(&[0x80, 0x00, 0x00, 0x04, 0xaa, 0xbb])
.expect("short payload is not malformed");
assert_eq!(record, RecordRead::Incomplete);
}
#[test]
fn opaque_auth_rejects_oversized_body() {
let body = Bytes::from(vec![0_u8; MAX_AUTH_BYTES + 1]);
let error = OpaqueAuth::new(AuthFlavor::Sys, body).expect_err("body must be rejected");
assert_eq!(error, WireError::AuthBodyTooLong(MAX_AUTH_BYTES + 1));
}
#[test]
fn rpc_call_round_trips() {
let message = RpcMessage {
xid: Xid(0x0102_0304),
body: MessageBody::Call(CallBody::new(
ProgramVersion {
program: 100_003,
version: 3,
},
Procedure(1),
auth(&[]),
auth(&[0xaa, 0xbb, 0xcc]),
Bytes::from_static(&[0xde, 0xad, 0xbe, 0xef]),
)),
};
let encoded = message.encode().expect("call should encode");
let decoded = RpcMessage::decode(&encoded).expect("call should decode");
assert_eq!(decoded, message);
}
#[test]
fn rpc_reply_round_trips() {
let message = RpcMessage {
xid: Xid(42),
body: MessageBody::Reply(ReplyBody::Accepted(AcceptedReply {
verifier: auth(&[]),
status: AcceptedStatus::Success(Bytes::from_static(&[0, 1, 2, 3])),
})),
};
let encoded = message.encode().expect("reply should encode");
let decoded = RpcMessage::decode(&encoded).expect("reply should decode");
assert_eq!(decoded, message);
}
#[test]
fn rpc_decode_rejects_invalid_message_type() {
let mut bytes = BytesMut::new();
bytes.put_u32(7);
bytes.put_u32(9);
let error = RpcMessage::decode(&bytes).expect_err("invalid message type must fail");
assert_eq!(error, WireError::InvalidMessageType(9));
}
#[test]
fn rpc_decode_rejects_invalid_accept_status() {
let mut bytes = BytesMut::new();
bytes.put_u32(7);
bytes.put_u32(MessageType::Reply.number());
bytes.put_u32(ReplyStat::MessageAccepted.number());
auth(&[]).encode_into(&mut bytes).expect("verifier encodes");
bytes.put_u32(88);
let error = RpcMessage::decode(&bytes).expect_err("invalid accept status must fail");
assert_eq!(error, WireError::InvalidAcceptStat(88));
}
#[test]
fn rpc_decode_rejects_non_zero_auth_padding() {
let bytes = [
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 3, 1, 2, 3, 0xff, 0, 0, 0, 0, 0, 0, 0, 0, ];
let error = RpcMessage::decode(&bytes).expect_err("invalid padding must fail");
assert_eq!(error, WireError::NonZeroXdrPadding);
}
#[test]
fn rpc_decode_rejects_trailing_bytes_on_fixed_reply_branch() {
let mut bytes = BytesMut::new();
bytes.put_u32(9);
bytes.put_u32(MessageType::Reply.number());
bytes.put_u32(ReplyStat::MessageDenied.number());
bytes.put_u32(RejectStat::AuthError.number());
bytes.put_u32(AuthStat::Failed.number());
bytes.put_u32(0xdead_beef);
let error = RpcMessage::decode(&bytes).expect_err("trailing bytes must fail");
assert_eq!(error, WireError::TrailingBytes(4));
}
#[test]
fn rpc_decode_rejects_wrong_rpc_version() {
let mut bytes = BytesMut::new();
bytes.put_u32(1);
bytes.put_u32(MessageType::Call.number());
bytes.put_u32(99);
bytes.put_u32(3);
bytes.put_u32(4);
bytes.put_u32(5);
auth(&[]).encode_into(&mut bytes).expect("cred encodes");
auth(&[]).encode_into(&mut bytes).expect("verifier encodes");
let error = RpcMessage::decode(&bytes).expect_err("unexpected rpc version must fail");
assert_eq!(error, WireError::UnsupportedRpcVersion(99));
}
}