1use std::io::{self, Read, Write};
7use thiserror::Error;
8
9pub const WORKER_MAGIC_1: u64 = 0x6e697863; pub const WORKER_MAGIC_2: u64 = 0x6478696f; pub const PROTOCOL_VERSION: u64 = (1 << 8) | 37; #[derive(Debug, Error)]
18pub enum WireError {
19 #[error("io error: {0}")]
20 Io(#[from] io::Error),
21 #[error("protocol error: {0}")]
22 Protocol(String),
23 #[error("unexpected magic: expected {expected:#x}, got {got:#x}")]
24 BadMagic { expected: u64, got: u64 },
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29#[non_exhaustive]
30#[repr(u64)]
31pub enum WorkerOp {
32 IsValidPath = 1,
33 HasSubstitutes = 3,
34 QueryPathHash = 4,
35 QueryReferences = 5,
36 QueryReferrers = 6,
37 AddToStore = 7,
38 AddTextToStore = 8,
39 BuildPaths = 9,
40 EnsurePath = 10,
41 AddTempRoot = 11,
42 AddIndirectRoot = 12,
43 SyncWithGC = 13,
44 FindRoots = 14,
45 ExportPath = 16,
46 QueryDeriver = 18,
47 SetOptions = 19,
48 CollectGarbage = 20,
49 QuerySubstitutablePathInfo = 21,
50 QueryDerivationOutputs = 22,
51 QueryAllValidPaths = 23,
52 QueryFailedPaths = 24,
53 ClearFailedPaths = 25,
54 QueryPathInfo = 26,
55 ImportPaths = 27,
56 QueryDerivationOutputNames = 28,
57 QueryPathFromHashPart = 29,
58 QuerySubstitutablePathInfos = 30,
59 QueryValidPaths = 31,
60 QuerySubstitutablePaths = 32,
61 QueryValidDerivers = 33,
62 OptimiseStore = 34,
63 VerifyStore = 35,
64 BuildDerivation = 36,
65 AddSignatures = 37,
66 NarFromPath = 38,
67 AddToStoreNar = 39,
68 QueryMissing = 40,
69 QueryDerivationOutputMap = 41,
70 RegisterDrvOutput = 42,
71 QueryRealisation = 43,
72 AddMultipleToStore = 44,
73 AddBuildLog = 45,
74}
75
76impl WorkerOp {
77 #[must_use]
79 pub fn from_u64(v: u64) -> Option<Self> {
80 Self::try_from(v).ok()
81 }
82}
83
84impl TryFrom<u64> for WorkerOp {
85 type Error = WireError;
86
87 fn try_from(v: u64) -> Result<Self, Self::Error> {
88 match v {
89 1 => Ok(Self::IsValidPath),
90 3 => Ok(Self::HasSubstitutes),
91 4 => Ok(Self::QueryPathHash),
92 5 => Ok(Self::QueryReferences),
93 6 => Ok(Self::QueryReferrers),
94 7 => Ok(Self::AddToStore),
95 8 => Ok(Self::AddTextToStore),
96 9 => Ok(Self::BuildPaths),
97 10 => Ok(Self::EnsurePath),
98 11 => Ok(Self::AddTempRoot),
99 12 => Ok(Self::AddIndirectRoot),
100 13 => Ok(Self::SyncWithGC),
101 14 => Ok(Self::FindRoots),
102 16 => Ok(Self::ExportPath),
103 18 => Ok(Self::QueryDeriver),
104 19 => Ok(Self::SetOptions),
105 20 => Ok(Self::CollectGarbage),
106 21 => Ok(Self::QuerySubstitutablePathInfo),
107 22 => Ok(Self::QueryDerivationOutputs),
108 23 => Ok(Self::QueryAllValidPaths),
109 24 => Ok(Self::QueryFailedPaths),
110 25 => Ok(Self::ClearFailedPaths),
111 26 => Ok(Self::QueryPathInfo),
112 27 => Ok(Self::ImportPaths),
113 28 => Ok(Self::QueryDerivationOutputNames),
114 29 => Ok(Self::QueryPathFromHashPart),
115 30 => Ok(Self::QuerySubstitutablePathInfos),
116 31 => Ok(Self::QueryValidPaths),
117 32 => Ok(Self::QuerySubstitutablePaths),
118 33 => Ok(Self::QueryValidDerivers),
119 34 => Ok(Self::OptimiseStore),
120 35 => Ok(Self::VerifyStore),
121 36 => Ok(Self::BuildDerivation),
122 37 => Ok(Self::AddSignatures),
123 38 => Ok(Self::NarFromPath),
124 39 => Ok(Self::AddToStoreNar),
125 40 => Ok(Self::QueryMissing),
126 41 => Ok(Self::QueryDerivationOutputMap),
127 42 => Ok(Self::RegisterDrvOutput),
128 43 => Ok(Self::QueryRealisation),
129 44 => Ok(Self::AddMultipleToStore),
130 45 => Ok(Self::AddBuildLog),
131 _ => Err(WireError::Protocol(format!("unknown worker op: {v}"))),
132 }
133 }
134}
135
136#[derive(Debug, Clone, Copy, PartialEq, Eq)]
138#[non_exhaustive]
139#[repr(u64)]
140pub enum StderrMsg {
141 Write = 0x6f6c6d67, Last = 0x616c7473, Error = 0x63787470, StartActivity = 0x53545254, StopActivity = 0x53544f50, Result = 0x52534c54, }
154
155pub fn write_u64(w: &mut impl Write, v: u64) -> io::Result<()> {
159 w.write_all(&v.to_le_bytes())
160}
161
162pub fn read_u64(r: &mut impl Read) -> io::Result<u64> {
164 let mut buf = [0u8; 8];
165 r.read_exact(&mut buf)?;
166 Ok(u64::from_le_bytes(buf))
167}
168
169const PADDING: [u8; 8] = [0u8; 8];
171
172pub fn write_bytes(w: &mut impl Write, data: &[u8]) -> io::Result<()> {
174 write_u64(w, data.len() as u64)?;
175 w.write_all(data)?;
176 let pad = (8 - (data.len() % 8)) % 8;
177 if pad > 0 {
178 w.write_all(&PADDING[..pad])?;
179 }
180 Ok(())
181}
182
183pub fn read_bytes(r: &mut impl Read) -> io::Result<Vec<u8>> {
185 let len = read_u64(r)? as usize;
186 let mut buf = vec![0u8; len];
187 r.read_exact(&mut buf)?;
188 let pad = (8 - (len % 8)) % 8;
189 if pad > 0 {
190 let mut pad_buf = [0u8; 8];
191 r.read_exact(&mut pad_buf[..pad])?;
192 }
193 Ok(buf)
194}
195
196pub fn write_string(w: &mut impl Write, s: impl AsRef<str>) -> io::Result<()> {
198 write_bytes(w, s.as_ref().as_bytes())
199}
200
201pub fn read_string(r: &mut impl Read) -> Result<String, WireError> {
203 let bytes = read_bytes(r)?;
204 String::from_utf8(bytes).map_err(|e| WireError::Protocol(format!("invalid UTF-8: {e}")))
205}
206
207pub fn write_bool(w: &mut impl Write, v: bool) -> io::Result<()> {
209 write_u64(w, u64::from(v))
210}
211
212pub fn read_bool(r: &mut impl Read) -> io::Result<bool> {
214 Ok(read_u64(r)? != 0)
215}
216
217pub fn write_string_list(w: &mut impl Write, list: &[impl AsRef<str>]) -> io::Result<()> {
219 write_u64(w, list.len() as u64)?;
220 for s in list {
221 write_string(w, s)?;
222 }
223 Ok(())
224}
225
226pub fn read_string_list(r: &mut impl Read) -> Result<Vec<String>, WireError> {
228 let count = read_u64(r)? as usize;
229 (0..count).map(|_| read_string(r)).collect()
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235 use std::io::Cursor;
236
237 #[test]
238 fn u64_roundtrip() {
239 let mut buf = Vec::new();
240 write_u64(&mut buf, 42).unwrap();
241 assert_eq!(buf.len(), 8);
242 let v = read_u64(&mut Cursor::new(&buf)).unwrap();
243 assert_eq!(v, 42);
244 }
245
246 #[test]
247 fn bytes_roundtrip() {
248 let data = b"hello";
249 let mut buf = Vec::new();
250 write_bytes(&mut buf, data).unwrap();
251 assert_eq!(buf.len(), 16);
253 let read = read_bytes(&mut Cursor::new(&buf)).unwrap();
254 assert_eq!(read, data);
255 }
256
257 #[test]
258 fn string_roundtrip() {
259 let s = "hello world";
260 let mut buf = Vec::new();
261 write_string(&mut buf, s).unwrap();
262 let read = read_string(&mut Cursor::new(&buf)).unwrap();
263 assert_eq!(read, s);
264 }
265
266 #[test]
267 fn bool_roundtrip() {
268 for v in [true, false] {
269 let mut buf = Vec::new();
270 write_bool(&mut buf, v).unwrap();
271 let read = read_bool(&mut Cursor::new(&buf)).unwrap();
272 assert_eq!(read, v);
273 }
274 }
275
276 #[test]
277 fn string_list_roundtrip() {
278 let list = vec!["foo".to_string(), "bar".to_string(), "baz".to_string()];
279 let mut buf = Vec::new();
280 write_string_list(&mut buf, &list).unwrap();
281 let read = read_string_list(&mut Cursor::new(&buf)).unwrap();
282 assert_eq!(read, list);
283 }
284
285 #[test]
286 fn empty_string() {
287 let mut buf = Vec::new();
288 write_string(&mut buf, "").unwrap();
289 assert_eq!(buf.len(), 8); let read = read_string(&mut Cursor::new(&buf)).unwrap();
291 assert_eq!(read, "");
292 }
293
294 #[test]
295 fn worker_op_from_u64() {
296 assert_eq!(WorkerOp::from_u64(1), Some(WorkerOp::IsValidPath));
297 assert_eq!(WorkerOp::from_u64(26), Some(WorkerOp::QueryPathInfo));
298 assert_eq!(WorkerOp::from_u64(9999), None);
299 }
300
301 #[test]
302 fn magic_constants() {
303 assert_eq!(&WORKER_MAGIC_1.to_le_bytes()[..4], b"cxin");
305 assert_eq!(&WORKER_MAGIC_2.to_le_bytes()[..4], b"oixd");
306 }
307
308 #[test]
309 fn eight_byte_alignment() {
310 let mut buf = Vec::new();
312 write_bytes(&mut buf, b"abc").unwrap();
313 assert_eq!(buf.len() % 8, 0);
314 assert_eq!(buf.len(), 16); }
316
317 #[test]
318 fn large_string_roundtrip() {
319 let s: String = "abcdefghij".repeat(120); assert!(s.len() >= 1024);
322 let mut buf = Vec::new();
323 write_string(&mut buf, &s).unwrap();
324 assert_eq!(buf.len() % 8, 0);
325 let read = read_string(&mut Cursor::new(&buf)).unwrap();
326 assert_eq!(read, s);
327 }
328
329 #[test]
330 fn empty_list_roundtrip() {
331 let list: Vec<String> = vec![];
332 let mut buf = Vec::new();
333 write_string_list(&mut buf, &list).unwrap();
334 assert_eq!(buf.len(), 8);
336 let read = read_string_list(&mut Cursor::new(&buf)).unwrap();
337 assert!(read.is_empty());
338 }
339
340 #[test]
341 fn binary_data_with_zero_bytes() {
342 let data: Vec<u8> = vec![0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x01, 0x00];
343 let mut buf = Vec::new();
344 write_bytes(&mut buf, &data).unwrap();
345 let read = read_bytes(&mut Cursor::new(&buf)).unwrap();
346 assert_eq!(read, data);
347 }
348
349 #[test]
350 fn protocol_version_constant() {
351 assert_eq!(PROTOCOL_VERSION, (1 << 8) | 37);
353 assert_eq!(PROTOCOL_VERSION, 293);
354 let major = PROTOCOL_VERSION >> 8;
356 let minor = PROTOCOL_VERSION & 0xFF;
357 assert_eq!(major, 1);
358 assert_eq!(minor, 37);
359 }
360
361 #[test]
362 fn large_u64_roundtrip() {
363 let mut buf = Vec::new();
364 write_u64(&mut buf, u64::MAX).unwrap();
365 let v = read_u64(&mut Cursor::new(&buf)).unwrap();
366 assert_eq!(v, u64::MAX);
367 }
368
369 #[test]
370 fn bytes_exact_8_byte_size() {
371 let data = b"12345678";
373 let mut buf = Vec::new();
374 write_bytes(&mut buf, data).unwrap();
375 assert_eq!(buf.len(), 16);
377 let read = read_bytes(&mut Cursor::new(&buf)).unwrap();
378 assert_eq!(read, data);
379 }
380
381 #[test]
382 fn worker_op_all_valid_codes() {
383 let valid_ops: Vec<(u64, WorkerOp)> = vec![
384 (1, WorkerOp::IsValidPath),
385 (7, WorkerOp::AddToStore),
386 (9, WorkerOp::BuildPaths),
387 (26, WorkerOp::QueryPathInfo),
388 (38, WorkerOp::NarFromPath),
389 (45, WorkerOp::AddBuildLog),
390 ];
391 for (code, expected) in valid_ops {
392 assert_eq!(WorkerOp::from_u64(code), Some(expected));
393 }
394 }
395
396 #[test]
397 fn worker_op_invalid_codes() {
398 assert_eq!(WorkerOp::from_u64(0), None);
399 assert_eq!(WorkerOp::from_u64(2), None);
400 assert_eq!(WorkerOp::from_u64(15), None);
401 assert_eq!(WorkerOp::from_u64(17), None);
402 assert_eq!(WorkerOp::from_u64(46), None);
403 assert_eq!(WorkerOp::from_u64(u64::MAX), None);
404 }
405
406 #[test]
409 fn u64_zero_roundtrip() {
410 let mut buf = Vec::new();
411 write_u64(&mut buf, 0).unwrap();
412 let v = read_u64(&mut Cursor::new(&buf)).unwrap();
413 assert_eq!(v, 0);
414 }
415
416 #[test]
417 fn bytes_empty_roundtrip() {
418 let mut buf = Vec::new();
419 write_bytes(&mut buf, &[]).unwrap();
420 assert_eq!(buf.len(), 8);
421 let read = read_bytes(&mut Cursor::new(&buf)).unwrap();
422 assert!(read.is_empty());
423 }
424
425 #[test]
426 fn string_with_unicode() {
427 let s = "日本語テスト 🎉";
428 let mut buf = Vec::new();
429 write_string(&mut buf, s).unwrap();
430 assert_eq!(buf.len() % 8, 0);
431 let read = read_string(&mut Cursor::new(&buf)).unwrap();
432 assert_eq!(read, s);
433 }
434
435 #[test]
436 fn string_list_single_entry() {
437 let list = vec!["only-one".to_string()];
438 let mut buf = Vec::new();
439 write_string_list(&mut buf, &list).unwrap();
440 let read = read_string_list(&mut Cursor::new(&buf)).unwrap();
441 assert_eq!(read, list);
442 }
443
444 #[test]
445 fn string_list_many_entries() {
446 let list: Vec<String> = (0..100).map(|i| format!("item-{i}")).collect();
447 let mut buf = Vec::new();
448 write_string_list(&mut buf, &list).unwrap();
449 let read = read_string_list(&mut Cursor::new(&buf)).unwrap();
450 assert_eq!(read, list);
451 }
452
453 #[test]
454 fn worker_op_exhaustive_coverage() {
455 let all: Vec<(u64, WorkerOp)> = vec![
456 (1, WorkerOp::IsValidPath),
457 (3, WorkerOp::HasSubstitutes),
458 (4, WorkerOp::QueryPathHash),
459 (5, WorkerOp::QueryReferences),
460 (6, WorkerOp::QueryReferrers),
461 (7, WorkerOp::AddToStore),
462 (8, WorkerOp::AddTextToStore),
463 (9, WorkerOp::BuildPaths),
464 (10, WorkerOp::EnsurePath),
465 (11, WorkerOp::AddTempRoot),
466 (12, WorkerOp::AddIndirectRoot),
467 (13, WorkerOp::SyncWithGC),
468 (14, WorkerOp::FindRoots),
469 (16, WorkerOp::ExportPath),
470 (18, WorkerOp::QueryDeriver),
471 (19, WorkerOp::SetOptions),
472 (20, WorkerOp::CollectGarbage),
473 (21, WorkerOp::QuerySubstitutablePathInfo),
474 (22, WorkerOp::QueryDerivationOutputs),
475 (23, WorkerOp::QueryAllValidPaths),
476 (24, WorkerOp::QueryFailedPaths),
477 (25, WorkerOp::ClearFailedPaths),
478 (26, WorkerOp::QueryPathInfo),
479 (27, WorkerOp::ImportPaths),
480 (28, WorkerOp::QueryDerivationOutputNames),
481 (29, WorkerOp::QueryPathFromHashPart),
482 (30, WorkerOp::QuerySubstitutablePathInfos),
483 (31, WorkerOp::QueryValidPaths),
484 (32, WorkerOp::QuerySubstitutablePaths),
485 (33, WorkerOp::QueryValidDerivers),
486 (34, WorkerOp::OptimiseStore),
487 (35, WorkerOp::VerifyStore),
488 (36, WorkerOp::BuildDerivation),
489 (37, WorkerOp::AddSignatures),
490 (38, WorkerOp::NarFromPath),
491 (39, WorkerOp::AddToStoreNar),
492 (40, WorkerOp::QueryMissing),
493 (41, WorkerOp::QueryDerivationOutputMap),
494 (42, WorkerOp::RegisterDrvOutput),
495 (43, WorkerOp::QueryRealisation),
496 (44, WorkerOp::AddMultipleToStore),
497 (45, WorkerOp::AddBuildLog),
498 ];
499 for (code, expected) in &all {
500 assert_eq!(WorkerOp::from_u64(*code), Some(*expected), "failed for code {code}");
501 }
502 assert_eq!(all.len(), 42);
503 }
504
505 #[test]
506 fn read_u64_truncated_input() {
507 let result = read_u64(&mut Cursor::new(&[0u8; 4]));
508 assert!(result.is_err());
509 }
510
511 #[test]
512 fn read_bytes_truncated_data() {
513 let mut buf = Vec::new();
514 write_u64(&mut buf, 100).unwrap();
515 buf.extend_from_slice(&[0u8; 10]);
516 let result = read_bytes(&mut Cursor::new(&buf));
517 assert!(result.is_err());
518 }
519
520 #[test]
523 fn write_bytes_alignment_for_each_residue() {
524 for len in 0..16 {
526 let data = vec![0xAA_u8; len];
527 let mut buf = Vec::new();
528 write_bytes(&mut buf, &data).unwrap();
529 assert_eq!(buf.len() % 8, 0, "len={len} not 8-byte aligned");
530 let aligned_data_size = ((len + 7) / 8) * 8;
532 assert_eq!(buf.len(), 8 + aligned_data_size);
533 }
534 }
535
536 #[test]
537 fn write_bytes_padding_is_zeros() {
538 let mut buf = Vec::new();
540 write_bytes(&mut buf, b"hello").unwrap();
541 assert_eq!(&buf[8..13], b"hello");
543 assert_eq!(&buf[13..16], &[0u8, 0u8, 0u8]);
544 }
545
546 #[test]
547 fn read_bytes_alignment_for_each_residue() {
548 for len in 0_usize..16 {
549 let data = vec![0xCC_u8; len];
550 let mut buf = Vec::new();
551 write_bytes(&mut buf, &data).unwrap();
552 let read = read_bytes(&mut Cursor::new(&buf)).unwrap();
553 assert_eq!(read, data, "roundtrip failed for len={len}");
554 }
555 }
556
557 #[test]
560 fn read_string_invalid_utf8_returns_protocol_error() {
561 let mut buf = Vec::new();
562 write_bytes(&mut buf, &[0xFF, 0xFE, 0xFD]).unwrap();
564 let result = read_string(&mut Cursor::new(&buf));
565 assert!(result.is_err());
566 match result {
567 Err(WireError::Protocol(_)) => {}
568 other => panic!("expected Protocol error, got {other:?}"),
569 }
570 }
571
572 #[test]
575 fn worker_op_try_from_known() {
576 let op: WorkerOp = WorkerOp::try_from(26).unwrap();
577 assert_eq!(op, WorkerOp::QueryPathInfo);
578 }
579
580 #[test]
581 fn worker_op_try_from_unknown_returns_protocol_error() {
582 let result = WorkerOp::try_from(999u64);
583 assert!(result.is_err());
584 match result {
585 Err(WireError::Protocol(s)) => assert!(s.contains("999")),
586 other => panic!("expected Protocol error, got {other:?}"),
587 }
588 }
589
590 #[test]
593 fn stderr_msg_constants_distinct() {
594 let codes = [
595 StderrMsg::Write as u64,
596 StderrMsg::Last as u64,
597 StderrMsg::Error as u64,
598 StderrMsg::StartActivity as u64,
599 StderrMsg::StopActivity as u64,
600 StderrMsg::Result as u64,
601 ];
602 for i in 0..codes.len() {
604 for j in (i + 1)..codes.len() {
605 assert_ne!(codes[i], codes[j]);
606 }
607 }
608 }
609
610 #[test]
611 fn stderr_msg_eq_clone_copy() {
612 let a = StderrMsg::Write;
613 let b = a;
614 let c = a;
615 assert_eq!(a, b);
616 assert_eq!(b, c);
617 }
618
619 #[test]
622 fn worker_magic_constants_distinct() {
623 assert_ne!(WORKER_MAGIC_1, WORKER_MAGIC_2);
624 }
625
626 #[test]
629 fn read_bool_nonzero_is_true() {
630 let mut buf = Vec::new();
631 write_u64(&mut buf, 42).unwrap();
632 let v = read_bool(&mut Cursor::new(&buf)).unwrap();
633 assert!(v);
634 }
635
636 #[test]
637 fn read_bool_zero_is_false() {
638 let mut buf = Vec::new();
639 write_u64(&mut buf, 0).unwrap();
640 let v = read_bool(&mut Cursor::new(&buf)).unwrap();
641 assert!(!v);
642 }
643
644 #[test]
647 fn read_string_list_truncated_count_returns_error() {
648 let buf = [0u8; 4];
649 let result = read_string_list(&mut Cursor::new(&buf));
650 assert!(result.is_err());
651 }
652
653 #[test]
654 fn read_string_list_count_exceeds_data_returns_error() {
655 let mut buf = Vec::new();
656 write_u64(&mut buf, 100).unwrap(); let result = read_string_list(&mut Cursor::new(&buf));
658 assert!(result.is_err());
659 }
660
661 #[test]
664 fn write_string_list_mixed_lengths() {
665 let list = vec![
666 String::new(),
667 "a".to_string(),
668 "ab".to_string(),
669 "abcdefgh".to_string(),
670 "abcdefghi".to_string(), ];
672 let mut buf = Vec::new();
673 write_string_list(&mut buf, &list).unwrap();
674 let read = read_string_list(&mut Cursor::new(&buf)).unwrap();
675 assert_eq!(read, list);
676 }
677
678 #[test]
681 fn write_string_accepts_str_types() {
682 let mut buf1 = Vec::new();
683 write_string(&mut buf1, "hello").unwrap();
684
685 let mut buf2 = Vec::new();
686 write_string(&mut buf2, String::from("hello")).unwrap();
687
688 let mut buf3 = Vec::new();
689 let s = String::from("hello");
690 write_string(&mut buf3, &s).unwrap();
691
692 assert_eq!(buf1, buf2);
693 assert_eq!(buf2, buf3);
694 }
695
696 #[test]
699 fn bytes_length_seven_padding() {
700 let data = b"1234567";
702 let mut buf = Vec::new();
703 write_bytes(&mut buf, data).unwrap();
704 assert_eq!(buf.len(), 16);
706 let read = read_bytes(&mut Cursor::new(&buf)).unwrap();
707 assert_eq!(read, data);
708 }
709
710 #[test]
711 fn bytes_length_one_padding() {
712 let data = b"x";
714 let mut buf = Vec::new();
715 write_bytes(&mut buf, data).unwrap();
716 assert_eq!(buf.len(), 16);
718 let read = read_bytes(&mut Cursor::new(&buf)).unwrap();
719 assert_eq!(read, data);
720 }
721
722 #[test]
723 fn bytes_length_nine_padding() {
724 let data = b"123456789";
726 let mut buf = Vec::new();
727 write_bytes(&mut buf, data).unwrap();
728 assert_eq!(buf.len(), 24);
730 let read = read_bytes(&mut Cursor::new(&buf)).unwrap();
731 assert_eq!(read, data);
732 }
733
734 #[test]
737 fn u64_written_in_little_endian() {
738 let mut buf = Vec::new();
739 write_u64(&mut buf, 0x0102_0304_0506_0708).unwrap();
740 assert_eq!(buf, vec![0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]);
742 }
743
744 #[test]
747 fn protocol_version_major_minor_extraction() {
748 let major = PROTOCOL_VERSION >> 8;
749 let minor = PROTOCOL_VERSION & 0xFF;
750 assert_eq!(major, 1);
751 assert_eq!(minor, 37);
752 }
753
754 #[test]
757 fn worker_op_from_u64_matches_try_from() {
758 for code in [1, 3, 7, 26, 38, 45] {
759 let from_u64 = WorkerOp::from_u64(code);
760 let try_from = WorkerOp::try_from(code).ok();
761 assert_eq!(from_u64, try_from);
762 }
763 assert_eq!(WorkerOp::from_u64(0), None);
765 assert!(WorkerOp::try_from(0u64).is_err());
766 }
767
768 #[test]
771 fn wire_error_protocol_display() {
772 let err = WireError::Protocol("custom message".to_string());
773 let s = format!("{err}");
774 assert!(s.contains("custom message"));
775 }
776
777 #[test]
778 fn wire_error_bad_magic_display() {
779 let err = WireError::BadMagic { expected: 0x1234, got: 0x5678 };
780 let s = format!("{err}");
781 assert!(s.contains("0x1234"));
782 assert!(s.contains("0x5678"));
783 }
784
785 #[test]
788 fn read_bytes_empty_via_zero_length() {
789 let mut buf = Vec::new();
790 write_u64(&mut buf, 0).unwrap();
791 let read = read_bytes(&mut Cursor::new(&buf)).unwrap();
792 assert!(read.is_empty());
793 }
794
795 #[test]
798 fn multiple_writes_then_reads_in_order() {
799 let mut buf = Vec::new();
800 write_u64(&mut buf, 11).unwrap();
801 write_string(&mut buf, "first").unwrap();
802 write_u64(&mut buf, 22).unwrap();
803 write_string(&mut buf, "second").unwrap();
804 write_bool(&mut buf, true).unwrap();
805
806 let mut cur = Cursor::new(&buf);
807 assert_eq!(read_u64(&mut cur).unwrap(), 11);
808 assert_eq!(read_string(&mut cur).unwrap(), "first");
809 assert_eq!(read_u64(&mut cur).unwrap(), 22);
810 assert_eq!(read_string(&mut cur).unwrap(), "second");
811 assert!(read_bool(&mut cur).unwrap());
812 }
813}