#![cfg_attr(MODULE, allow(dead_code, unreachable_pub))]
#[cfg(MODULE)]
#[path = "wire_types.rs"]
mod wire_types;
#[cfg(not(MODULE))]
pub use crate::wire_types::*;
#[cfg(MODULE)]
pub use wire_types::*;
#[cfg(MODULE)]
#[path = "wire_messages.rs"]
mod wire_messages;
#[cfg(not(MODULE))]
use crate::{wire_messages, wire_types};
#[path = "slice_messages.rs"]
mod messages;
#[cfg_attr(MODULE, allow(unused_imports))]
pub use messages::{
DirectoryEntry, Frame, LockType, Message, Names, QidList, carries_op_id, decode_entry,
decode_frame, encode_entry, encode_frame, encoded_frame_size,
};
pub const MAX_MSIZE: u32 = P9_MAX_MSIZE;
pub const NOTAG: u16 = u16::MAX;
pub const AT_REMOVEDIR: u32 = 0x200;
pub const MAX_NAME_LEN: usize = P9_MAX_NAME_LEN as usize;
pub const LOCK_TYPE_RDLCK: u8 = 0;
pub const LOCK_TYPE_WRLCK: u8 = 1;
pub const LOCK_TYPE_UNLCK: u8 = 2;
pub const LOCK_FLAGS_BLOCK: u32 = 1;
pub const LOCK_SUCCESS: u8 = LockStatus::Success.as_wire();
pub const LOCK_BLOCKED: u8 = LockStatus::Blocked.as_wire();
pub const LOCK_ERROR: u8 = LockStatus::LockError.as_wire();
pub const LOCK_GRACE: u8 = LockStatus::Grace.as_wire();
pub const REBIND_REPLAY: u8 = P9_REBIND_REPLAY;
pub const REBIND_OPENED: u8 = P9_REBIND_OPENED;
pub const REBIND_KNOWN_FLAGS: u8 = P9_REBIND_KNOWN_FLAGS;
pub const REBIND_CREDENTIAL_SENTINEL: u8 = P9_REBIND_CREDENTIAL_SENTINEL;
pub const REBIND_CREDENTIAL_VERSION: u8 = P9_REBIND_CREDENTIAL_VERSION;
pub const REBIND_CREDENTIAL_GROUPS_INCOMPLETE: u8 = P9_REBIND_CREDENTIAL_GROUPS_INCOMPLETE;
pub const REBIND_CREDENTIAL_HEADER_SIZE: usize = P9_REBIND_CREDENTIAL_HEADER_SIZE;
pub const REBIND_CREDENTIAL_MAX_SIZE: usize = P9_REBIND_CREDENTIAL_MAX_SIZE;
pub fn lock_range_end(start: u64, length: u64) -> u64 {
if length == 0 {
u64::MAX
} else {
start.saturating_add(length)
}
}
pub fn subtract_lock_range(
held_start: u64,
held_length: u64,
remove_start: u64,
remove_length: u64,
) -> [Option<(u64, u64)>; 2] {
let held_end = lock_range_end(held_start, held_length);
let remove_end = lock_range_end(remove_start, remove_length);
if held_start >= remove_end || remove_start >= held_end {
return [Some((held_start, held_length)), None];
}
let mut left = None;
if held_start < remove_start {
let left_end = remove_start.min(held_end);
if held_start < left_end {
left = Some((held_start, left_end - held_start));
}
}
let mut right = None;
if remove_end < held_end {
let right_start = remove_end.max(held_start);
if right_start < held_end {
let right_length = if held_end == u64::MAX {
0
} else {
held_end - right_start
};
right = Some((right_start, right_length));
}
}
[left, right]
}
pub const HEADER_SIZE: usize = P9_HEADER_SIZE;
pub const OP_ID_SIZE: usize = P9_OP_ID_LEN;
pub const OP_FLAG_RETRY: u8 = P9_OP_FLAG_RETRY;
pub const OP_KNOWN_FLAGS: u8 = P9_OP_KNOWN_FLAGS;
pub const OP_ENVELOPE_SIZE: usize = P9_OP_ENVELOPE_LEN;
pub const TWRITE_OVERHEAD: usize = HEADER_SIZE + OP_ENVELOPE_SIZE + 4 + 8 + 4;
pub const RLOPENATREAD_OVERHEAD: usize = P9_RLOPENATREAD_HDR as usize;
pub const QID_WIRE_SIZE: usize = Qid::WIRE_SIZE;
pub const STAT_WIRE_SIZE: usize = Stat::WIRE_SIZE;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CodecError {
BufferTooSmall,
MessageTooLarge,
StringTooLong,
TooManyNames,
LengthOverflow,
Truncated,
InvalidFrameSize,
FrameSizeMismatch,
InvalidTag,
TagMismatch {
expected: u16,
actual: u16,
},
UnexpectedMessageType(u8),
TrailingData,
InvalidLockType,
}
impl core::fmt::Display for CodecError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "9P codec error: {self:?}")
}
}
impl core::error::Error for CodecError {}
impl From<core::num::TryFromIntError> for CodecError {
fn from(_: core::num::TryFromIntError) -> Self {
Self::LengthOverflow
}
}
pub fn max_write_payload(msize: u32) -> u32 {
msize.saturating_sub(TWRITE_OVERHEAD as u32)
}
pub fn max_lopenatread_payload(msize: u32) -> u32 {
msize.saturating_sub(RLOPENATREAD_OVERHEAD as u32)
}
#[derive(Clone, Copy)]
pub enum Request<'a> {
Tversion { msize: u32, version: &'a [u8] },
Tgetlineage,
Trebind {
fid: u32,
inode_id: u64,
root_inode: u64,
flags: u8,
uname: &'a [u8],
n_uname: u32,
},
Twalkgetattr {
fid: u32,
newfid: u32,
names: &'a [&'a [u8]],
},
Tgetattr { fid: u32, request_mask: u64 },
Tsetattrattr {
envelope: MutationEnvelope,
fid: u32,
valid: u32,
mode: u32,
uid: u32,
gid: u32,
size: u64,
atime_sec: u64,
atime_nsec: u64,
mtime_sec: u64,
mtime_nsec: u64,
},
Tfallocate {
envelope: MutationEnvelope,
fid: u32,
offset: u64,
length: u64,
mode: u32,
},
Tlopenat { fid: u32, newfid: u32, flags: u32 },
Tlopenatread {
fid: u32,
newfid: u32,
flags: u32,
count: u32,
},
Tlcreateattr {
envelope: MutationEnvelope,
dfid: u32,
newfid: u32,
name: &'a [u8],
flags: u32,
mode: u32,
gid: u32,
},
Tmkdirattr {
envelope: MutationEnvelope,
dfid: u32,
name: &'a [u8],
mode: u32,
gid: u32,
},
Tsymlinkattr {
envelope: MutationEnvelope,
dfid: u32,
name: &'a [u8],
target: &'a [u8],
gid: u32,
},
Tmknodattr {
envelope: MutationEnvelope,
dfid: u32,
name: &'a [u8],
mode: u32,
major: u32,
minor: u32,
gid: u32,
},
Tlinkattr {
envelope: MutationEnvelope,
dfid: u32,
fid: u32,
name: &'a [u8],
},
Trenameat {
envelope: MutationEnvelope,
olddirfid: u32,
oldname: &'a [u8],
newdirfid: u32,
newname: &'a [u8],
},
Tunlinkat {
envelope: MutationEnvelope,
dirfid: u32,
name: &'a [u8],
flags: u32,
},
Treadlink { fid: u32 },
Tflush { oldtag: u16 },
Tread { fid: u32, offset: u64, count: u32 },
Twrite {
envelope: MutationEnvelope,
fid: u32,
offset: u64,
data: &'a [u8],
},
Tfsyncdur { fid: u32, datasync: u32, token: u64 },
Treaddirattr { fid: u32, offset: u64, count: u32 },
Tclunk { fid: u32 },
Tstatfs { fid: u32 },
Tlock {
fid: u32,
lock_type: u8,
flags: u32,
start: u64,
length: u64,
proc_id: u32,
client_id: &'a [u8],
},
Tgetlock {
fid: u32,
lock_type: u8,
start: u64,
length: u64,
proc_id: u32,
client_id: &'a [u8],
},
}
impl Request<'_> {
fn as_message(&self) -> Result<(Message<'_>, MutationEnvelope), CodecError> {
Ok(match *self {
Self::Tversion { msize, version } => (
Message::Tversion { msize, version },
MutationEnvelope::default(),
),
Self::Tgetlineage => (Message::Tgetlineage {}, MutationEnvelope::default()),
Self::Trebind {
fid,
inode_id,
root_inode,
flags,
uname,
n_uname,
} => (
Message::Trebind {
fid,
inode_id,
root_inode,
flags,
uname,
n_uname,
},
MutationEnvelope::default(),
),
Self::Twalkgetattr { fid, newfid, names } => (
Message::Twalkgetattr {
fid,
newfid,
nwname: names
.len()
.try_into()
.map_err(|_| CodecError::TooManyNames)?,
wnames: Names::Slices(names),
},
MutationEnvelope::default(),
),
Self::Tgetattr { fid, request_mask } => (
Message::Tgetattr { fid, request_mask },
MutationEnvelope::default(),
),
Self::Tsetattrattr {
envelope,
fid,
valid,
mode,
uid,
gid,
size,
atime_sec,
atime_nsec,
mtime_sec,
mtime_nsec,
} => (
Message::Tsetattrattr {
fid,
valid,
mode,
uid,
gid,
size,
atime_sec,
atime_nsec,
mtime_sec,
mtime_nsec,
},
envelope,
),
Self::Tfallocate {
envelope,
fid,
offset,
length,
mode,
} => (
Message::Tfallocate {
fid,
offset,
length,
mode,
},
envelope,
),
Self::Tlopenat { fid, newfid, flags } => (
Message::Tlopenat { fid, newfid, flags },
MutationEnvelope::default(),
),
Self::Tlopenatread {
fid,
newfid,
flags,
count,
} => (
Message::Tlopenatread {
fid,
newfid,
flags,
count,
},
MutationEnvelope::default(),
),
Self::Tlcreateattr {
envelope,
dfid,
newfid,
name,
flags,
mode,
gid,
} => (
Message::Tlcreateattr {
dfid,
newfid,
name,
flags,
mode,
gid,
},
envelope,
),
Self::Tmkdirattr {
envelope,
dfid,
name,
mode,
gid,
} => (
Message::Tmkdirattr {
dfid,
name,
mode,
gid,
},
envelope,
),
Self::Tsymlinkattr {
envelope,
dfid,
name,
target,
gid,
} => (
Message::Tsymlinkattr {
dfid,
name,
symtgt: target,
gid,
},
envelope,
),
Self::Tmknodattr {
envelope,
dfid,
name,
mode,
major,
minor,
gid,
} => (
Message::Tmknodattr {
dfid,
name,
mode,
major,
minor,
gid,
},
envelope,
),
Self::Tlinkattr {
envelope,
dfid,
fid,
name,
} => (Message::Tlinkattr { dfid, fid, name }, envelope),
Self::Trenameat {
envelope,
olddirfid,
oldname,
newdirfid,
newname,
} => (
Message::Trenameat {
olddirfid,
oldname,
newdirfid,
newname,
},
envelope,
),
Self::Tunlinkat {
envelope,
dirfid,
name,
flags,
} => (
Message::Tunlinkat {
dirfid,
name,
flags,
},
envelope,
),
Self::Treadlink { fid } => (Message::Treadlink { fid }, MutationEnvelope::default()),
Self::Tflush { oldtag } => (Message::Tflush { oldtag }, MutationEnvelope::default()),
Self::Tread { fid, offset, count } => (
Message::Tread { fid, offset, count },
MutationEnvelope::default(),
),
Self::Twrite {
envelope,
fid,
offset,
data,
} => (
Message::Twrite {
fid,
offset,
count: data
.len()
.try_into()
.map_err(|_| CodecError::MessageTooLarge)?,
data,
},
envelope,
),
Self::Tfsyncdur {
fid,
datasync,
token,
} => (
Message::Tfsyncdur {
fid,
datasync,
token,
},
MutationEnvelope::default(),
),
Self::Treaddirattr { fid, offset, count } => (
Message::Treaddirattr { fid, offset, count },
MutationEnvelope::default(),
),
Self::Tclunk { fid } => (Message::Tclunk { fid }, MutationEnvelope::default()),
Self::Tstatfs { fid } => (Message::Tstatfs { fid }, MutationEnvelope::default()),
Self::Tlock {
fid,
lock_type,
flags,
start,
length,
proc_id,
client_id,
} => (
Message::Tlock {
fid,
lock_type: lock_type.try_into()?,
flags,
start,
length,
proc_id,
client_id,
},
MutationEnvelope::default(),
),
Self::Tgetlock {
fid,
lock_type,
start,
length,
proc_id,
client_id,
} => (
Message::Tgetlock {
fid,
lock_type: lock_type.try_into()?,
start,
length,
proc_id,
client_id,
},
MutationEnvelope::default(),
),
})
}
fn tag_is_valid(&self, tag: u16) -> bool {
matches!(self, Self::Tversion { .. }) == (tag == NOTAG)
}
}
pub fn encode_request(
output: &mut [u8],
max_msize: u32,
tag: u16,
request: Request<'_>,
) -> Result<usize, CodecError> {
if !request.tag_is_valid(tag) {
return Err(CodecError::InvalidTag);
}
let (body, envelope) = request.as_message()?;
encode_frame(output, max_msize, tag, envelope, &body, true)
}
pub fn encoded_request_size(request: &Request<'_>) -> Result<usize, CodecError> {
let (body, _) = request.as_message()?;
encoded_frame_size(&body, true)
}
pub fn encode_twrite_prefix(
output: &mut [u8],
max_msize: u32,
tag: u16,
envelope: MutationEnvelope,
fid: u32,
offset: u64,
data_length: usize,
) -> Result<usize, CodecError> {
if tag == NOTAG {
return Err(CodecError::InvalidTag);
}
if data_length > u32::MAX as usize {
return Err(CodecError::MessageTooLarge);
}
let frame_size = TWRITE_OVERHEAD
.checked_add(data_length)
.ok_or(CodecError::LengthOverflow)?;
if frame_size > u32::MAX as usize || frame_size > max_msize as usize {
return Err(CodecError::MessageTooLarge);
}
let mut writer = Writer::new(output);
writer.put_u32(frame_size as u32)?;
writer.put_u8(message_type::TWRITE)?;
writer.put_u16(tag)?;
put_mutation_envelope(&mut writer, envelope)?;
writer.put_u32(fid)?;
writer.put_u64(offset)?;
writer.put_u32(data_length as u32)?;
Ok(writer.position)
}
pub fn decode_frame_size(prefix: &[u8], max_msize: u32) -> Result<usize, CodecError> {
let mut reader = Reader::new(prefix);
let size = reader.get_u32()?;
if size < HEADER_SIZE as u32 {
return Err(CodecError::InvalidFrameSize);
}
if size > max_msize {
return Err(CodecError::MessageTooLarge);
}
Ok(size as usize)
}
pub fn decode_header(prefix: &[u8], max_msize: u32) -> Result<Header, CodecError> {
let declared_size = decode_frame_size(prefix, max_msize)?;
let mut reader = Reader::new(prefix);
let _size = reader.get_u32()?;
let type_ = reader.get_u8()?;
let tag = reader.get_u16()?;
Ok(Header {
size: declared_size as u32,
type_,
tag,
})
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Header {
pub size: u32,
pub type_: u8,
pub tag: u16,
}
#[derive(Clone, Copy, Debug)]
pub struct DecodedResponse<'a> {
pub header: Header,
pub body: Response<'a>,
}
#[derive(Clone, Copy, Debug)]
pub enum Response<'a> {
Rversion(Rversion<'a>),
Rgetlineage(Rgetlineage),
Rrebind(Rrebind),
Rwalkgetattr(Rwalkgetattr<'a>),
Rgetattr(Rgetattr),
Rsetattrattr(Stat),
Rfallocate,
Rlopen(Rlopen),
Rlopenat(Rlopen),
Rlopenatread(Rlopenatread<'a>),
Rlcreateattr(Rlcreateattr),
Rmkdirattr(Stat),
Rsymlinkattr(Stat),
Rmknodattr(Stat),
Rlinkattr(Stat),
Rrenameat,
Runlinkat,
Rreadlink(Rreadlink<'a>),
Rflush,
Rread(Rread<'a>),
Rwrite(Rwrite),
Rfsync,
Rreaddirattr(Rreaddirattr<'a>),
Rclunk,
Rstatfs(Rstatfs),
Rlock(Rlock),
Rgetlock(Rgetlock<'a>),
Rlerror(Rlerror),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Rversion<'a> {
pub msize: u32,
pub version: WireString<&'a [u8]>,
}
#[derive(Clone, Copy, Debug)]
pub struct Rwalkgetattr<'a> {
pub qids: Qids<'a>,
pub stat: Stat,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Rreadlink<'a> {
pub target: WireString<&'a [u8]>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Rread<'a> {
pub data: WireBytes<&'a [u8]>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Rlopenatread<'a> {
pub qid: Qid,
pub iounit: u32,
pub eof: u8,
pub data: WireBytes<&'a [u8]>,
}
#[derive(Clone, Copy, Debug)]
pub struct Rreaddirattr<'a> {
data: WireBytes<&'a [u8]>,
entry_count: usize,
}
impl<'a> Rreaddirattr<'a> {
pub fn data(&self) -> &'a [u8] {
self.data.0
}
pub fn len(&self) -> usize {
self.entry_count
}
pub fn is_empty(&self) -> bool {
self.entry_count == 0
}
pub fn entries(&self) -> DirEntryPlusIter<'a> {
DirEntryPlusIter {
reader: Reader::new(self.data.0),
remaining: self.entry_count,
}
}
}
pub fn decode_readdirattr_payload(data: &[u8]) -> Result<Rreaddirattr<'_>, CodecError> {
let entry_count = validate_dir_entries(data)?;
Ok(Rreaddirattr {
data: WireBytes::from(data),
entry_count,
})
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Rgetlock<'a> {
pub lock_type: u8,
pub start: u64,
pub length: u64,
pub proc_id: u32,
pub client_id: WireString<&'a [u8]>,
}
pub fn decode_response<'a>(
frame: &'a [u8],
max_msize: u32,
expected_tag: u16,
) -> Result<DecodedResponse<'a>, CodecError> {
let header = decode_header(frame, max_msize)?;
if header.size as usize != frame.len() {
return Err(CodecError::FrameSizeMismatch);
}
if header.tag != expected_tag {
return Err(CodecError::TagMismatch {
expected: expected_tag,
actual: header.tag,
});
}
let frame = decode_frame(frame, max_msize, false)?;
let body = match frame.body {
Message::Rgetlineage {
token,
writer_epoch,
} => Response::Rgetlineage(Rgetlineage {
token,
writer_epoch,
}),
Message::Rrebind { qid } => Response::Rrebind(Rrebind { qid }),
Message::Rgetattr { valid, stat } => Response::Rgetattr(Rgetattr { valid, stat }),
Message::Rlcreateattr { iounit, stat } => {
Response::Rlcreateattr(Rlcreateattr { iounit, stat })
}
Message::Rwrite { count } => Response::Rwrite(Rwrite { count }),
Message::Rstatfs {
r#type,
bsize,
blocks,
bfree,
bavail,
files,
ffree,
fsid,
namelen,
} => Response::Rstatfs(Rstatfs {
r#type,
bsize,
blocks,
bfree,
bavail,
files,
ffree,
fsid,
namelen,
}),
Message::Rlock { status } => Response::Rlock(Rlock { status }),
Message::Rlerror { ecode } => Response::Rlerror(Rlerror { ecode }),
Message::Rsetattrattr { stat } => Response::Rsetattrattr(stat),
Message::Rmkdirattr { stat } => Response::Rmkdirattr(stat),
Message::Rsymlinkattr { stat } => Response::Rsymlinkattr(stat),
Message::Rmknodattr { stat } => Response::Rmknodattr(stat),
Message::Rlinkattr { stat } => Response::Rlinkattr(stat),
Message::Rfallocate {} => Response::Rfallocate,
Message::Rrenameat {} => Response::Rrenameat,
Message::Runlinkat {} => Response::Runlinkat,
Message::Rflush {} => Response::Rflush,
Message::Rfsync {} => Response::Rfsync,
Message::Rclunk {} => Response::Rclunk,
Message::Rlopen { qid, iounit } => Response::Rlopen(Rlopen { qid, iounit }),
Message::Rlopenat { qid, iounit } => Response::Rlopenat(Rlopen { qid, iounit }),
Message::Rversion { msize, version } => Response::Rversion(Rversion {
msize,
version: WireString::from_storage(version),
}),
Message::Rwalkgetattr {
wqids: QidList::Encoded(bytes),
nwqid: count,
stat,
} => Response::Rwalkgetattr(Rwalkgetattr {
qids: Qids { bytes, count },
stat,
}),
Message::Rreadlink { target } => Response::Rreadlink(Rreadlink {
target: WireString::from_storage(target),
}),
Message::Rread { data, .. } => Response::Rread(Rread {
data: WireBytes::from(data),
}),
Message::Rlopenatread {
qid,
iounit,
eof,
data,
..
} => Response::Rlopenatread(Rlopenatread {
qid,
iounit,
eof,
data: WireBytes::from(data),
}),
Message::Rreaddirattr { data, .. } => {
Response::Rreaddirattr(decode_readdirattr_payload(data)?)
}
Message::Rgetlock {
lock_type,
start,
length,
proc_id,
client_id,
} => Response::Rgetlock(Rgetlock {
lock_type: lock_type as u8,
start,
length,
proc_id,
client_id: WireString::from_storage(client_id),
}),
other => return Err(CodecError::UnexpectedMessageType(other.type_id())),
};
Ok(DecodedResponse {
header: frame.header,
body,
})
}
#[derive(Clone, Copy, Debug)]
pub struct Qids<'a> {
bytes: &'a [u8],
count: u16,
}
impl<'a> Qids<'a> {
pub fn len(&self) -> usize {
self.count as usize
}
pub fn is_empty(&self) -> bool {
self.count == 0
}
pub fn get(&self, index: usize) -> Option<Qid> {
if index >= self.len() {
return None;
}
let start = index.checked_mul(QID_WIRE_SIZE)?;
let end = start.checked_add(QID_WIRE_SIZE)?;
let bytes = self.bytes.get(start..end)?;
let mut reader = Reader::new(bytes);
decode_qid(&mut reader).ok()
}
pub fn iter(&self) -> QidIter<'a> {
QidIter {
qids: *self,
index: 0,
}
}
}
pub struct QidIter<'a> {
qids: Qids<'a>,
index: usize,
}
impl Iterator for QidIter<'_> {
type Item = Qid;
fn next(&mut self) -> Option<Self::Item> {
let qid = self.qids.get(self.index)?;
self.index = self.index.saturating_add(1);
Some(qid)
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.qids.len().saturating_sub(self.index);
(remaining, Some(remaining))
}
}
impl ExactSizeIterator for QidIter<'_> {}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct DirEntryPlus<'a> {
pub qid: Qid,
pub offset: u64,
pub type_: u8,
pub name: &'a [u8],
pub stat: Stat,
}
pub struct DirEntryPlusIter<'a> {
reader: Reader<'a>,
remaining: usize,
}
impl<'a> Iterator for DirEntryPlusIter<'a> {
type Item = Result<DirEntryPlus<'a>, CodecError>;
fn next(&mut self) -> Option<Self::Item> {
if self.remaining == 0 {
return None;
}
self.remaining -= 1;
Some(decode_dir_entry_plus(&mut self.reader))
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.remaining, Some(self.remaining))
}
}
impl ExactSizeIterator for DirEntryPlusIter<'_> {}
fn validate_dir_entries(data: &[u8]) -> Result<usize, CodecError> {
let mut reader = Reader::new(data);
let mut count = 0usize;
while reader.remaining() != 0 {
let _entry = decode_dir_entry_plus(&mut reader)?;
count = count.checked_add(1).ok_or(CodecError::LengthOverflow)?;
}
Ok(count)
}
fn put_mutation_envelope(
writer: &mut Writer<'_>,
envelope: MutationEnvelope,
) -> Result<(), CodecError> {
writer.put(&envelope.op_id)?;
writer.put_u8(envelope.flags)?;
writer.put_u64(envelope.origin_writer_epoch)
}
fn string_size(value: &[u8]) -> Result<usize, CodecError> {
if value.len() > u16::MAX as usize {
return Err(CodecError::StringTooLong);
}
2usize
.checked_add(value.len())
.ok_or(CodecError::LengthOverflow)
}
fn decode_dir_entry_plus<'a>(reader: &mut Reader<'a>) -> Result<DirEntryPlus<'a>, CodecError> {
let (entry, length) = decode_entry(&reader.input[reader.position..], true)?;
reader.take(length)?;
Ok(DirEntryPlus {
qid: entry.qid,
offset: entry.offset,
type_: entry.type_,
name: entry.name,
stat: entry.stat.ok_or(CodecError::Truncated)?,
})
}
fn decode_qid(reader: &mut Reader<'_>) -> Result<Qid, CodecError> {
<Qid as messages::Fixed>::read(reader)
}
struct Writer<'a> {
output: &'a mut [u8],
position: usize,
}
impl<'a> Writer<'a> {
fn new(output: &'a mut [u8]) -> Self {
Self {
output,
position: 0,
}
}
fn put(&mut self, bytes: &[u8]) -> Result<(), CodecError> {
let end = self
.position
.checked_add(bytes.len())
.ok_or(CodecError::LengthOverflow)?;
let destination = self
.output
.get_mut(self.position..end)
.ok_or(CodecError::BufferTooSmall)?;
destination.copy_from_slice(bytes);
self.position = end;
Ok(())
}
fn put_u8(&mut self, value: u8) -> Result<(), CodecError> {
self.put(&[value])
}
fn put_u16(&mut self, value: u16) -> Result<(), CodecError> {
self.put(&value.to_le_bytes())
}
fn put_u32(&mut self, value: u32) -> Result<(), CodecError> {
self.put(&value.to_le_bytes())
}
fn put_u64(&mut self, value: u64) -> Result<(), CodecError> {
self.put(&value.to_le_bytes())
}
fn put_string(&mut self, value: &[u8]) -> Result<(), CodecError> {
if value.len() > u16::MAX as usize {
return Err(CodecError::StringTooLong);
}
self.put_u16(value.len() as u16)?;
self.put(value)
}
}
#[derive(Clone, Copy, Debug)]
struct Reader<'a> {
input: &'a [u8],
position: usize,
}
impl<'a> Reader<'a> {
fn new(input: &'a [u8]) -> Self {
Self { input, position: 0 }
}
fn remaining(&self) -> usize {
self.input.len().saturating_sub(self.position)
}
fn take(&mut self, count: usize) -> Result<&'a [u8], CodecError> {
let end = self
.position
.checked_add(count)
.ok_or(CodecError::LengthOverflow)?;
let bytes = self
.input
.get(self.position..end)
.ok_or(CodecError::Truncated)?;
self.position = end;
Ok(bytes)
}
fn get_u8(&mut self) -> Result<u8, CodecError> {
match self.take(1)? {
[value] => Ok(*value),
_ => Err(CodecError::Truncated),
}
}
fn get_u16(&mut self) -> Result<u16, CodecError> {
match self.take(2)? {
[a, b] => Ok(u16::from_le_bytes([*a, *b])),
_ => Err(CodecError::Truncated),
}
}
fn get_u32(&mut self) -> Result<u32, CodecError> {
match self.take(4)? {
[a, b, c, d] => Ok(u32::from_le_bytes([*a, *b, *c, *d])),
_ => Err(CodecError::Truncated),
}
}
fn get_u64(&mut self) -> Result<u64, CodecError> {
match self.take(8)? {
[a, b, c, d, e, f, g, h] => Ok(u64::from_le_bytes([*a, *b, *c, *d, *e, *f, *g, *h])),
_ => Err(CodecError::Truncated),
}
}
fn get_string(&mut self) -> Result<&'a [u8], CodecError> {
let length = self.get_u16()? as usize;
self.take(length)
}
fn require_end(&self) -> Result<(), CodecError> {
if self.remaining() == 0 {
Ok(())
} else {
Err(CodecError::TrailingData)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(not(MODULE))]
use std::{vec, vec::Vec};
const TEST_MSIZE: u32 = 4096;
fn put_header(frame: &mut [u8], type_: u8, tag: u16) {
let size = frame.len() as u32;
frame[0..4].copy_from_slice(&size.to_le_bytes());
frame[4] = type_;
frame[5..7].copy_from_slice(&tag.to_le_bytes());
}
fn put_qid(writer: &mut Writer<'_>, qid: Qid) {
writer.put_u8(qid.type_).unwrap();
writer.put_u32(qid.version).unwrap();
writer.put_u64(qid.path).unwrap();
}
fn put_stat(writer: &mut Writer<'_>, stat: Stat) {
put_qid(writer, stat.qid);
writer.put_u32(stat.mode).unwrap();
writer.put_u32(stat.uid).unwrap();
writer.put_u32(stat.gid).unwrap();
writer.put_u64(stat.nlink).unwrap();
writer.put_u64(stat.rdev).unwrap();
writer.put_u64(stat.size).unwrap();
writer.put_u64(stat.blksize).unwrap();
writer.put_u64(stat.blocks).unwrap();
writer.put_u64(stat.atime_sec).unwrap();
writer.put_u64(stat.atime_nsec).unwrap();
writer.put_u64(stat.mtime_sec).unwrap();
writer.put_u64(stat.mtime_nsec).unwrap();
writer.put_u64(stat.ctime_sec).unwrap();
writer.put_u64(stat.ctime_nsec).unwrap();
writer.put_u64(stat.btime_sec).unwrap();
writer.put_u64(stat.btime_nsec).unwrap();
writer.put_u64(stat.r#gen).unwrap();
writer.put_u64(stat.data_version).unwrap();
}
fn mutation_envelope() -> MutationEnvelope {
MutationEnvelope {
op_id: [
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
0x0e, 0x0f,
],
flags: OP_FLAG_RETRY,
origin_writer_epoch: 0x0807_0605_0403_0201,
}
}
fn encode_test_request(request: Request<'_>, tag: u16) -> Vec<u8> {
let size = encoded_request_size(&request).unwrap();
let mut frame = vec![0u8; size];
assert_eq!(
encode_request(&mut frame, TEST_MSIZE, tag, request),
Ok(size)
);
frame
}
fn assert_mutation_prefix(frame: &[u8], type_: u8, tag: u16) {
assert_eq!(frame[4], type_);
assert_eq!(&frame[5..7], &tag.to_le_bytes());
assert_eq!(&frame[7..23], &mutation_envelope().op_id);
assert_eq!(frame[23], OP_FLAG_RETRY);
assert_eq!(
&frame[24..32],
&mutation_envelope().origin_writer_epoch.to_le_bytes()
);
}
#[test]
fn malformed_borrowed_lists_cannot_be_encoded() {
let walk = Message::Twalk {
fid: 0,
newfid: 1,
nwname: 1,
wnames: Names::Encoded {
bytes: &[1, 0, b'a', b'b'],
count: 1,
},
};
assert_eq!(walk.body_size(), Err(CodecError::TrailingData));
let walk = Message::Rwalk {
nwqid: 1,
wqids: QidList::Encoded(&[0; 14]),
};
assert_eq!(walk.body_size(), Err(CodecError::Truncated));
}
#[test]
fn tversion_matches_the_9p_layout() {
let mut output = [0u8; 64];
let request = Request::Tversion {
msize: TEST_MSIZE,
version: VERSION_9P2000L_ZEROFS,
};
assert_eq!(encoded_request_size(&request), Ok(23));
let length = encode_request(&mut output, TEST_MSIZE, NOTAG, request).unwrap();
let expected = [
23,
0,
0,
0,
message_type::TVERSION,
0xff,
0xff,
0x00,
0x10,
0x00,
0x00,
10,
0,
b'9',
b'P',
b'2',
b'0',
b'0',
b'0',
b'.',
b'L',
b'.',
b'Z',
];
assert_eq!(&output[..length], &expected);
}
#[test]
fn tlopenatread_matches_the_compound_open_layout() {
let request = Request::Tlopenatread {
fid: 0x1122_3344,
newfid: 0x5566_7788,
flags: 0x99aa_bbcc,
count: 0xddee_ff00,
};
assert_eq!(encoded_request_size(&request), Ok(HEADER_SIZE + 4 * 4));
let mut output = [0u8; HEADER_SIZE + 4 * 4];
let length = encode_request(&mut output, TEST_MSIZE, 0x1234, request).unwrap();
let expected = [
23,
0,
0,
0,
message_type::TLOPENATREAD,
0x34,
0x12,
0x44,
0x33,
0x22,
0x11,
0x88,
0x77,
0x66,
0x55,
0xcc,
0xbb,
0xaa,
0x99,
0x00,
0xff,
0xee,
0xdd,
];
assert_eq!(length, expected.len());
assert_eq!(output, expected);
let request_size = output.len();
assert_eq!(
encode_request(&mut output, (request_size - 1) as u32, 0x1234, request),
Err(CodecError::MessageTooLarge)
);
assert_eq!(
encode_request(&mut output[..request_size - 1], TEST_MSIZE, 0x1234, request),
Err(CodecError::BufferTooSmall)
);
assert_eq!(
encode_request(&mut output, TEST_MSIZE, NOTAG, request),
Err(CodecError::InvalidTag)
);
}
#[test]
fn decodes_and_bounds_lopenatread_payloads() {
assert_eq!(
RLOPENATREAD_OVERHEAD,
HEADER_SIZE + QID_WIRE_SIZE + 4 + 1 + 4
);
let tag = 0x1234;
let qid = Qid {
type_: QID_TYPE_FILE,
version: 0x5566_7788,
path: 0x0102_0304_0506_0708,
};
let mut frame = vec![0u8; RLOPENATREAD_OVERHEAD + 3];
put_header(&mut frame, message_type::RLOPENATREAD, tag);
let mut writer = Writer::new(&mut frame[HEADER_SIZE..]);
put_qid(&mut writer, qid);
writer.put_u32(8192).unwrap();
writer.put_u8(1).unwrap();
writer.put_u32(3).unwrap();
writer.put(b"abc").unwrap();
match decode_response(&frame, frame.len() as u32, tag)
.unwrap()
.body
{
Response::Rlopenatread(reply) => {
assert_eq!(reply.qid, qid);
assert_eq!(reply.iounit, 8192);
assert_eq!(reply.eof, 1);
assert_eq!(reply.data.as_ref(), b"abc");
assert_eq!(
reply.data.as_ref().as_ptr(),
frame[RLOPENATREAD_OVERHEAD..].as_ptr()
);
}
_ => panic!("wrong response"),
}
assert!(matches!(
decode_response(&frame, frame.len() as u32 - 1, tag),
Err(CodecError::MessageTooLarge)
));
assert_eq!(max_lopenatread_payload(frame.len() as u32), 3);
assert_eq!(max_lopenatread_payload(RLOPENATREAD_OVERHEAD as u32 - 1), 0);
let mut fixed_prefix_truncated = vec![0u8; RLOPENATREAD_OVERHEAD - 1];
put_header(&mut fixed_prefix_truncated, message_type::RLOPENATREAD, tag);
assert!(matches!(
decode_response(&fixed_prefix_truncated, TEST_MSIZE, tag),
Err(CodecError::Truncated)
));
let count_offset = RLOPENATREAD_OVERHEAD - 4;
let mut count_overrun = frame.clone();
count_overrun[count_offset..RLOPENATREAD_OVERHEAD].copy_from_slice(&4u32.to_le_bytes());
assert!(matches!(
decode_response(&count_overrun, TEST_MSIZE, tag),
Err(CodecError::Truncated)
));
let mut maximum_count = frame.clone();
maximum_count[count_offset..RLOPENATREAD_OVERHEAD].copy_from_slice(&u32::MAX.to_le_bytes());
assert!(matches!(
decode_response(&maximum_count, TEST_MSIZE, tag),
Err(CodecError::Truncated)
));
let mut trailing = frame;
trailing[count_offset..RLOPENATREAD_OVERHEAD].copy_from_slice(&2u32.to_le_bytes());
assert!(matches!(
decode_response(&trailing, TEST_MSIZE, tag),
Err(CodecError::TrailingData)
));
}
#[test]
fn twrite_matches_the_canonical_mutation_layout() {
let mut output = [0u8; TWRITE_OVERHEAD + 3];
let envelope = MutationEnvelope {
op_id: [
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
0x0e, 0x0f,
],
flags: OP_FLAG_RETRY,
origin_writer_epoch: 0x0807_0605_0403_0201,
};
let request = Request::Twrite {
envelope,
fid: 0x1122_3344,
offset: 0x0102_0304_0506_0708,
data: b"abc",
};
assert_eq!(encoded_request_size(&request), Ok(TWRITE_OVERHEAD + 3));
let length = encode_request(&mut output, TEST_MSIZE, 0x1234, request).unwrap();
let expected = [
51,
0,
0,
0,
message_type::TWRITE,
0x34,
0x12,
0x00,
0x01,
0x02,
0x03,
0x04,
0x05,
0x06,
0x07,
0x08,
0x09,
0x0a,
0x0b,
0x0c,
0x0d,
0x0e,
0x0f,
OP_FLAG_RETRY,
0x01,
0x02,
0x03,
0x04,
0x05,
0x06,
0x07,
0x08,
0x44,
0x33,
0x22,
0x11,
0x08,
0x07,
0x06,
0x05,
0x04,
0x03,
0x02,
0x01,
3,
0,
0,
0,
b'a',
b'b',
b'c',
];
assert_eq!(length, expected.len());
assert_eq!(output, expected);
let mut prefix = [0u8; TWRITE_OVERHEAD];
assert_eq!(
encode_twrite_prefix(
&mut prefix,
TEST_MSIZE,
0x1234,
envelope,
0x1122_3344,
0x0102_0304_0506_0708,
3,
),
Ok(TWRITE_OVERHEAD)
);
assert_eq!(prefix, expected[..TWRITE_OVERHEAD]);
}
#[test]
fn twrite_payload_is_bounded_by_the_enveloped_msize() {
let maximum = max_write_payload(TEST_MSIZE) as usize;
assert_eq!(maximum, TEST_MSIZE as usize - TWRITE_OVERHEAD);
let payload = vec![0xa5; maximum];
let request = Request::Twrite {
envelope: MutationEnvelope {
op_id: [7; OP_ID_SIZE],
flags: 0,
origin_writer_epoch: 42,
},
fid: 9,
offset: 11,
data: &payload,
};
assert_eq!(encoded_request_size(&request), Ok(TEST_MSIZE as usize));
let mut output = vec![0; TEST_MSIZE as usize];
assert_eq!(
encode_request(&mut output, TEST_MSIZE, 5, request),
Ok(TEST_MSIZE as usize)
);
assert_eq!(
&output[TWRITE_OVERHEAD - 4..TWRITE_OVERHEAD],
&(maximum as u32).to_le_bytes()
);
assert_eq!(&output[TWRITE_OVERHEAD..], payload.as_slice());
let oversized = vec![0x5a; maximum + 1];
let request = Request::Twrite {
envelope: MutationEnvelope::default(),
fid: 9,
offset: 11,
data: &oversized,
};
assert_eq!(encoded_request_size(&request), Ok(TEST_MSIZE as usize + 1));
let mut output = vec![0; TEST_MSIZE as usize + 1];
assert_eq!(
encode_request(&mut output, TEST_MSIZE, 5, request),
Err(CodecError::MessageTooLarge)
);
}
#[test]
fn setattrattr_and_fallocate_match_canonical_mutation_layouts() {
let setattr = encode_test_request(
Request::Tsetattrattr {
envelope: mutation_envelope(),
fid: 0x1122_3344,
valid: SETATTR_MODE | SETATTR_SIZE | SETATTR_MTIME | SETATTR_MTIME_SET,
mode: 0o100640,
uid: 1000,
gid: 1001,
size: 0x0102_0304_0506_0708,
atime_sec: 11,
atime_nsec: 12,
mtime_sec: 13,
mtime_nsec: 14,
},
0x1234,
);
assert_eq!(setattr.len(), 92);
assert_mutation_prefix(&setattr, message_type::TSETATTRATTR, 0x1234);
let mut body = Reader::new(&setattr[HEADER_SIZE + OP_ENVELOPE_SIZE..]);
assert_eq!(body.get_u32(), Ok(0x1122_3344));
assert_eq!(
body.get_u32(),
Ok(SETATTR_MODE | SETATTR_SIZE | SETATTR_MTIME | SETATTR_MTIME_SET)
);
assert_eq!(body.get_u32(), Ok(0o100640));
assert_eq!(body.get_u32(), Ok(1000));
assert_eq!(body.get_u32(), Ok(1001));
assert_eq!(body.get_u64(), Ok(0x0102_0304_0506_0708));
assert_eq!(body.get_u64(), Ok(11));
assert_eq!(body.get_u64(), Ok(12));
assert_eq!(body.get_u64(), Ok(13));
assert_eq!(body.get_u64(), Ok(14));
assert_eq!(body.require_end(), Ok(()));
let fallocate = encode_test_request(
Request::Tfallocate {
envelope: mutation_envelope(),
fid: 9,
offset: 10,
length: 11,
mode: FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
},
7,
);
assert_eq!(fallocate.len(), 56);
assert_mutation_prefix(&fallocate, message_type::TFALLOCATE, 7);
let mut body = Reader::new(&fallocate[HEADER_SIZE + OP_ENVELOPE_SIZE..]);
assert_eq!(body.get_u32(), Ok(9));
assert_eq!(body.get_u64(), Ok(10));
assert_eq!(body.get_u64(), Ok(11));
assert_eq!(
body.get_u32(),
Ok(FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE)
);
assert_eq!(body.require_end(), Ok(()));
}
#[test]
fn namespace_requests_match_canonical_compound_layouts() {
let tag = 23;
let create = encode_test_request(
Request::Tlcreateattr {
envelope: mutation_envelope(),
dfid: 1,
newfid: 2,
name: b"f",
flags: 3,
mode: 0o100640,
gid: 4,
},
tag,
);
assert_eq!(create.len(), 55);
assert_mutation_prefix(&create, message_type::TLCREATEATTR, tag);
let mut body = Reader::new(&create[HEADER_SIZE + OP_ENVELOPE_SIZE..]);
assert_eq!(body.get_u32(), Ok(1));
assert_eq!(body.get_u32(), Ok(2));
assert_eq!(body.get_string(), Ok(b"f".as_slice()));
assert_eq!(body.get_u32(), Ok(3));
assert_eq!(body.get_u32(), Ok(0o100640));
assert_eq!(body.get_u32(), Ok(4));
assert_eq!(body.require_end(), Ok(()));
let mkdir = encode_test_request(
Request::Tmkdirattr {
envelope: mutation_envelope(),
dfid: 5,
name: b"d",
mode: 0o40750,
gid: 6,
},
tag,
);
assert_eq!(mkdir.len(), 47);
assert_mutation_prefix(&mkdir, message_type::TMKDIRATTR, tag);
let mut body = Reader::new(&mkdir[HEADER_SIZE + OP_ENVELOPE_SIZE..]);
assert_eq!(body.get_u32(), Ok(5));
assert_eq!(body.get_string(), Ok(b"d".as_slice()));
assert_eq!(body.get_u32(), Ok(0o40750));
assert_eq!(body.get_u32(), Ok(6));
assert_eq!(body.require_end(), Ok(()));
let symlink = encode_test_request(
Request::Tsymlinkattr {
envelope: mutation_envelope(),
dfid: 7,
name: b"s",
target: b"to",
gid: 8,
},
tag,
);
assert_eq!(symlink.len(), 47);
assert_mutation_prefix(&symlink, message_type::TSYMLINKATTR, tag);
let mut body = Reader::new(&symlink[HEADER_SIZE + OP_ENVELOPE_SIZE..]);
assert_eq!(body.get_u32(), Ok(7));
assert_eq!(body.get_string(), Ok(b"s".as_slice()));
assert_eq!(body.get_string(), Ok(b"to".as_slice()));
assert_eq!(body.get_u32(), Ok(8));
assert_eq!(body.require_end(), Ok(()));
let mknod = encode_test_request(
Request::Tmknodattr {
envelope: mutation_envelope(),
dfid: 9,
name: b"p",
mode: 0o10640,
major: 10,
minor: 11,
gid: 12,
},
tag,
);
assert_eq!(mknod.len(), 55);
assert_mutation_prefix(&mknod, message_type::TMKNODATTR, tag);
let mut body = Reader::new(&mknod[HEADER_SIZE + OP_ENVELOPE_SIZE..]);
assert_eq!(body.get_u32(), Ok(9));
assert_eq!(body.get_string(), Ok(b"p".as_slice()));
assert_eq!(body.get_u32(), Ok(0o10640));
assert_eq!(body.get_u32(), Ok(10));
assert_eq!(body.get_u32(), Ok(11));
assert_eq!(body.get_u32(), Ok(12));
assert_eq!(body.require_end(), Ok(()));
let link = encode_test_request(
Request::Tlinkattr {
envelope: mutation_envelope(),
dfid: 13,
fid: 14,
name: b"h",
},
tag,
);
assert_eq!(link.len(), 43);
assert_mutation_prefix(&link, message_type::TLINKATTR, tag);
let mut body = Reader::new(&link[HEADER_SIZE + OP_ENVELOPE_SIZE..]);
assert_eq!(body.get_u32(), Ok(13));
assert_eq!(body.get_u32(), Ok(14));
assert_eq!(body.get_string(), Ok(b"h".as_slice()));
assert_eq!(body.require_end(), Ok(()));
let rename = encode_test_request(
Request::Trenameat {
envelope: mutation_envelope(),
olddirfid: 15,
oldname: b"a",
newdirfid: 16,
newname: b"b",
},
tag,
);
assert_eq!(rename.len(), 46);
assert_mutation_prefix(&rename, message_type::TRENAMEAT, tag);
let mut body = Reader::new(&rename[HEADER_SIZE + OP_ENVELOPE_SIZE..]);
assert_eq!(body.get_u32(), Ok(15));
assert_eq!(body.get_string(), Ok(b"a".as_slice()));
assert_eq!(body.get_u32(), Ok(16));
assert_eq!(body.get_string(), Ok(b"b".as_slice()));
assert_eq!(body.require_end(), Ok(()));
let unlink = encode_test_request(
Request::Tunlinkat {
envelope: mutation_envelope(),
dirfid: 17,
name: b"x",
flags: AT_REMOVEDIR,
},
tag,
);
assert_eq!(unlink.len(), 43);
assert_mutation_prefix(&unlink, message_type::TUNLINKAT, tag);
let mut body = Reader::new(&unlink[HEADER_SIZE + OP_ENVELOPE_SIZE..]);
assert_eq!(body.get_u32(), Ok(17));
assert_eq!(body.get_string(), Ok(b"x".as_slice()));
assert_eq!(body.get_u32(), Ok(AT_REMOVEDIR));
assert_eq!(body.require_end(), Ok(()));
let readlink = encode_test_request(Request::Treadlink { fid: 18 }, tag);
assert_eq!(readlink.len(), HEADER_SIZE + 4);
assert_eq!(readlink[4], message_type::TREADLINK);
assert_eq!(&readlink[HEADER_SIZE..], &18u32.to_le_bytes());
}
#[test]
fn fixed_and_variable_requests_have_exact_sizes() {
let mut output = [0u8; 128];
let walk = Request::Twalkgetattr {
fid: 1,
newfid: 2,
names: &[b"dir", b"file"],
};
let walk_size = HEADER_SIZE + 4 + 4 + 2 + 2 + 3 + 2 + 4;
assert_eq!(encoded_request_size(&walk), Ok(walk_size));
let length = encode_request(&mut output, TEST_MSIZE, 5, walk).unwrap();
assert_eq!(length, walk_size);
assert_eq!(output[4], message_type::TWALKGETATTR);
assert_eq!(&output[7..11], &1u32.to_le_bytes());
assert_eq!(&output[11..15], &2u32.to_le_bytes());
assert_eq!(&output[15..17], &2u16.to_le_bytes());
assert_eq!(&output[17..22], &[3, 0, b'd', b'i', b'r']);
assert_eq!(&output[22..28], &[4, 0, b'f', b'i', b'l', b'e']);
let rebind = Request::Trebind {
fid: 3,
inode_id: 4,
root_inode: 5,
flags: 1,
uname: b"u",
n_uname: 1000,
};
let rebind_size = HEADER_SIZE + 4 + 8 + 8 + 1 + 2 + 1 + 4;
assert_eq!(encoded_request_size(&rebind), Ok(rebind_size));
let length = encode_request(&mut output, TEST_MSIZE, 6, rebind).unwrap();
assert_eq!(length, rebind_size);
assert_eq!(output[4], message_type::TREBIND);
let cases = [
(Request::Tgetlineage, message_type::TGETLINEAGE, HEADER_SIZE),
(
Request::Tgetattr {
fid: 1,
request_mask: 2,
},
message_type::TGETATTR,
HEADER_SIZE + 12,
),
(
Request::Tlopenat {
fid: 1,
newfid: 2,
flags: 3,
},
message_type::TLOPENAT,
HEADER_SIZE + 12,
),
(
Request::Tread {
fid: 1,
offset: 2,
count: 3,
},
message_type::TREAD,
HEADER_SIZE + 16,
),
(
Request::Tfsyncdur {
fid: 1,
datasync: 1,
token: 2,
},
message_type::TFSYNCDUR,
HEADER_SIZE + 16,
),
(
Request::Treaddirattr {
fid: 1,
offset: 2,
count: 3,
},
message_type::TREADDIRATTR,
HEADER_SIZE + 16,
),
(
Request::Tclunk { fid: 1 },
message_type::TCLUNK,
HEADER_SIZE + 4,
),
(
Request::Tstatfs { fid: 1 },
message_type::TSTATFS,
HEADER_SIZE + 4,
),
];
for (request, type_, expected_length) in cases {
assert_eq!(encoded_request_size(&request), Ok(expected_length));
let length = encode_request(&mut output, TEST_MSIZE, 7, request).unwrap();
assert_eq!(length, expected_length);
assert_eq!(output[4], type_);
}
}
#[test]
fn flush_matches_the_standard_9p_layout() {
let tag = 0x1234;
let oldtag = 0xabcd;
let request = Request::Tflush { oldtag };
assert_eq!(encoded_request_size(&request), Ok(HEADER_SIZE + 2));
let frame = encode_test_request(request, tag);
assert_eq!(frame.len(), HEADER_SIZE + 2);
assert_eq!(frame[4], message_type::TFLUSH);
assert_eq!(&frame[5..7], &tag.to_le_bytes());
assert_eq!(&frame[HEADER_SIZE..], &oldtag.to_le_bytes());
let mut response = [0u8; HEADER_SIZE];
put_header(&mut response, message_type::RFLUSH, tag);
assert!(matches!(
decode_response(&response, TEST_MSIZE, tag).unwrap().body,
Response::Rflush
));
let mut trailing = [0u8; HEADER_SIZE + 1];
put_header(&mut trailing, message_type::RFLUSH, tag);
assert!(matches!(
decode_response(&trailing, TEST_MSIZE, tag),
Err(CodecError::TrailingData)
));
}
#[test]
fn lock_requests_match_the_9p_layout() {
assert_eq!(
[LOCK_TYPE_RDLCK, LOCK_TYPE_WRLCK, LOCK_TYPE_UNLCK],
[0, 1, 2]
);
assert_eq!(
[LOCK_SUCCESS, LOCK_BLOCKED, LOCK_ERROR, LOCK_GRACE],
[0, 1, 2, 3]
);
assert_eq!(LOCK_FLAGS_BLOCK, 1);
let lock = Request::Tlock {
fid: 0x1122_3344,
lock_type: LOCK_TYPE_WRLCK,
flags: LOCK_FLAGS_BLOCK,
start: 0x0102_0304_0506_0708,
length: 4096,
proc_id: 7,
client_id: b"id",
};
assert_eq!(encoded_request_size(&lock), Ok(HEADER_SIZE + 31 + 2));
let frame = encode_test_request(lock, 0x1234);
let expected = [
40,
0,
0,
0,
message_type::TLOCK,
0x34,
0x12,
0x44,
0x33,
0x22,
0x11,
1,
1,
0,
0,
0,
0x08,
0x07,
0x06,
0x05,
0x04,
0x03,
0x02,
0x01,
0x00,
0x10,
0,
0,
0,
0,
0,
0,
7,
0,
0,
0,
2,
0,
b'i',
b'd',
];
assert_eq!(frame.as_slice(), expected.as_slice());
let getlock = Request::Tgetlock {
fid: 9,
lock_type: LOCK_TYPE_RDLCK,
start: 10,
length: 11,
proc_id: 12,
client_id: b"peer",
};
assert_eq!(encoded_request_size(&getlock), Ok(HEADER_SIZE + 27 + 4));
let frame = encode_test_request(getlock, 5);
assert_eq!(frame.len(), HEADER_SIZE + 27 + 4);
assert_eq!(frame[4], message_type::TGETLOCK);
assert_eq!(&frame[5..7], &5u16.to_le_bytes());
let mut body = Reader::new(&frame[HEADER_SIZE..]);
assert_eq!(body.get_u32(), Ok(9));
assert_eq!(body.get_u8(), Ok(LOCK_TYPE_RDLCK));
assert_eq!(body.get_u64(), Ok(10));
assert_eq!(body.get_u64(), Ok(11));
assert_eq!(body.get_u32(), Ok(12));
assert_eq!(body.get_string(), Ok(b"peer".as_slice()));
assert_eq!(body.require_end(), Ok(()));
let anonymous = Request::Tgetlock {
fid: 9,
lock_type: LOCK_TYPE_UNLCK,
start: 0,
length: 0,
proc_id: 0,
client_id: b"",
};
assert_eq!(encoded_request_size(&anonymous), Ok(HEADER_SIZE + 27));
assert_eq!(encode_test_request(anonymous, 5).len(), HEADER_SIZE + 27);
}
#[test]
fn lock_range_subtraction_matches_the_owned_codec() {
assert_eq!(lock_range_end(10, 0), u64::MAX);
assert_eq!(lock_range_end(10, 5), 15);
assert_eq!(lock_range_end(u64::MAX, 10), u64::MAX);
assert_eq!(
subtract_lock_range(10, 90, 30, 20),
[Some((10, 20)), Some((50, 50))]
);
assert_eq!(
subtract_lock_range(10, 0, 30, 20),
[Some((10, 20)), Some((50, 0))]
);
assert_eq!(subtract_lock_range(10, 0, 30, 0), [Some((10, 20)), None]);
assert_eq!(subtract_lock_range(10, 20, 40, 10), [Some((10, 20)), None]);
assert_eq!(subtract_lock_range(10, 20, 0, 40), [None, None]);
}
#[test]
fn rebind_binary_credentials_are_encoded_verbatim() {
let credentials = [
REBIND_CREDENTIAL_SENTINEL,
REBIND_CREDENTIAL_VERSION,
0xd2,
0x04,
0,
0,
2,
0x2e,
0x16,
0,
0,
0x34,
0x12,
0,
0,
];
let request = Request::Trebind {
fid: 3,
inode_id: 4,
root_inode: 0,
flags: 0,
uname: &credentials,
n_uname: 1000,
};
let mut output = [0u8; 128];
let length = encode_request(&mut output, TEST_MSIZE, 6, request).unwrap();
assert_eq!(
length,
HEADER_SIZE + 4 + 8 + 8 + 1 + 2 + credentials.len() + 4
);
let mut body = Reader::new(&output[HEADER_SIZE..length]);
assert_eq!(body.get_u32(), Ok(3));
assert_eq!(body.get_u64(), Ok(4));
assert_eq!(body.get_u64(), Ok(0));
assert_eq!(body.get_u8(), Ok(0));
assert_eq!(body.get_string(), Ok(credentials.as_slice()));
assert_eq!(body.get_u32(), Ok(1000));
assert_eq!(body.require_end(), Ok(()));
}
#[test]
fn header_can_be_routed_before_the_body_arrives() {
let header = [11, 0, 0, 0, message_type::RLERROR, 0x34, 0x12];
assert_eq!(
decode_header(&header, TEST_MSIZE),
Ok(Header {
size: 11,
type_: message_type::RLERROR,
tag: 0x1234,
})
);
assert_eq!(
decode_header(&header[..HEADER_SIZE - 1], TEST_MSIZE),
Err(CodecError::Truncated)
);
}
#[test]
fn errors_are_bounded_and_tags_are_checked() {
let mut tiny = [0u8; HEADER_SIZE];
assert_eq!(
encode_request(
&mut tiny,
TEST_MSIZE,
1,
Request::Tread {
fid: 0,
offset: 0,
count: 0,
},
),
Err(CodecError::BufferTooSmall)
);
assert_eq!(
encode_request(
&mut [0u8; 64],
TEST_MSIZE,
1,
Request::Tversion {
msize: TEST_MSIZE,
version: VERSION_9P2000L_ZEROFS,
},
),
Err(CodecError::InvalidTag)
);
assert_eq!(
encode_request(
&mut [0u8; 64],
TEST_MSIZE,
NOTAG,
Request::Tclunk { fid: 1 },
),
Err(CodecError::InvalidTag)
);
}
#[test]
fn decodes_version_error_read_and_open_replies() {
let mut version = [0u8; HEADER_SIZE + 4 + 2 + 10];
put_header(&mut version, message_type::RVERSION, NOTAG);
version[7..11].copy_from_slice(&TEST_MSIZE.to_le_bytes());
version[11..13].copy_from_slice(&10u16.to_le_bytes());
version[13..].copy_from_slice(VERSION_9P2000L_ZEROFS);
let decoded = decode_response(&version, TEST_MSIZE, NOTAG).unwrap();
match decoded.body {
Response::Rversion(reply) => {
assert_eq!(reply.msize, TEST_MSIZE);
assert_eq!(reply.version.as_ref(), VERSION_9P2000L_ZEROFS);
}
_ => panic!("wrong response"),
}
let mut error = [0u8; HEADER_SIZE + 4];
put_header(&mut error, message_type::RLERROR, 9);
error[7..].copy_from_slice(&5u32.to_le_bytes());
match decode_response(&error, TEST_MSIZE, 9).unwrap().body {
Response::Rlerror(reply) => assert_eq!(reply.ecode, 5),
_ => panic!("wrong response"),
}
let mut read = [0u8; HEADER_SIZE + 4 + 3];
put_header(&mut read, message_type::RREAD, 10);
read[7..11].copy_from_slice(&3u32.to_le_bytes());
read[11..].copy_from_slice(b"abc");
match decode_response(&read, TEST_MSIZE, 10).unwrap().body {
Response::Rread(reply) => assert_eq!(reply.data.as_ref(), b"abc"),
_ => panic!("wrong response"),
}
let mut open = [0u8; HEADER_SIZE + QID_WIRE_SIZE + 4];
put_header(&mut open, message_type::RLOPENAT, 11);
let mut writer = Writer::new(&mut open[HEADER_SIZE..]);
put_qid(
&mut writer,
Qid {
type_: 0,
version: 2,
path: 3,
},
);
writer.put_u32(8192).unwrap();
match decode_response(&open, TEST_MSIZE, 11).unwrap().body {
Response::Rlopenat(reply) => {
assert_eq!(reply.qid.path, 3);
assert_eq!(reply.iounit, 8192);
}
_ => panic!("wrong response"),
}
}
#[test]
fn decodes_compound_attribute_namespace_and_readlink_replies() {
let stat = Stat {
qid: Qid {
type_: QID_TYPE_FILE,
version: 3,
path: 0x1122_3344_5566_7788,
},
mode: 0o100640,
uid: 1000,
gid: 1001,
nlink: 2,
size: 123,
mtime_sec: 456,
data_version: 789,
..Stat::default()
};
let mut create = [0u8; HEADER_SIZE + 4 + STAT_WIRE_SIZE];
put_header(&mut create, message_type::RLCREATEATTR, 31);
let mut writer = Writer::new(&mut create[HEADER_SIZE..]);
writer.put_u32(8192).unwrap();
put_stat(&mut writer, stat);
match decode_response(&create, TEST_MSIZE, 31).unwrap().body {
Response::Rlcreateattr(reply) => {
assert_eq!(reply.iounit, 8192);
assert_eq!(reply.stat, stat);
}
_ => panic!("wrong create response"),
}
for (type_, expected) in [
(message_type::RMKDIRATTR, 0u8),
(message_type::RSYMLINKATTR, 1u8),
(message_type::RMKNODATTR, 2u8),
(message_type::RLINKATTR, 3u8),
(message_type::RSETATTRATTR, 4u8),
] {
let mut frame = [0u8; HEADER_SIZE + STAT_WIRE_SIZE];
put_header(&mut frame, type_, 32);
put_stat(&mut Writer::new(&mut frame[HEADER_SIZE..]), stat);
let decoded = decode_response(&frame, TEST_MSIZE, 32).unwrap();
let decoded_stat = match (expected, decoded.body) {
(0, Response::Rmkdirattr(value))
| (1, Response::Rsymlinkattr(value))
| (2, Response::Rmknodattr(value))
| (3, Response::Rlinkattr(value))
| (4, Response::Rsetattrattr(value)) => value,
_ => panic!("wrong stat response"),
};
assert_eq!(decoded_stat, stat);
}
for (type_, expected) in [
(message_type::RFALLOCATE, 0u8),
(message_type::RRENAMEAT, 1u8),
(message_type::RUNLINKAT, 2u8),
] {
let mut frame = [0u8; HEADER_SIZE];
put_header(&mut frame, type_, 33);
let body = decode_response(&frame, TEST_MSIZE, 33).unwrap().body;
assert!(matches!(
(expected, body),
(0, Response::Rfallocate) | (1, Response::Rrenameat) | (2, Response::Runlinkat)
));
}
let mut readlink = [0u8; HEADER_SIZE + 2 + 6];
put_header(&mut readlink, message_type::RREADLINK, 34);
readlink[HEADER_SIZE..HEADER_SIZE + 2].copy_from_slice(&6u16.to_le_bytes());
readlink[HEADER_SIZE + 2..].copy_from_slice(b"target");
match decode_response(&readlink, TEST_MSIZE, 34).unwrap().body {
Response::Rreadlink(reply) => assert_eq!(reply.target.as_ref(), b"target"),
_ => panic!("wrong readlink response"),
}
let truncated = &readlink[..readlink.len() - 1];
assert!(matches!(
decode_response(truncated, TEST_MSIZE, 34),
Err(CodecError::FrameSizeMismatch)
));
}
#[test]
fn tfsyncdur_and_write_replies_match_the_canonical_layout() {
let mut request = [0u8; HEADER_SIZE + 4 + 4 + 8];
let length = encode_request(
&mut request,
TEST_MSIZE,
0x1234,
Request::Tfsyncdur {
fid: 0x1122_3344,
datasync: 1,
token: 0x0807_0605_0403_0201,
},
)
.unwrap();
let expected = [
23,
0,
0,
0,
message_type::TFSYNCDUR,
0x34,
0x12,
0x44,
0x33,
0x22,
0x11,
1,
0,
0,
0,
1,
2,
3,
4,
5,
6,
7,
8,
];
assert_eq!(length, expected.len());
assert_eq!(request, expected);
let mut write = [0u8; HEADER_SIZE + 4];
put_header(&mut write, message_type::RWRITE, 7);
write[HEADER_SIZE..].copy_from_slice(&123u32.to_le_bytes());
match decode_response(&write, TEST_MSIZE, 7).unwrap().body {
Response::Rwrite(response) => assert_eq!(response.count, 123),
_ => panic!("wrong response"),
}
let mut fsync = [0u8; HEADER_SIZE];
put_header(&mut fsync, message_type::RFSYNC, 8);
assert!(matches!(
decode_response(&fsync, TEST_MSIZE, 8).unwrap().body,
Response::Rfsync
));
let truncated = &write[..write.len() - 1];
assert!(matches!(
decode_response(truncated, TEST_MSIZE, 7),
Err(CodecError::FrameSizeMismatch)
));
}
#[test]
fn decodes_lock_replies_and_rejects_truncation() {
for status in [LOCK_SUCCESS, LOCK_BLOCKED] {
let mut frame = [0u8; HEADER_SIZE + 1];
put_header(&mut frame, message_type::RLOCK, 21);
frame[HEADER_SIZE] = status;
match decode_response(&frame, TEST_MSIZE, 21).unwrap().body {
Response::Rlock(reply) => assert_eq!(reply.status, status),
_ => panic!("wrong lock response"),
}
}
let mut getlock = [0u8; HEADER_SIZE + 1 + 8 + 8 + 4 + 2 + 4];
put_header(&mut getlock, message_type::RGETLOCK, 22);
let mut writer = Writer::new(&mut getlock[HEADER_SIZE..]);
writer.put_u8(LOCK_TYPE_WRLCK).unwrap();
writer.put_u64(64).unwrap();
writer.put_u64(128).unwrap();
writer.put_u32(4242).unwrap();
writer.put_string(b"peer").unwrap();
match decode_response(&getlock, TEST_MSIZE, 22).unwrap().body {
Response::Rgetlock(reply) => {
assert_eq!(reply.lock_type, LOCK_TYPE_WRLCK);
assert_eq!(reply.start, 64);
assert_eq!(reply.length, 128);
assert_eq!(reply.proc_id, 4242);
assert_eq!(reply.client_id.as_ref(), b"peer");
assert!(core::ptr::eq(
reply.client_id.as_ref().as_ptr(),
getlock[HEADER_SIZE + 23..].as_ptr(),
));
}
_ => panic!("wrong getlock response"),
}
let mut headerless = [0u8; HEADER_SIZE];
put_header(&mut headerless, message_type::RLOCK, 23);
assert!(matches!(
decode_response(&headerless, TEST_MSIZE, 23),
Err(CodecError::Truncated)
));
let mut overrun = [0u8; HEADER_SIZE + 1 + 8 + 8 + 4 + 2];
put_header(&mut overrun, message_type::RGETLOCK, 24);
let mut writer = Writer::new(&mut overrun[HEADER_SIZE..]);
writer.put_u8(LOCK_TYPE_RDLCK).unwrap();
writer.put_u64(0).unwrap();
writer.put_u64(0).unwrap();
writer.put_u32(0).unwrap();
writer.put_u16(3).unwrap();
assert!(matches!(
decode_response(&overrun, TEST_MSIZE, 24),
Err(CodecError::Truncated)
));
let mut trailing = [0u8; HEADER_SIZE + 2];
put_header(&mut trailing, message_type::RLOCK, 25);
assert!(matches!(
decode_response(&trailing, TEST_MSIZE, 25),
Err(CodecError::TrailingData)
));
}
#[test]
fn walk_and_readdir_views_are_allocation_free() {
let stat = Stat {
qid: Qid {
type_: 0x80,
version: 7,
path: 8,
},
mode: 0o40755,
uid: 1000,
gid: 1001,
nlink: 2,
size: 99,
data_version: 42,
..Stat::default()
};
let qid = Qid {
type_: 0,
version: 3,
path: 4,
};
let mut walk = [0u8; HEADER_SIZE + 2 + QID_WIRE_SIZE + STAT_WIRE_SIZE];
put_header(&mut walk, message_type::RWALKGETATTR, 12);
let mut writer = Writer::new(&mut walk[HEADER_SIZE..]);
writer.put_u16(1).unwrap();
put_qid(&mut writer, qid);
put_stat(&mut writer, stat);
match decode_response(&walk, TEST_MSIZE, 12).unwrap().body {
Response::Rwalkgetattr(reply) => {
assert_eq!(reply.qids.len(), 1);
assert_eq!(reply.qids.get(0), Some(qid));
assert_eq!(reply.qids.get(1), None);
assert_eq!(reply.stat, stat);
}
_ => panic!("wrong response"),
}
let entry_size = QID_WIRE_SIZE + 8 + 1 + 2 + 4 + STAT_WIRE_SIZE;
let mut readdir = [0u8; HEADER_SIZE + 4 + QID_WIRE_SIZE + 8 + 1 + 2 + 4 + STAT_WIRE_SIZE];
put_header(&mut readdir, message_type::RREADDIRATTR, 13);
readdir[7..11].copy_from_slice(&(entry_size as u32).to_le_bytes());
let mut writer = Writer::new(&mut readdir[11..]);
put_qid(&mut writer, qid);
writer.put_u64(55).unwrap();
writer.put_u8(0).unwrap();
writer.put_string(b"name").unwrap();
put_stat(&mut writer, stat);
match decode_response(&readdir, TEST_MSIZE, 13).unwrap().body {
Response::Rreaddirattr(reply) => {
assert_eq!(reply.len(), 1);
let entries = reply.entries().collect::<Result<Vec<_>, _>>().unwrap();
assert_eq!(entries.len(), 1);
assert_eq!(entries[0].qid, qid);
assert_eq!(entries[0].offset, 55);
assert_eq!(entries[0].name, b"name");
assert_eq!(entries[0].stat, stat);
}
_ => panic!("wrong response"),
}
}
#[test]
fn rejects_count_size_tag_and_trailing_data_violations() {
let mut truncated = [0u8; HEADER_SIZE + 4 + 2];
put_header(&mut truncated, message_type::RREAD, 1);
truncated[7..11].copy_from_slice(&3u32.to_le_bytes());
assert!(matches!(
decode_response(&truncated, TEST_MSIZE, 1),
Err(CodecError::Truncated)
));
let mut trailing = [0u8; HEADER_SIZE + 1];
put_header(&mut trailing, message_type::RCLUNK, 2);
assert!(matches!(
decode_response(&trailing, TEST_MSIZE, 2),
Err(CodecError::TrailingData)
));
assert!(matches!(
decode_response(&trailing, TEST_MSIZE, 3),
Err(CodecError::TagMismatch {
expected: 3,
actual: 2,
})
));
let mut mismatch = trailing;
mismatch[0..4].copy_from_slice(&(HEADER_SIZE as u32).to_le_bytes());
assert!(matches!(
decode_response(&mismatch, TEST_MSIZE, 2),
Err(CodecError::FrameSizeMismatch)
));
}
}