1use anyhow::{bail, ensure, Context, Result};
6use std::time::Duration;
7use tokio::io::{AsyncRead, AsyncReadExt};
8
9pub const VERSION: &str = "9P2000";
10pub const MAX_MSIZE: u32 = 65536;
12pub const MIN_MSIZE: u32 = 256;
14pub const IOHDRSZ: u32 = 24;
16pub const MAX_WELEM: usize = 16;
18pub const MAX_NAME: usize = 255;
20pub const MAX_FIDS: usize = 256;
22pub const MAX_CONTENT: usize = 1024 * 1024;
24pub const MAX_ENTRIES: usize = 1024;
26pub const IO_TIMEOUT: Duration = Duration::from_secs(30);
28pub const NOFID: u32 = u32::MAX;
29pub const NOTAG: u16 = u16::MAX;
30
31pub const DMDIR: u32 = 0x8000_0000;
32pub const QTDIR: u8 = 0x80;
33pub const QTFILE: u8 = 0x00;
34pub const OREAD: u8 = 0;
35pub const OWRITE: u8 = 1;
36pub const ORDWR: u8 = 2;
37pub const OEXEC: u8 = 3;
38pub const OTRUNC: u8 = 0x10;
39pub const ORCLOSE: u8 = 0x40;
40
41pub const TVERSION: u8 = 100;
42pub const RVERSION: u8 = 101;
43pub const TAUTH: u8 = 102;
44pub const RAUTH: u8 = 103;
45pub const TATTACH: u8 = 104;
46pub const RATTACH: u8 = 105;
47pub const RERROR: u8 = 107;
48pub const TFLUSH: u8 = 108;
49pub const RFLUSH: u8 = 109;
50pub const TWALK: u8 = 110;
51pub const RWALK: u8 = 111;
52pub const TOPEN: u8 = 112;
53pub const ROPEN: u8 = 113;
54pub const TCREATE: u8 = 114;
55pub const RCREATE: u8 = 115;
56pub const TREAD: u8 = 116;
57pub const RREAD: u8 = 117;
58pub const TWRITE: u8 = 118;
59pub const RWRITE: u8 = 119;
60pub const TCLUNK: u8 = 120;
61pub const RCLUNK: u8 = 121;
62pub const TREMOVE: u8 = 122;
63pub const RREMOVE: u8 = 123;
64pub const TSTAT: u8 = 124;
65pub const RSTAT: u8 = 125;
66pub const TWSTAT: u8 = 126;
67pub const RWSTAT: u8 = 127;
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub struct Qid {
71 pub kind: u8,
72 pub version: u32,
73 pub path: u64,
74}
75
76impl Qid {
77 pub fn is_dir(&self) -> bool {
78 self.kind & QTDIR != 0
79 }
80}
81
82#[derive(Debug, Clone, PartialEq, Eq)]
84pub struct Stat {
85 pub kind: u16,
86 pub dev: u32,
87 pub qid: Qid,
88 pub mode: u32,
89 pub atime: u32,
90 pub mtime: u32,
91 pub length: u64,
92 pub name: String,
93 pub uid: String,
94 pub gid: String,
95 pub muid: String,
96}
97
98impl Stat {
99 pub fn dont_touch() -> Self {
101 Self {
102 kind: u16::MAX,
103 dev: u32::MAX,
104 qid: Qid {
105 kind: u8::MAX,
106 version: u32::MAX,
107 path: u64::MAX,
108 },
109 mode: u32::MAX,
110 atime: u32::MAX,
111 mtime: u32::MAX,
112 length: u64::MAX,
113 name: String::new(),
114 uid: String::new(),
115 gid: String::new(),
116 muid: String::new(),
117 }
118 }
119}
120
121#[derive(Default)]
123pub struct Writer {
124 buf: Vec<u8>,
125}
126
127impl Writer {
128 pub fn new() -> Self {
129 Self::default()
130 }
131 pub fn u8(mut self, v: u8) -> Self {
132 self.buf.push(v);
133 self
134 }
135 pub fn u16(mut self, v: u16) -> Self {
136 self.buf.extend_from_slice(&v.to_le_bytes());
137 self
138 }
139 pub fn u32(mut self, v: u32) -> Self {
140 self.buf.extend_from_slice(&v.to_le_bytes());
141 self
142 }
143 pub fn u64(mut self, v: u64) -> Self {
144 self.buf.extend_from_slice(&v.to_le_bytes());
145 self
146 }
147 pub fn string(self, s: &str) -> Self {
148 let len = s.len().min(u16::MAX as usize);
149 let mut w = self.u16(len as u16);
150 w.buf.extend_from_slice(&s.as_bytes()[..len]);
151 w
152 }
153 pub fn bytes(mut self, b: &[u8]) -> Self {
154 self.buf.extend_from_slice(b);
155 self
156 }
157 pub fn qid(self, q: &Qid) -> Self {
158 self.u8(q.kind).u32(q.version).u64(q.path)
159 }
160 pub fn stat(self, s: &Stat) -> Self {
161 self.bytes(&encode_stat(s))
162 }
163 pub fn finish(self, kind: u8, tag: u16) -> Vec<u8> {
164 let size = (7 + self.buf.len()) as u32;
165 let mut out = Vec::with_capacity(size as usize);
166 out.extend_from_slice(&size.to_le_bytes());
167 out.push(kind);
168 out.extend_from_slice(&tag.to_le_bytes());
169 out.extend_from_slice(&self.buf);
170 out
171 }
172}
173
174pub fn encode_stat(s: &Stat) -> Vec<u8> {
176 let body = Writer::new()
177 .u16(s.kind)
178 .u32(s.dev)
179 .qid(&s.qid)
180 .u32(s.mode)
181 .u32(s.atime)
182 .u32(s.mtime)
183 .u64(s.length)
184 .string(&s.name)
185 .string(&s.uid)
186 .string(&s.gid)
187 .string(&s.muid)
188 .buf;
189 let mut out = Vec::with_capacity(body.len() + 2);
190 out.extend_from_slice(&(body.len() as u16).to_le_bytes());
191 out.extend_from_slice(&body);
192 out
193}
194
195pub struct Reader<'a> {
197 buf: &'a [u8],
198}
199
200impl<'a> Reader<'a> {
201 pub fn new(buf: &'a [u8]) -> Self {
202 Self { buf }
203 }
204 pub fn is_empty(&self) -> bool {
205 self.buf.is_empty()
206 }
207 pub fn take(&mut self, n: usize) -> Result<&'a [u8]> {
208 ensure!(self.buf.len() >= n, "9P message ends inside a field");
209 let (head, tail) = self.buf.split_at(n);
210 self.buf = tail;
211 Ok(head)
212 }
213 pub fn u8(&mut self) -> Result<u8> {
214 Ok(self.take(1)?[0])
215 }
216 pub fn u16(&mut self) -> Result<u16> {
217 Ok(u16::from_le_bytes(self.take(2)?.try_into()?))
218 }
219 pub fn u32(&mut self) -> Result<u32> {
220 Ok(u32::from_le_bytes(self.take(4)?.try_into()?))
221 }
222 pub fn u64(&mut self) -> Result<u64> {
223 Ok(u64::from_le_bytes(self.take(8)?.try_into()?))
224 }
225 pub fn string(&mut self) -> Result<String> {
226 let len = self.u16()? as usize;
227 Ok(std::str::from_utf8(self.take(len)?)
228 .context("9P string is not UTF-8")?
229 .to_string())
230 }
231 pub fn qid(&mut self) -> Result<Qid> {
232 Ok(Qid {
233 kind: self.u8()?,
234 version: self.u32()?,
235 path: self.u64()?,
236 })
237 }
238 pub fn stat(&mut self) -> Result<Stat> {
239 let size = self.u16()? as usize;
240 let mut r = Reader::new(self.take(size)?);
241 Ok(Stat {
242 kind: r.u16()?,
243 dev: r.u32()?,
244 qid: r.qid()?,
245 mode: r.u32()?,
246 atime: r.u32()?,
247 mtime: r.u32()?,
248 length: r.u64()?,
249 name: r.string()?,
250 uid: r.string()?,
251 gid: r.string()?,
252 muid: r.string()?,
253 })
254 }
255}
256
257pub async fn read_message<R: AsyncRead + Unpin>(
261 reader: &mut R,
262 max: u32,
263 idle: Duration,
264) -> Result<Option<(u8, u16, Vec<u8>)>> {
265 let mut size = [0u8; 4];
266 match tokio::time::timeout(idle, reader.read(&mut size[..1])).await {
267 Ok(Ok(0)) => return Ok(None),
268 Ok(Ok(_)) => {}
269 Ok(Err(e)) => return Err(e.into()),
270 Err(_) => bail!("9P peer idle for {}s", idle.as_secs()),
271 }
272 tokio::time::timeout(IO_TIMEOUT, async {
273 reader.read_exact(&mut size[1..]).await?;
274 let size = u32::from_le_bytes(size);
275 ensure!(size >= 7, "9P message size {size} is below the header");
276 ensure!(
277 size <= max,
278 "9P message of {size} bytes exceeds msize {max}"
279 );
280 let mut rest = vec![0u8; size as usize - 4];
281 reader.read_exact(&mut rest).await?;
282 let kind = rest[0];
283 let tag = u16::from_le_bytes([rest[1], rest[2]]);
284 Ok(Some((kind, tag, rest[3..].to_vec())))
285 })
286 .await
287 .context("9P read deadline")?
288}
289
290pub fn check_name(name: &str) -> Result<()> {
293 ensure!(
294 !name.is_empty()
295 && name.len() <= MAX_NAME
296 && !name.contains('/')
297 && name != "."
298 && !name.contains('\0'),
299 "invalid 9P name {name:?}"
300 );
301 Ok(())
302}
303
304pub fn qid_path(path: &str) -> u64 {
306 path.bytes().fold(0xcbf2_9ce4_8422_2325u64, |h, b| {
307 (h ^ u64::from(b)).wrapping_mul(0x0000_0100_0000_01b3)
308 })
309}
310
311pub fn join(parent: &str, name: &str) -> String {
313 if parent == "/" {
314 format!("/{name}")
315 } else {
316 format!("{parent}/{name}")
317 }
318}
319
320pub fn parent(path: &str) -> String {
322 match path.rsplit_once('/') {
323 Some(("", _)) | None => "/".into(),
324 Some((p, _)) => p.into(),
325 }
326}
327
328pub fn base(path: &str) -> String {
330 match path.rsplit_once('/') {
331 Some((_, "")) | None => "/".into(),
332 Some((_, name)) => name.into(),
333 }
334}
335
336pub fn elements(path: &str) -> Result<Vec<String>> {
338 ensure!(path.starts_with('/'), "9P paths are absolute: {path:?}");
339 let parts: Vec<String> = path
340 .split('/')
341 .filter(|p| !p.is_empty())
342 .map(str::to_string)
343 .collect();
344 for p in &parts {
345 if p != ".." {
346 check_name(p)?;
347 }
348 }
349 Ok(parts)
350}
351
352pub fn to_text(bytes: &[u8]) -> (String, &'static str) {
354 match std::str::from_utf8(bytes) {
355 Ok(s) => (s.to_string(), "utf8"),
356 Err(_) => (bytes.iter().map(|b| format!("{b:02x}")).collect(), "hex"),
357 }
358}
359
360pub fn from_text(text: &str, encoding: Option<&str>) -> Result<Vec<u8>> {
362 match encoding.unwrap_or("utf8") {
363 "utf8" => Ok(text.as_bytes().to_vec()),
364 "hex" => {
365 ensure!(
366 text.is_ascii() && text.len().is_multiple_of(2),
367 "hex data must be an even number of hex digits"
368 );
369 (0..text.len())
370 .step_by(2)
371 .map(|i| u8::from_str_radix(&text[i..i + 2], 16).context("invalid hex data"))
372 .collect()
373 }
374 other => bail!("encoding must be utf8 or hex, not {other}"),
375 }
376}