1pub const PROTOCOL: u32 = 3;
19
20pub const MAX_MESSAGE: usize = 40 << 20;
22
23pub const MAX_READ: u32 = 4 << 20;
25
26pub const MAX_WRITE: usize = 32 << 20;
28
29#[derive(Debug, Clone, PartialEq, Eq)]
31pub enum Request {
32 Hello,
34 Stat {
35 path: String,
36 },
37 List {
39 path: String,
40 offset: u32,
41 limit: u32,
42 },
43 Read {
45 path: String,
46 offset: u64,
47 len: u32,
48 },
49 Write {
52 path: String,
53 data: Vec<u8>,
54 },
55 Append {
59 path: String,
60 data: Vec<u8>,
61 },
62 Mkdir {
63 path: String,
64 parents: bool,
65 },
66 Rename {
67 from: String,
68 to: String,
69 },
70 Delete {
71 path: String,
72 recursive: bool,
73 },
74 Replace {
77 from: String,
78 to: String,
79 },
80 Realpath {
82 path: String,
83 },
84 SetMeta {
87 path: String,
88 mode: u32,
89 mtime_ms: i64,
90 },
91 Sync {
93 path: String,
94 },
95 Procs {
99 top: u32,
100 filter: String,
101 },
102 PaneProcs {
107 block_id: String,
108 session: String,
109 },
110}
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq)]
114pub enum PaneFound {
115 None = 0,
117 Session = 1,
119 Marker = 2,
121}
122
123impl PaneFound {
124 fn from_u8(v: u8) -> Self {
125 match v {
126 1 => Self::Session,
127 2 => Self::Marker,
128 _ => Self::None,
129 }
130 }
131}
132
133#[derive(Debug, Clone, PartialEq, Eq)]
136pub struct PaneFrame {
137 pub found: PaneFound,
138 pub processes: u32,
139 pub names: Vec<(String, u32)>,
141 pub started_at_ms: Option<u64>,
143 pub cpu_milli: Option<u64>,
146 pub mem: Option<u64>,
148 pub mem_resident: Option<u64>,
150 pub unmeasured: u32,
154 pub memory_metric: String,
155 pub cpu_count: u32,
157 pub host_cpu_milli: Option<u64>,
158 pub host_mem: u64,
159 pub host_processes: u32,
160 pub host_unmeasured: u32,
163}
164
165#[derive(Debug, Clone, PartialEq, Eq)]
168pub struct ProcRow {
169 pub pid: u32,
170 pub ppid: Option<u32>,
171 pub start_key: u64,
173 pub started_at_ms: Option<u64>,
174 pub name: String,
176 pub cpu_milli: Option<u32>,
178 pub mem: Option<u64>,
180 pub mem_resident: Option<u64>,
181}
182
183#[derive(Debug, Clone, PartialEq, Eq)]
185pub struct ProcFrame {
186 pub cpu_count: u32,
188 pub memory_metric: String,
189 pub total: u32,
191 pub matched: u32,
193 pub unmeasured: u32,
195 pub cpu_milli: Option<u64>,
198 pub mem: u64,
200 pub rows: Vec<ProcRow>,
201}
202
203#[derive(Debug, Clone, Copy, PartialEq, Eq)]
206pub enum Kind {
207 File = 1,
208 Dir = 2,
209 Other = 3,
210}
211
212#[derive(Debug, Clone, PartialEq, Eq)]
213pub struct Entry {
214 pub name: String,
216 pub kind: Kind,
217 pub size: u64,
218 pub mtime_ms: i64,
220 pub mode: u32,
222 pub symlink: bool,
223 pub link_target: String,
225}
226
227#[derive(Debug, Clone, Copy, PartialEq, Eq)]
229pub enum ErrKind {
230 NotFound = 1,
231 PermissionDenied = 2,
232 AlreadyExists = 3,
233 NotADirectory = 4,
234 IsADirectory = 5,
235 DirectoryNotEmpty = 6,
236 TooLarge = 7,
237 Invalid = 8,
238 Other = 255,
239}
240
241impl ErrKind {
242 pub fn from_io(e: &std::io::Error) -> Self {
243 use std::io::ErrorKind as K;
244 match e.kind() {
245 K::NotFound => ErrKind::NotFound,
246 K::PermissionDenied => ErrKind::PermissionDenied,
247 K::AlreadyExists => ErrKind::AlreadyExists,
248 K::InvalidInput | K::InvalidData => ErrKind::Invalid,
249 K::NotADirectory => ErrKind::NotADirectory,
252 K::IsADirectory => ErrKind::IsADirectory,
253 K::DirectoryNotEmpty => ErrKind::DirectoryNotEmpty,
254 _ => ErrKind::Other,
255 }
256 }
257
258 fn from_u8(b: u8) -> Self {
259 match b {
260 1 => ErrKind::NotFound,
261 2 => ErrKind::PermissionDenied,
262 3 => ErrKind::AlreadyExists,
263 4 => ErrKind::NotADirectory,
264 5 => ErrKind::IsADirectory,
265 6 => ErrKind::DirectoryNotEmpty,
266 7 => ErrKind::TooLarge,
267 8 => ErrKind::Invalid,
268 _ => ErrKind::Other,
269 }
270 }
271}
272
273#[derive(Debug, Clone, PartialEq, Eq)]
275pub enum Reply {
276 Hello {
277 protocol: u32,
278 home: String,
280 os: String,
282 },
283 Stat(Entry),
284 List {
285 entries: Vec<Entry>,
286 total: u32,
288 },
289 Read {
290 data: Vec<u8>,
291 eof: bool,
293 },
294 Done,
295 Path(String),
297 Procs(ProcFrame),
298 PaneProcs(PaneFrame),
299 Err {
300 kind: ErrKind,
301 message: String,
302 },
303}
304
305mod op {
306 pub const HELLO: u8 = 1;
307 pub const STAT: u8 = 2;
308 pub const LIST: u8 = 3;
309 pub const READ: u8 = 4;
310 pub const WRITE: u8 = 5;
311 pub const MKDIR: u8 = 6;
312 pub const RENAME: u8 = 7;
313 pub const DELETE: u8 = 8;
314 pub const APPEND: u8 = 9;
315 pub const REPLACE: u8 = 10;
316 pub const REALPATH: u8 = 11;
317 pub const SETMETA: u8 = 12;
318 pub const SYNC: u8 = 13;
319 pub const PROCS: u8 = 14;
320 pub const PANE_PROCS: u8 = 15;
321}
322
323mod status {
324 pub const HELLO: u8 = 1;
325 pub const STAT: u8 = 2;
326 pub const LIST: u8 = 3;
327 pub const READ: u8 = 4;
328 pub const DONE: u8 = 5;
329 pub const PATH: u8 = 6;
330 pub const PROCS: u8 = 7;
331 pub const PANE_PROCS: u8 = 8;
332 pub const ERR: u8 = 255;
333}
334
335struct W(Vec<u8>);
337
338impl W {
339 fn u8(&mut self, v: u8) -> &mut Self {
340 self.0.push(v);
341 self
342 }
343 fn u32(&mut self, v: u32) -> &mut Self {
344 self.0.extend_from_slice(&v.to_be_bytes());
345 self
346 }
347 fn u64(&mut self, v: u64) -> &mut Self {
348 self.0.extend_from_slice(&v.to_be_bytes());
349 self
350 }
351 fn i64(&mut self, v: i64) -> &mut Self {
352 self.0.extend_from_slice(&v.to_be_bytes());
353 self
354 }
355 fn bytes(&mut self, v: &[u8]) -> &mut Self {
356 self.u32(v.len() as u32);
357 self.0.extend_from_slice(v);
358 self
359 }
360 fn str(&mut self, v: &str) -> &mut Self {
361 self.bytes(v.as_bytes())
362 }
363 fn entry(&mut self, e: &Entry) -> &mut Self {
364 self.str(&e.name)
365 .u8(e.kind as u8)
366 .u64(e.size)
367 .i64(e.mtime_ms)
368 .u32(e.mode)
369 .u8(u8::from(e.symlink))
370 .str(&e.link_target)
371 }
372 fn opt_u32(&mut self, v: Option<u32>) -> &mut Self {
374 self.u32(v.unwrap_or(u32::MAX))
375 }
376 fn opt_u64(&mut self, v: Option<u64>) -> &mut Self {
377 self.u64(v.unwrap_or(u64::MAX))
378 }
379 fn proc_row(&mut self, p: &ProcRow) -> &mut Self {
380 self.u32(p.pid)
381 .opt_u32(p.ppid)
382 .u64(p.start_key)
383 .opt_u64(p.started_at_ms)
384 .str(&p.name)
385 .opt_u32(p.cpu_milli)
386 .opt_u64(p.mem)
387 .opt_u64(p.mem_resident)
388 }
389 fn message(self) -> Vec<u8> {
391 let mut out = Vec::with_capacity(4 + self.0.len());
392 out.extend_from_slice(&(self.0.len() as u32).to_be_bytes());
393 out.extend_from_slice(&self.0);
394 out
395 }
396}
397
398struct R<'a> {
400 p: &'a [u8],
401 i: usize,
402}
403
404impl<'a> R<'a> {
405 fn take(&mut self, n: usize) -> Result<&'a [u8], String> {
406 let end = self.i.checked_add(n).filter(|&e| e <= self.p.len());
407 let end = end.ok_or("message too short")?;
408 let s = &self.p[self.i..end];
409 self.i = end;
410 Ok(s)
411 }
412 fn u8(&mut self) -> Result<u8, String> {
413 Ok(self.take(1)?[0])
414 }
415 fn u32(&mut self) -> Result<u32, String> {
416 Ok(u32::from_be_bytes(self.take(4)?.try_into().unwrap()))
417 }
418 fn u64(&mut self) -> Result<u64, String> {
419 Ok(u64::from_be_bytes(self.take(8)?.try_into().unwrap()))
420 }
421 fn i64(&mut self) -> Result<i64, String> {
422 Ok(i64::from_be_bytes(self.take(8)?.try_into().unwrap()))
423 }
424 fn bytes(&mut self) -> Result<Vec<u8>, String> {
425 let n = self.u32()? as usize;
426 Ok(self.take(n)?.to_vec())
427 }
428 fn str(&mut self) -> Result<String, String> {
429 String::from_utf8(self.bytes()?).map_err(|_| "a string is not UTF-8".to_string())
430 }
431 fn entry(&mut self) -> Result<Entry, String> {
432 Ok(Entry {
433 name: self.str()?,
434 kind: match self.u8()? {
435 1 => Kind::File,
436 2 => Kind::Dir,
437 _ => Kind::Other,
438 },
439 size: self.u64()?,
440 mtime_ms: self.i64()?,
441 mode: self.u32()?,
442 symlink: self.u8()? != 0,
443 link_target: self.str()?,
444 })
445 }
446 fn opt_u32(&mut self) -> Result<Option<u32>, String> {
447 Ok(Some(self.u32()?).filter(|&v| v != u32::MAX))
448 }
449 fn opt_u64(&mut self) -> Result<Option<u64>, String> {
450 Ok(Some(self.u64()?).filter(|&v| v != u64::MAX))
451 }
452 fn proc_row(&mut self) -> Result<ProcRow, String> {
453 Ok(ProcRow {
454 pid: self.u32()?,
455 ppid: self.opt_u32()?,
456 start_key: self.u64()?,
457 started_at_ms: self.opt_u64()?,
458 name: self.str()?,
459 cpu_milli: self.opt_u32()?,
460 mem: self.opt_u64()?,
461 mem_resident: self.opt_u64()?,
462 })
463 }
464 fn done(&self) -> Result<(), String> {
465 if self.i == self.p.len() {
466 Ok(())
467 } else {
468 Err("trailing bytes in a message".into())
469 }
470 }
471}
472
473impl Request {
474 pub fn encode(&self, id: u32) -> Vec<u8> {
476 let mut w = W(Vec::new());
477 w.u32(id);
478 match self {
479 Request::Hello => {
480 w.u8(op::HELLO);
481 }
482 Request::Stat { path } => {
483 w.u8(op::STAT).str(path);
484 }
485 Request::List {
486 path,
487 offset,
488 limit,
489 } => {
490 w.u8(op::LIST).str(path).u32(*offset).u32(*limit);
491 }
492 Request::Read { path, offset, len } => {
493 w.u8(op::READ).str(path).u64(*offset).u32(*len);
494 }
495 Request::Write { path, data } => {
496 w.u8(op::WRITE).str(path).bytes(data);
497 }
498 Request::Append { path, data } => {
499 w.u8(op::APPEND).str(path).bytes(data);
500 }
501 Request::Mkdir { path, parents } => {
502 w.u8(op::MKDIR).str(path).u8(u8::from(*parents));
503 }
504 Request::Rename { from, to } => {
505 w.u8(op::RENAME).str(from).str(to);
506 }
507 Request::Delete { path, recursive } => {
508 w.u8(op::DELETE).str(path).u8(u8::from(*recursive));
509 }
510 Request::Replace { from, to } => {
511 w.u8(op::REPLACE).str(from).str(to);
512 }
513 Request::Realpath { path } => {
514 w.u8(op::REALPATH).str(path);
515 }
516 Request::SetMeta {
517 path,
518 mode,
519 mtime_ms,
520 } => {
521 w.u8(op::SETMETA).str(path).u32(*mode).i64(*mtime_ms);
522 }
523 Request::Sync { path } => {
524 w.u8(op::SYNC).str(path);
525 }
526 Request::Procs { top, filter } => {
527 w.u8(op::PROCS).u32(*top).str(filter);
528 }
529 Request::PaneProcs { block_id, session } => {
530 w.u8(op::PANE_PROCS).str(block_id).str(session);
531 }
532 }
533 w.message()
534 }
535
536 pub fn decode(body: &[u8]) -> Result<(u32, Request), String> {
538 let mut r = R { p: body, i: 0 };
539 let id = r.u32()?;
540 let req = match r.u8()? {
541 op::HELLO => Request::Hello,
542 op::STAT => Request::Stat { path: r.str()? },
543 op::LIST => Request::List {
544 path: r.str()?,
545 offset: r.u32()?,
546 limit: r.u32()?,
547 },
548 op::READ => Request::Read {
549 path: r.str()?,
550 offset: r.u64()?,
551 len: r.u32()?,
552 },
553 op::WRITE => Request::Write {
554 path: r.str()?,
555 data: r.bytes()?,
556 },
557 op::APPEND => Request::Append {
558 path: r.str()?,
559 data: r.bytes()?,
560 },
561 op::MKDIR => Request::Mkdir {
562 path: r.str()?,
563 parents: r.u8()? != 0,
564 },
565 op::RENAME => Request::Rename {
566 from: r.str()?,
567 to: r.str()?,
568 },
569 op::DELETE => Request::Delete {
570 path: r.str()?,
571 recursive: r.u8()? != 0,
572 },
573 op::REPLACE => Request::Replace {
574 from: r.str()?,
575 to: r.str()?,
576 },
577 op::REALPATH => Request::Realpath { path: r.str()? },
578 op::SYNC => Request::Sync { path: r.str()? },
579 op::PROCS => Request::Procs {
580 top: r.u32()?,
581 filter: r.str()?,
582 },
583 op::PANE_PROCS => Request::PaneProcs {
584 block_id: r.str()?,
585 session: r.str()?,
586 },
587 op::SETMETA => Request::SetMeta {
588 path: r.str()?,
589 mode: r.u32()?,
590 mtime_ms: r.i64()?,
591 },
592 other => return Err(format!("unknown request {other}")),
593 };
594 r.done()?;
595 Ok((id, req))
596 }
597}
598
599impl Reply {
600 pub fn encode(&self, id: u32) -> Vec<u8> {
602 let mut w = W(Vec::new());
603 w.u32(id);
604 match self {
605 Reply::Hello { protocol, home, os } => {
606 w.u8(status::HELLO).u32(*protocol).str(home).str(os);
607 }
608 Reply::Stat(e) => {
609 w.u8(status::STAT).entry(e);
610 }
611 Reply::List { entries, total } => {
612 w.u8(status::LIST).u32(*total).u32(entries.len() as u32);
613 for e in entries {
614 w.entry(e);
615 }
616 }
617 Reply::Read { data, eof } => {
618 w.u8(status::READ).u8(u8::from(*eof)).bytes(data);
619 }
620 Reply::Done => {
621 w.u8(status::DONE);
622 }
623 Reply::Path(p) => {
624 w.u8(status::PATH).str(p);
625 }
626 Reply::Procs(f) => {
627 w.u8(status::PROCS)
628 .u32(f.cpu_count)
629 .str(&f.memory_metric)
630 .u32(f.total)
631 .u32(f.matched)
632 .u32(f.unmeasured)
633 .opt_u64(f.cpu_milli)
634 .u64(f.mem)
635 .u32(f.rows.len() as u32);
636 for p in &f.rows {
637 w.proc_row(p);
638 }
639 }
640 Reply::PaneProcs(f) => {
641 w.u8(status::PANE_PROCS)
642 .u8(f.found as u8)
643 .u32(f.processes)
644 .u32(f.names.len() as u32);
645 for (name, count) in &f.names {
646 w.str(name).u32(*count);
647 }
648 w.opt_u64(f.started_at_ms)
649 .opt_u64(f.cpu_milli)
650 .opt_u64(f.mem)
651 .opt_u64(f.mem_resident)
652 .u32(f.unmeasured)
653 .str(&f.memory_metric)
654 .u32(f.cpu_count)
655 .opt_u64(f.host_cpu_milli)
656 .u64(f.host_mem)
657 .u32(f.host_processes)
658 .u32(f.host_unmeasured);
659 }
660 Reply::Err { kind, message } => {
661 w.u8(status::ERR).u8(*kind as u8).str(message);
662 }
663 }
664 w.message()
665 }
666
667 pub fn decode(body: &[u8]) -> Result<(u32, Reply), String> {
669 let mut r = R { p: body, i: 0 };
670 let id = r.u32()?;
671 let reply = match r.u8()? {
672 status::HELLO => Reply::Hello {
673 protocol: r.u32()?,
674 home: r.str()?,
675 os: r.str()?,
676 },
677 status::STAT => Reply::Stat(r.entry()?),
678 status::LIST => {
679 let total = r.u32()?;
680 let n = r.u32()? as usize;
681 if n > (body.len() / 30) {
683 return Err("list: impossible entry count".into());
684 }
685 let mut entries = Vec::with_capacity(n);
686 for _ in 0..n {
687 entries.push(r.entry()?);
688 }
689 Reply::List { entries, total }
690 }
691 status::READ => Reply::Read {
692 eof: r.u8()? != 0,
693 data: r.bytes()?,
694 },
695 status::DONE => Reply::Done,
696 status::PATH => Reply::Path(r.str()?),
697 status::PROCS => {
698 let (cpu_count, memory_metric) = (r.u32()?, r.str()?);
699 let (total, matched, unmeasured) = (r.u32()?, r.u32()?, r.u32()?);
700 let (cpu_milli, mem) = (r.opt_u64()?, r.u64()?);
701 let n = r.u32()? as usize;
702 if n > body.len() / 48 {
704 return Err("procs: impossible row count".into());
705 }
706 let mut rows = Vec::with_capacity(n);
707 for _ in 0..n {
708 rows.push(r.proc_row()?);
709 }
710 Reply::Procs(ProcFrame {
711 cpu_count,
712 memory_metric,
713 total,
714 matched,
715 unmeasured,
716 cpu_milli,
717 mem,
718 rows,
719 })
720 }
721 status::PANE_PROCS => {
722 let (found, processes) = (PaneFound::from_u8(r.u8()?), r.u32()?);
723 let n = r.u32()? as usize;
724 if n > body.len() / 8 {
726 return Err("pane procs: impossible name count".into());
727 }
728 let mut names = Vec::with_capacity(n);
729 for _ in 0..n {
730 names.push((r.str()?, r.u32()?));
731 }
732 Reply::PaneProcs(PaneFrame {
733 found,
734 processes,
735 names,
736 started_at_ms: r.opt_u64()?,
737 cpu_milli: r.opt_u64()?,
738 mem: r.opt_u64()?,
739 mem_resident: r.opt_u64()?,
740 unmeasured: r.u32()?,
741 memory_metric: r.str()?,
742 cpu_count: r.u32()?,
743 host_cpu_milli: r.opt_u64()?,
744 host_mem: r.u64()?,
745 host_processes: r.u32()?,
746 host_unmeasured: r.u32()?,
747 })
748 }
749 status::ERR => Reply::Err {
750 kind: ErrKind::from_u8(r.u8()?),
751 message: r.str()?,
752 },
753 other => return Err(format!("unknown reply {other}")),
754 };
755 r.done()?;
756 Ok((id, reply))
757 }
758}
759
760#[derive(Debug, Default)]
762pub struct Splitter {
763 buf: Vec<u8>,
764}
765
766impl Splitter {
767 pub fn new() -> Self {
768 Self::default()
769 }
770
771 pub fn push(&mut self, bytes: &[u8]) -> Result<Vec<Vec<u8>>, String> {
774 self.buf.extend_from_slice(bytes);
775 let mut bodies = Vec::new();
776 let mut at = 0;
777 while self.buf.len() - at >= 4 {
778 let len = u32::from_be_bytes(self.buf[at..at + 4].try_into().unwrap()) as usize;
779 if len > MAX_MESSAGE {
780 return Err(format!("a message of {len} bytes is over the limit"));
781 }
782 if self.buf.len() - at - 4 < len {
783 break;
784 }
785 bodies.push(self.buf[at + 4..at + 4 + len].to_vec());
786 at += 4 + len;
787 }
788 self.buf.drain(..at);
789 Ok(bodies)
790 }
791}
792
793#[cfg(test)]
794mod tests {
795 use super::*;
796
797 fn entry(name: &str) -> Entry {
798 Entry {
799 name: name.into(),
800 kind: Kind::File,
801 size: 42,
802 mtime_ms: 1_700_000_000_000,
803 mode: 0o644,
804 symlink: true,
805 link_target: "../elsewhere".into(),
806 }
807 }
808
809 #[test]
810 fn every_request_and_reply_survives_the_wire_in_any_pieces() {
811 let requests = vec![
812 Request::Hello,
813 Request::Stat {
814 path: "~/a b".into(),
815 },
816 Request::List {
817 path: "/etc".into(),
818 offset: 100,
819 limit: 50,
820 },
821 Request::Read {
822 path: "/x".into(),
823 offset: 1 << 40,
824 len: 4096,
825 },
826 Request::Write {
827 path: "/x".into(),
828 data: vec![0, 1, 2, 255],
829 },
830 Request::Append {
831 path: "/x".into(),
832 data: vec![3, 4],
833 },
834 Request::Mkdir {
835 path: "/d".into(),
836 parents: true,
837 },
838 Request::Rename {
839 from: "/a".into(),
840 to: "/b".into(),
841 },
842 Request::Delete {
843 path: "/d".into(),
844 recursive: false,
845 },
846 Request::Replace {
847 from: "/t".into(),
848 to: "/x".into(),
849 },
850 Request::Realpath { path: "~/l".into() },
851 Request::Sync { path: "/x".into() },
852 Request::SetMeta {
853 path: "/x".into(),
854 mode: 0o755,
855 mtime_ms: 1_700_000_000_000,
856 },
857 Request::Procs {
858 top: 100,
859 filter: "node agent".into(),
860 },
861 Request::PaneProcs {
862 block_id: "0b6c-block".into(),
863 session: "".into(),
864 },
865 ];
866 let replies = vec![
867 Reply::Hello {
868 protocol: PROTOCOL,
869 home: "/home/u".into(),
870 os: "linux".into(),
871 },
872 Reply::Stat(entry("a")),
873 Reply::List {
874 entries: vec![entry("a"), entry("ü")],
875 total: 7,
876 },
877 Reply::Read {
878 data: b"hello".to_vec(),
879 eof: true,
880 },
881 Reply::Done,
882 Reply::Path("/home/u/a".into()),
883 Reply::Err {
884 kind: ErrKind::NotFound,
885 message: "no such file".into(),
886 },
887 Reply::Procs(ProcFrame {
888 cpu_count: 8,
889 memory_metric: "anonymous resident + swap".into(),
890 total: 300,
891 matched: 2,
892 unmeasured: 1,
893 cpu_milli: Some(1500),
894 mem: 1 << 30,
895 rows: vec![
896 ProcRow {
897 pid: 42,
898 ppid: Some(1),
899 start_key: 98_765,
900 started_at_ms: Some(1_700_000_000_000),
901 name: "node".into(),
902 cpu_milli: Some(1000),
903 mem: Some(200 << 20),
904 mem_resident: Some(300 << 20),
905 },
906 ProcRow {
908 pid: 1,
909 ppid: None,
910 start_key: 0,
911 started_at_ms: None,
912 name: "launchd".into(),
913 cpu_milli: None,
914 mem: None,
915 mem_resident: None,
916 },
917 ],
918 }),
919 Reply::PaneProcs(PaneFrame {
920 found: PaneFound::Marker,
921 processes: 3,
922 names: vec![("bash".into(), 1), ("node".into(), 2)],
923 started_at_ms: Some(1_700_000_000_000),
924 cpu_milli: Some(250),
925 mem: Some(80 << 20),
926 mem_resident: Some(120 << 20),
927 unmeasured: 1,
928 memory_metric: "anonymous resident + swap".into(),
929 cpu_count: 16,
930 host_cpu_milli: None,
931 host_mem: 6 << 30,
932 host_processes: 412,
933 host_unmeasured: 0,
934 }),
935 Reply::PaneProcs(PaneFrame {
937 found: PaneFound::None,
938 processes: 0,
939 names: vec![],
940 started_at_ms: None,
941 cpu_milli: None,
942 mem: None,
943 mem_resident: None,
944 unmeasured: 0,
945 memory_metric: "memory footprint".into(),
946 cpu_count: 8,
947 host_cpu_milli: Some(3_100),
948 host_mem: 9 << 30,
949 host_processes: 600,
950 host_unmeasured: 210,
951 }),
952 ];
953 let mut stream = Vec::new();
954 for (i, r) in requests.iter().enumerate() {
955 stream.extend(r.encode(i as u32));
956 }
957 let mut s = Splitter::new();
958 let mut bodies = Vec::new();
959 for piece in stream.chunks(3) {
960 bodies.extend(s.push(piece).unwrap());
961 }
962 let got: Vec<(u32, Request)> = bodies.iter().map(|b| Request::decode(b).unwrap()).collect();
963 assert_eq!(
964 got,
965 requests
966 .into_iter()
967 .enumerate()
968 .map(|(i, r)| (i as u32, r))
969 .collect::<Vec<_>>()
970 );
971
972 for (i, r) in replies.iter().enumerate() {
973 let msg = r.encode(i as u32 + 9);
974 let (id, back) = Reply::decode(&msg[4..]).unwrap();
975 assert_eq!((id, &back), (i as u32 + 9, r));
976 }
977 }
978
979 #[test]
980 fn a_broken_stream_is_refused_not_guessed_at() {
981 assert!(Splitter::new().push(&u32::MAX.to_be_bytes()).is_err());
982 assert!(Request::decode(&[0, 0, 0, 1, 99]).is_err());
983 assert!(Request::decode(&[0, 0, 0, 1]).is_err());
984 assert!(Request::decode(&[0, 0, 0, 1, 2, 0, 0, 0, 9, b'a']).is_err());
986 assert!(Request::decode(&[0, 0, 0, 1, 1, 7]).is_err());
988 let mut body = vec![0, 0, 0, 1, 3];
990 body.extend_from_slice(&5u32.to_be_bytes());
991 body.extend_from_slice(&1_000_000u32.to_be_bytes());
992 assert!(Reply::decode(&body).is_err());
993 }
994}