use anyhow::{bail, ensure, Context, Result};
use std::time::Duration;
use tokio::io::{AsyncRead, AsyncReadExt};
pub const VERSION: &str = "9P2000";
pub const MAX_MSIZE: u32 = 65536;
pub const MIN_MSIZE: u32 = 256;
pub const IOHDRSZ: u32 = 24;
pub const MAX_WELEM: usize = 16;
pub const MAX_NAME: usize = 255;
pub const MAX_FIDS: usize = 256;
pub const MAX_CONTENT: usize = 1024 * 1024;
pub const MAX_ENTRIES: usize = 1024;
pub const IO_TIMEOUT: Duration = Duration::from_secs(30);
pub const NOFID: u32 = u32::MAX;
pub const NOTAG: u16 = u16::MAX;
pub const DMDIR: u32 = 0x8000_0000;
pub const QTDIR: u8 = 0x80;
pub const QTFILE: u8 = 0x00;
pub const OREAD: u8 = 0;
pub const OWRITE: u8 = 1;
pub const ORDWR: u8 = 2;
pub const OEXEC: u8 = 3;
pub const OTRUNC: u8 = 0x10;
pub const ORCLOSE: u8 = 0x40;
pub const TVERSION: u8 = 100;
pub const RVERSION: u8 = 101;
pub const TAUTH: u8 = 102;
pub const RAUTH: u8 = 103;
pub const TATTACH: u8 = 104;
pub const RATTACH: u8 = 105;
pub const RERROR: u8 = 107;
pub const TFLUSH: u8 = 108;
pub const RFLUSH: u8 = 109;
pub const TWALK: u8 = 110;
pub const RWALK: u8 = 111;
pub const TOPEN: u8 = 112;
pub const ROPEN: u8 = 113;
pub const TCREATE: u8 = 114;
pub const RCREATE: u8 = 115;
pub const TREAD: u8 = 116;
pub const RREAD: u8 = 117;
pub const TWRITE: u8 = 118;
pub const RWRITE: u8 = 119;
pub const TCLUNK: u8 = 120;
pub const RCLUNK: u8 = 121;
pub const TREMOVE: u8 = 122;
pub const RREMOVE: u8 = 123;
pub const TSTAT: u8 = 124;
pub const RSTAT: u8 = 125;
pub const TWSTAT: u8 = 126;
pub const RWSTAT: u8 = 127;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Qid {
pub kind: u8,
pub version: u32,
pub path: u64,
}
impl Qid {
pub fn is_dir(&self) -> bool {
self.kind & QTDIR != 0
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Stat {
pub kind: 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,
}
impl Stat {
pub fn dont_touch() -> Self {
Self {
kind: u16::MAX,
dev: u32::MAX,
qid: Qid {
kind: u8::MAX,
version: u32::MAX,
path: u64::MAX,
},
mode: u32::MAX,
atime: u32::MAX,
mtime: u32::MAX,
length: u64::MAX,
name: String::new(),
uid: String::new(),
gid: String::new(),
muid: String::new(),
}
}
}
#[derive(Default)]
pub struct Writer {
buf: Vec<u8>,
}
impl Writer {
pub fn new() -> Self {
Self::default()
}
pub fn u8(mut self, v: u8) -> Self {
self.buf.push(v);
self
}
pub fn u16(mut self, v: u16) -> Self {
self.buf.extend_from_slice(&v.to_le_bytes());
self
}
pub fn u32(mut self, v: u32) -> Self {
self.buf.extend_from_slice(&v.to_le_bytes());
self
}
pub fn u64(mut self, v: u64) -> Self {
self.buf.extend_from_slice(&v.to_le_bytes());
self
}
pub fn string(self, s: &str) -> Self {
let len = s.len().min(u16::MAX as usize);
let mut w = self.u16(len as u16);
w.buf.extend_from_slice(&s.as_bytes()[..len]);
w
}
pub fn bytes(mut self, b: &[u8]) -> Self {
self.buf.extend_from_slice(b);
self
}
pub fn qid(self, q: &Qid) -> Self {
self.u8(q.kind).u32(q.version).u64(q.path)
}
pub fn stat(self, s: &Stat) -> Self {
self.bytes(&encode_stat(s))
}
pub fn finish(self, kind: u8, tag: u16) -> Vec<u8> {
let size = (7 + self.buf.len()) as u32;
let mut out = Vec::with_capacity(size as usize);
out.extend_from_slice(&size.to_le_bytes());
out.push(kind);
out.extend_from_slice(&tag.to_le_bytes());
out.extend_from_slice(&self.buf);
out
}
}
pub fn encode_stat(s: &Stat) -> Vec<u8> {
let body = Writer::new()
.u16(s.kind)
.u32(s.dev)
.qid(&s.qid)
.u32(s.mode)
.u32(s.atime)
.u32(s.mtime)
.u64(s.length)
.string(&s.name)
.string(&s.uid)
.string(&s.gid)
.string(&s.muid)
.buf;
let mut out = Vec::with_capacity(body.len() + 2);
out.extend_from_slice(&(body.len() as u16).to_le_bytes());
out.extend_from_slice(&body);
out
}
pub struct Reader<'a> {
buf: &'a [u8],
}
impl<'a> Reader<'a> {
pub fn new(buf: &'a [u8]) -> Self {
Self { buf }
}
pub fn is_empty(&self) -> bool {
self.buf.is_empty()
}
pub fn take(&mut self, n: usize) -> Result<&'a [u8]> {
ensure!(self.buf.len() >= n, "9P message ends inside a field");
let (head, tail) = self.buf.split_at(n);
self.buf = tail;
Ok(head)
}
pub fn u8(&mut self) -> Result<u8> {
Ok(self.take(1)?[0])
}
pub fn u16(&mut self) -> Result<u16> {
Ok(u16::from_le_bytes(self.take(2)?.try_into()?))
}
pub fn u32(&mut self) -> Result<u32> {
Ok(u32::from_le_bytes(self.take(4)?.try_into()?))
}
pub fn u64(&mut self) -> Result<u64> {
Ok(u64::from_le_bytes(self.take(8)?.try_into()?))
}
pub fn string(&mut self) -> Result<String> {
let len = self.u16()? as usize;
Ok(std::str::from_utf8(self.take(len)?)
.context("9P string is not UTF-8")?
.to_string())
}
pub fn qid(&mut self) -> Result<Qid> {
Ok(Qid {
kind: self.u8()?,
version: self.u32()?,
path: self.u64()?,
})
}
pub fn stat(&mut self) -> Result<Stat> {
let size = self.u16()? as usize;
let mut r = Reader::new(self.take(size)?);
Ok(Stat {
kind: r.u16()?,
dev: r.u32()?,
qid: r.qid()?,
mode: r.u32()?,
atime: r.u32()?,
mtime: r.u32()?,
length: r.u64()?,
name: r.string()?,
uid: r.string()?,
gid: r.string()?,
muid: r.string()?,
})
}
}
pub async fn read_message<R: AsyncRead + Unpin>(
reader: &mut R,
max: u32,
idle: Duration,
) -> Result<Option<(u8, u16, Vec<u8>)>> {
let mut size = [0u8; 4];
match tokio::time::timeout(idle, reader.read(&mut size[..1])).await {
Ok(Ok(0)) => return Ok(None),
Ok(Ok(_)) => {}
Ok(Err(e)) => return Err(e.into()),
Err(_) => bail!("9P peer idle for {}s", idle.as_secs()),
}
tokio::time::timeout(IO_TIMEOUT, async {
reader.read_exact(&mut size[1..]).await?;
let size = u32::from_le_bytes(size);
ensure!(size >= 7, "9P message size {size} is below the header");
ensure!(
size <= max,
"9P message of {size} bytes exceeds msize {max}"
);
let mut rest = vec![0u8; size as usize - 4];
reader.read_exact(&mut rest).await?;
let kind = rest[0];
let tag = u16::from_le_bytes([rest[1], rest[2]]);
Ok(Some((kind, tag, rest[3..].to_vec())))
})
.await
.context("9P read deadline")?
}
pub fn check_name(name: &str) -> Result<()> {
ensure!(
!name.is_empty()
&& name.len() <= MAX_NAME
&& !name.contains('/')
&& name != "."
&& !name.contains('\0'),
"invalid 9P name {name:?}"
);
Ok(())
}
pub fn qid_path(path: &str) -> u64 {
path.bytes().fold(0xcbf2_9ce4_8422_2325u64, |h, b| {
(h ^ u64::from(b)).wrapping_mul(0x0000_0100_0000_01b3)
})
}
pub fn join(parent: &str, name: &str) -> String {
if parent == "/" {
format!("/{name}")
} else {
format!("{parent}/{name}")
}
}
pub fn parent(path: &str) -> String {
match path.rsplit_once('/') {
Some(("", _)) | None => "/".into(),
Some((p, _)) => p.into(),
}
}
pub fn base(path: &str) -> String {
match path.rsplit_once('/') {
Some((_, "")) | None => "/".into(),
Some((_, name)) => name.into(),
}
}
pub fn elements(path: &str) -> Result<Vec<String>> {
ensure!(path.starts_with('/'), "9P paths are absolute: {path:?}");
let parts: Vec<String> = path
.split('/')
.filter(|p| !p.is_empty())
.map(str::to_string)
.collect();
for p in &parts {
if p != ".." {
check_name(p)?;
}
}
Ok(parts)
}
pub fn to_text(bytes: &[u8]) -> (String, &'static str) {
match std::str::from_utf8(bytes) {
Ok(s) => (s.to_string(), "utf8"),
Err(_) => (bytes.iter().map(|b| format!("{b:02x}")).collect(), "hex"),
}
}
pub fn from_text(text: &str, encoding: Option<&str>) -> Result<Vec<u8>> {
match encoding.unwrap_or("utf8") {
"utf8" => Ok(text.as_bytes().to_vec()),
"hex" => {
ensure!(
text.is_ascii() && text.len().is_multiple_of(2),
"hex data must be an even number of hex digits"
);
(0..text.len())
.step_by(2)
.map(|i| u8::from_str_radix(&text[i..i + 2], 16).context("invalid hex data"))
.collect()
}
other => bail!("encoding must be utf8 or hex, not {other}"),
}
}