use crate::sync::SyncNineP;
use simple_coro::{Coro, CoroState, Handle, ReadyCoro};
use std::{
cell::UnsafeCell,
fmt,
future::Future,
io::{self, ErrorKind},
mem::size_of,
};
pub const MAX_SIZE_FIELD: usize = u16::MAX as usize;
pub const MAX_DATA_SIZE_FIELD: usize = u32::MAX as usize;
pub const MAX_DATA_LEN: usize = 32 * 1024 * 1024;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WriteError {
DataLength(usize),
FieldLength(usize),
}
impl fmt::Display for WriteError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::DataLength(n_bytes) => write!(
f,
"data field too long: max={MAX_DATA_SIZE_FIELD} len={n_bytes}"
),
Self::FieldLength(len) => write!(f, "string too long: max={MAX_SIZE_FIELD} len={len}"),
}
}
}
#[derive(Debug)]
pub struct SharedBuf(UnsafeCell<SharedBufInner>);
struct SharedBufInner {
pos: usize,
buf: Vec<u8>,
}
unsafe impl Send for SharedBuf {}
unsafe impl Sync for SharedBuf {}
impl Default for SharedBuf {
fn default() -> Self {
Self(UnsafeCell::new(SharedBufInner {
pos: 0,
buf: Vec::new(),
}))
}
}
impl SharedBuf {
#[allow(clippy::mut_from_ref)]
pub(crate) unsafe fn as_inner_mut(&self) -> &mut Vec<u8> {
unsafe {
let inner = &mut *self.0.get();
inner.pos = 0;
&mut inner.buf
}
}
pub(crate) unsafe fn as_slice_to(&self, end: usize) -> &[u8] {
unsafe {
let inner = &mut *self.0.get();
let prev = inner.pos;
inner.pos += end;
&inner.buf[prev..inner.pos]
}
}
unsafe fn parse_from_buffer<T: NineP>(&self) -> io::Result<T> {
let mut coro = T::read_9p_coro(self);
loop {
coro = match coro.resume() {
CoroState::Pending(c, _) => c.send(()),
CoroState::Complete(res) => return res,
};
}
}
}
pub trait NineP: Sized {
fn n_bytes(&self) -> usize;
fn write_bytes(&self, buf: &mut [u8]) -> Result<(), WriteError>;
fn write_9p_bytes(&self) -> Result<Vec<u8>, WriteError> {
let mut buf = vec![0; self.n_bytes()];
self.write_bytes(&mut buf)?;
Ok(buf)
}
fn read_9p(
buf: &SharedBuf,
handle: Handle<usize>,
) -> impl Future<Output = io::Result<Self>> + Send;
fn read_9p_coro(
buf: &SharedBuf,
) -> ReadyCoro<usize, (), io::Result<Self>, impl Future<Output = io::Result<Self>> + Send> {
Coro::from(async move |handle: Handle<usize>| Self::read_9p(buf, handle).await)
}
}
macro_rules! from_le_bytes {
($ty:ty, $bytes:expr) => {
unsafe { <$ty>::from_le_bytes($bytes[0..size_of::<$ty>()].try_into().unwrap_unchecked()) }
};
}
macro_rules! impl_u {
($($ty:ty),+) => {
$(
impl NineP for $ty {
fn n_bytes(&self) -> usize {
size_of::<$ty>()
}
fn write_bytes(&self, buf: &mut [u8]) -> Result<(), WriteError> {
buf[0..size_of::<$ty>()].copy_from_slice(&self.to_le_bytes());
Ok(())
}
async fn read_9p(buf: &SharedBuf, handle: Handle<usize>) -> io::Result<$ty> {
let n = size_of::<$ty>();
handle.yield_value(n).await;
Ok(from_le_bytes!($ty, buf.as_slice_to(n)))
}
}
)+
};
}
impl_u!(u8, u16, u32, u64);
impl NineP for String {
fn n_bytes(&self) -> usize {
size_of::<u16>() + self.len()
}
fn write_bytes(&self, buf: &mut [u8]) -> Result<(), WriteError> {
let len = self.len();
if len > MAX_SIZE_FIELD {
return Err(WriteError::FieldLength(len));
}
(len as u16).write_bytes(&mut buf[0..2])?;
buf[2..len + 2].copy_from_slice(self.as_bytes());
Ok(())
}
async fn read_9p(buf: &SharedBuf, handle: Handle<usize>) -> io::Result<Self> {
let len = u16::read_9p(buf, handle).await? as usize;
handle.yield_value(len).await;
let data = unsafe { buf.as_slice_to(len).to_vec() };
String::from_utf8(data).map_err(|e| io::Error::new(ErrorKind::InvalidData, e.to_string()))
}
}
impl<T: NineP + fmt::Debug + Send> NineP for Vec<T> {
fn n_bytes(&self) -> usize {
size_of::<u16>() + self.iter().map(|t| t.n_bytes()).sum::<usize>()
}
fn write_bytes(&self, mut buf: &mut [u8]) -> Result<(), WriteError> {
let n_bytes = self.iter().map(|t| t.n_bytes()).sum::<usize>();
if n_bytes > MAX_SIZE_FIELD {
return Err(WriteError::FieldLength(n_bytes));
}
(self.len() as u16).write_bytes(&mut buf[0..2])?;
buf = &mut buf[2..];
for t in self {
let n = t.n_bytes();
t.write_bytes(buf)?;
buf = &mut buf[n..];
}
Ok(())
}
async fn read_9p(buf: &SharedBuf, handle: Handle<usize>) -> io::Result<Self> {
let len = u16::read_9p(buf, handle).await? as usize;
let mut elems = Vec::with_capacity(len);
for _ in 0..len {
elems.push(T::read_9p(buf, handle).await?);
}
Ok(elems)
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct Data(pub(crate) Vec<u8>);
impl fmt::Debug for Data {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Data(n_bytes={})", self.0.len())
}
}
impl From<Vec<u8>> for Data {
fn from(value: Vec<u8>) -> Self {
Self(value)
}
}
impl TryFrom<Data> for Vec<RawStat> {
type Error = io::Error;
fn try_from(Data(bytes): Data) -> Result<Self, io::Error> {
let mut buf = Vec::new();
let mut bytes = bytes.as_slice();
let n = size_of::<RawStat>();
let sb = SharedBuf::default();
loop {
match RawStat::read_from(&sb, &mut bytes) {
Ok(rs) => {
buf.push(rs);
bytes = &bytes[n..];
}
Err(e) if e.kind() == ErrorKind::UnexpectedEof => break,
Err(e) => return Err(e),
}
}
Ok(buf)
}
}
impl NineP for Data {
fn n_bytes(&self) -> usize {
size_of::<u32>() + self.0.len()
}
fn write_bytes(&self, buf: &mut [u8]) -> Result<(), WriteError> {
let n_bytes = self.0.len();
if n_bytes > MAX_DATA_SIZE_FIELD {
return Err(WriteError::DataLength(n_bytes));
}
(n_bytes as u32).write_bytes(&mut buf[0..4])?;
buf[4..].copy_from_slice(&self.0);
Ok(())
}
async fn read_9p(buf: &SharedBuf, handle: Handle<usize>) -> io::Result<Self> {
let len = u32::read_9p(buf, handle).await? as usize;
if len > MAX_DATA_LEN {
return Err(io::Error::new(
ErrorKind::InvalidData,
format!("data field too long: max={MAX_DATA_LEN} len={len}"),
));
}
handle.yield_value(len).await;
let data = unsafe { buf.as_slice_to(len).to_vec() };
Ok(Data(data))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RawStat {
pub size: u16,
pub ty: u16,
pub dev: u32,
pub qid: Qid,
pub mode: u32,
pub atime: u32,
pub mtime: u32,
pub length: u64,
pub name: String,
pub uid: String,
pub gid: String,
pub muid: String,
}
macro_rules! write_fields {
($buf:expr, $self:expr, $($field:ident),+) => {
#[allow(unused_assignments)]
{
$(
let len = $self.$field.n_bytes();
$self.$field.write_bytes(&mut $buf[0..len])?;
$buf = &mut $buf[len..];
)+
Ok(())
}
};
}
impl NineP for RawStat {
fn n_bytes(&self) -> usize {
41 + self.name.n_bytes() + self.uid.n_bytes() + self.gid.n_bytes() + self.muid.n_bytes()
}
fn write_bytes(&self, mut buf: &mut [u8]) -> Result<(), WriteError> {
write_fields!(
buf, self, size, ty, dev, qid, mode, atime, mtime, length, name, uid, gid, muid
)
}
async fn read_9p(buf: &SharedBuf, handle: Handle<usize>) -> io::Result<Self> {
handle.yield_value(41).await;
let bytes = unsafe { buf.as_slice_to(41) };
let size = from_le_bytes!(u16, bytes);
let ty = from_le_bytes!(u16, &bytes[2..]);
let dev = from_le_bytes!(u32, &bytes[4..]);
let qid = Qid::from_bytes(&bytes[8..]);
let mode = from_le_bytes!(u32, &bytes[21..]);
let atime = from_le_bytes!(u32, &bytes[25..]);
let mtime = from_le_bytes!(u32, &bytes[29..]);
let length = from_le_bytes!(u64, &bytes[33..]);
let name = String::read_9p(buf, handle).await?;
let uid = String::read_9p(buf, handle).await?;
let gid = String::read_9p(buf, handle).await?;
let muid = String::read_9p(buf, handle).await?;
Ok(RawStat {
size,
ty,
dev,
qid,
mode,
atime,
mtime,
length,
name,
uid,
gid,
muid,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Qid {
pub ty: u8,
pub version: u32,
pub path: u64,
}
impl Qid {
#[inline]
fn from_bytes(bytes: &[u8]) -> Self {
let ty = from_le_bytes!(u8, &bytes);
let version = from_le_bytes!(u32, &bytes[1..]);
let path = from_le_bytes!(u64, &bytes[5..]);
Self { ty, version, path }
}
}
impl NineP for Qid {
fn n_bytes(&self) -> usize {
1 + 4 + 8
}
fn write_bytes(&self, mut buf: &mut [u8]) -> Result<(), WriteError> {
write_fields!(buf, self, ty, version, path)
}
async fn read_9p(buf: &SharedBuf, handle: Handle<usize>) -> io::Result<Self> {
let ty = u8::read_9p(buf, handle).await?;
let version = u32::read_9p(buf, handle).await?;
let path = u64::read_9p(buf, handle).await?;
Ok(Qid { ty, version, path })
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
struct MessageType(u8);
#[allow(non_upper_case_globals)]
impl MessageType {
const Tversion: Self = Self(100);
const Rversion: Self = Self(101);
const Tauth: Self = Self(102);
const Rauth: Self = Self(103);
const Tattach: Self = Self(104);
const Rattach: Self = Self(105);
const Rerror: Self = Self(107);
const Tflush: Self = Self(108);
const Rflush: Self = Self(109);
const Twalk: Self = Self(110);
const Rwalk: Self = Self(111);
const Topen: Self = Self(112);
const Ropen: Self = Self(113);
const Tcreate: Self = Self(114);
const Rcreate: Self = Self(115);
const Tread: Self = Self(116);
const Rread: Self = Self(117);
const Twrite: Self = Self(118);
const Rwrite: Self = Self(119);
const Tclunk: Self = Self(120);
const Rclunk: Self = Self(121);
const Tremove: Self = Self(122);
const Rremove: Self = Self(123);
const Tstat: Self = Self(124);
const Rstat: Self = Self(125);
const Twstat: Self = Self(126);
const Rwstat: Self = Self(127);
}
macro_rules! impl_message_format {
(
$message_ty:ident, $enum_ty:ident, $err:expr;
$($enum_variant:ident => $message_variant:ident {
$($field:ident: $ty:ty,)*
})+
) => {
impl NineP for $message_ty {
fn n_bytes(&self) -> usize {
let content_size = match &self.content {
$(
$enum_ty::$enum_variant { $($field,)* } => {
#[allow(unused_mut)]
let mut n = 0;
$(n += $field.n_bytes();)*
n
}
)+
};
4 + 1 + 2 + content_size
}
#[allow(unused_assignments)]
fn write_bytes(&self, buf: &mut [u8]) -> Result<(), WriteError> {
let ty = match self.content {
$($enum_ty::$enum_variant { .. } => MessageType::$message_variant.0,)+
};
(self.n_bytes() as u32).write_bytes(buf)?; ty.write_bytes(&mut buf[4..])?; self.tag.write_bytes(&mut buf[5..])?; let mut offset = 7;
match &self.content {
$(
$enum_ty::$enum_variant { $($field,)* } => {
$(
$field.write_bytes(&mut buf[offset..])?;
offset += $field.n_bytes();
)*
},
)+
}
Ok(())
}
#[allow(unused_assignments, unused_unsafe)]
async fn read_9p(buf: &SharedBuf, handle: Handle<usize>) -> io::Result<Self> {
let len = u32::read_9p(buf, handle).await? as usize;
handle.yield_value(len-4).await;
unsafe {
let bytes = buf.as_slice_to(3);
let ty = from_le_bytes!(u8, &bytes);
let tag = from_le_bytes!(u16, &bytes[1..]);
let content = match MessageType(ty) {
$(
MessageType::$message_variant => $enum_ty::$enum_variant {
$($field: buf.parse_from_buffer::<$ty>()?),*
},
)+
MessageType(ty) => return Err(io::Error::new(
ErrorKind::InvalidData,
format!($err, ty),
)),
};
Ok($message_ty { tag, content })
}
}
}
};
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Tmessage {
pub tag: u16,
pub content: Tdata,
}
impl Tmessage {
pub const fn new(tag: u16, content: Tdata) -> Self {
Self { tag, content }
}
}
macro_rules! impl_tdata {
($(
$(#[$docs:meta])+
$enum_variant:ident => $message_variant:ident {
$(
$(#[$field_docs:meta])+
$field:ident: $ty:ty,
)*
}
)+) => {
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Tdata {
$( $(#[$docs])+ $enum_variant { $($(#[$field_docs])+ $field: $ty,)* }, )+
}
impl_message_format!(
Tmessage, Tdata, "invalid message type for t-message: {}";
$($enum_variant => $message_variant {
$($field: $ty,)*
})+
);
};
}
impl_tdata! {
Version => Tversion {
msize: u32,
version: String,
}
Auth => Tauth {
afid: u32,
uname: String,
aname: String,
}
Attach => Tattach {
fid: u32,
afid: u32,
uname: String,
aname: String,
}
Flush => Tflush {
old_tag: u16,
}
Walk => Twalk {
fid: u32,
new_fid: u32,
wnames: Vec<String>,
}
Open => Topen {
fid: u32,
mode: u8,
}
Create => Tcreate {
fid: u32,
name: String,
perm: u32,
mode: u8,
}
Read => Tread {
fid: u32,
offset: u64,
count: u32,
}
Write => Twrite {
fid: u32,
offset: u64,
data: Data,
}
Clunk => Tclunk {
fid: u32,
}
Remove => Tremove {
fid: u32,
}
Stat => Tstat {
fid: u32,
}
Wstat => Twstat {
fid: u32,
size: u16,
stat: RawStat,
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Rmessage {
pub tag: u16,
pub content: Rdata,
}
macro_rules! impl_rdata {
($(
$(#[$docs:meta])+
$enum_variant:ident => $message_variant:ident {
$(
$(#[$field_docs:meta])+
$field:ident: $ty:ty,
)*
}
)+) => {
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Rdata {
$( $(#[$docs])+ $enum_variant { $($(#[$field_docs])+ $field: $ty,)* }, )+
}
impl_message_format!(
Rmessage, Rdata, "invalid message type for r-message: {}";
$($enum_variant => $message_variant {
$($field: $ty,)*
})+
);
};
}
impl_rdata! {
Version => Rversion {
msize: u32,
version: String,
}
Auth => Rauth {
aqid: Qid,
}
Error => Rerror {
ename: String,
}
Attach => Rattach {
aqid: Qid,
}
Flush => Rflush {}
Walk => Rwalk {
wqids: Vec<Qid>,
}
Open => Ropen {
qid: Qid,
iounit: u32,
}
Create => Rcreate {
qid: Qid,
iounit: u32,
}
Read => Rread {
data: Data,
}
Write => Rwrite {
count: u32,
}
Clunk => Rclunk {}
Remove => Rremove {}
Stat => Rstat {
size: u16,
stat: RawStat,
}
Wstat => Rwstat {}
}
#[cfg(test)]
mod tests {
use super::*;
use simple_test_case::test_case;
use std::cmp::PartialEq;
#[test]
fn uint_decode() {
let buf: Vec<u8> = vec![0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef];
let sb = SharedBuf::default();
assert_eq!(0x01, u8::read_from(&sb, &mut buf.as_slice()).unwrap());
assert_eq!(0x2301, u16::read_from(&sb, &mut buf.as_slice()).unwrap());
assert_eq!(
0x67452301,
u32::read_from(&sb, &mut buf.as_slice()).unwrap()
);
assert_eq!(
0xefcdab8967452301,
u64::read_from(&sb, &mut buf.as_slice()).unwrap()
);
}
#[test_case("test", &[0x04, 0x00, 0x74, 0x65, 0x73, 0x74]; "single byte chars only")]
#[test_case("", &[0x00, 0x00]; "empty string")]
#[test_case(
"Hello, 世界",
&[0x0d, 0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0xe4, 0xb8, 0x96, 0xe7, 0x95, 0x8c];
"including multi-byte chars"
)]
#[test]
fn string_encode(s: &str, bytes: &[u8]) {
let s = s.to_string();
let buf = s.write_9p_bytes().unwrap();
assert_eq!(&buf, bytes);
}
enum F9 {
U8(u8),
U16(u16),
U32(u32),
U64(u64),
S(&'static str),
V(Vec<String>),
D(Vec<u8>),
RawStat,
Clunk,
Walk,
Rwalk,
}
fn round_trip_inner<T>(t1: T)
where
T: NineP + PartialEq + fmt::Debug,
{
let buf = t1.write_9p_bytes().unwrap();
let sb = SharedBuf::default();
let t2 = T::read_from(&sb, &mut buf.as_slice()).unwrap();
assert_eq!(t1, t2);
}
#[test_case(F9::U8(42); "u8_")]
#[test_case(F9::U16(17); "u16_")]
#[test_case(F9::U32(773); "u32_")]
#[test_case(F9::U64(123456); "u64_")]
#[test_case(F9::S("testing"); "single-byte char string")]
#[test_case(F9::S("Hello, 世界"); "multi-byte char string")]
#[test_case(F9::V(vec!["foo".to_string(), "bar".to_string()]); "vec String")]
#[test_case(F9::D(vec![5, 6, 7, 8, u8::MAX]); "data")]
#[test_case(F9::RawStat; "raw stat")]
#[test_case(F9::Clunk; "clunk")]
#[test_case(F9::Walk; "walk")]
#[test_case(F9::Rwalk; "rwalk")]
#[test]
fn round_trip_is_fine(data: F9) {
match data {
F9::U8(t) => round_trip_inner(t),
F9::U16(t) => round_trip_inner(t),
F9::U32(t) => round_trip_inner(t),
F9::U64(t) => round_trip_inner(t),
F9::S(t) => round_trip_inner(t.to_string()),
F9::V(t) => round_trip_inner(t),
F9::D(t) => round_trip_inner(Data(t)),
F9::RawStat => round_trip_inner(RawStat {
size: 1,
ty: 2,
dev: 3,
qid: Qid {
ty: 1,
version: 2,
path: 3,
},
mode: 4,
atime: 5,
mtime: 6,
length: 7,
name: "test name".to_string(),
uid: "test uid".to_string(),
gid: "test gid".to_string(),
muid: "test muid".to_string(),
}),
F9::Clunk => round_trip_inner(Rmessage {
tag: 0,
content: Rdata::Clunk {},
}),
F9::Walk => round_trip_inner(Tmessage {
tag: 0,
content: Tdata::Walk {
fid: 0,
new_fid: 2,
wnames: vec!["bar".to_string()],
},
}),
F9::Rwalk => round_trip_inner(Rmessage {
tag: 0,
content: Rdata::Walk {
wqids: vec![
Qid {
ty: 0,
version: 1,
path: 2,
},
Qid {
ty: 3,
version: 4,
path: 5,
},
],
},
}),
}
}
}