1use std::fmt::Write;
7
8use crate::{
9 core::{ChannelKey, MicSize, NodeHint, PacketBuilder, PacketHeader, PublicKey},
10 crypto::{
11 CryptoEngine, DerivedChannelKeys, NodeIdentity, PairwiseKeys, SharedSecret,
12 software::{SoftwareAes, SoftwareIdentity, SoftwareSha256},
13 },
14};
15
16type SoftwareCryptoEngine = CryptoEngine<SoftwareAes, SoftwareSha256>;
17
18pub fn render_phase1_test_vectors_markdown() -> String {
23 let vectors = generate_phase1_vectors();
24 let mut out = String::new();
25
26 writeln!(&mut out, "# Test Vectors").unwrap();
27 writeln!(&mut out).unwrap();
28 writeln!(
29 &mut out,
30 "This appendix contains byte-level packet examples. All values are hexadecimal, and multi-byte numeric fields are big-endian."
31 )
32 .unwrap();
33 writeln!(&mut out).unwrap();
34 writeln!(&mut out, "## Conventions").unwrap();
35 writeln!(&mut out).unwrap();
36 writeln!(
37 &mut out,
38 "The generated examples use fixed Ed25519 private keys so the appendix covers the full path from private key to public key to X25519 ECDH to packet bytes."
39 )
40 .unwrap();
41 writeln!(&mut out).unwrap();
42 writeln!(
43 &mut out,
44 "- Node A private key: `{}`",
45 hex_words(&vectors.node_a_private)
46 )
47 .unwrap();
48 writeln!(
49 &mut out,
50 "- Node A public key: `{}`",
51 hex_words(&vectors.node_a_public.0)
52 )
53 .unwrap();
54 writeln!(
55 &mut out,
56 " - Source hint: `{}`",
57 hex_spaced(&vectors.node_a_public.hint().0)
58 )
59 .unwrap();
60 writeln!(
61 &mut out,
62 "- Node B private key: `{}`",
63 hex_words(&vectors.node_b_private)
64 )
65 .unwrap();
66 writeln!(
67 &mut out,
68 "- Node B public key: `{}`",
69 hex_words(&vectors.node_b_public.0)
70 )
71 .unwrap();
72 writeln!(
73 &mut out,
74 " - Destination hint: `{}`",
75 hex_spaced(&vectors.node_b_public.hint().0)
76 )
77 .unwrap();
78 writeln!(
79 &mut out,
80 "- Pairwise shared secret: `{}`",
81 hex_words(&vectors.shared_secret.0)
82 )
83 .unwrap();
84 writeln!(
85 &mut out,
86 " - Derived from the two private keys via the implementation's Ed25519-to-X25519 conversion and X25519 ECDH."
87 )
88 .unwrap();
89
90 let unicast_encrypted_header = parse_header(&vectors.unicast_encrypted);
91 let unicast_ackreq_header = parse_header(&vectors.unicast_ackreq_fullsrc);
92 let multicast_encrypted_header = parse_header(&vectors.multicast_encrypted);
93 let multicast_authenticated_header = parse_header(&vectors.multicast_authenticated);
94 let unicast_with_options_header = parse_header(&vectors.unicast_with_options);
95 let blind_unicast_header = parse_header(&vectors.blind_unicast);
96 writeln!(
97 &mut out,
98 "- Channel key: `{}`",
99 hex_words(&vectors.channel_key.0)
100 )
101 .unwrap();
102 writeln!(
103 &mut out,
104 "- Derived channel identifier: `{}`",
105 hex_spaced(&vectors.channel.channel_id.0)
106 )
107 .unwrap();
108 writeln!(&mut out).unwrap();
109 writeln!(&mut out, "### FCF Bit Layout Reference").unwrap();
110 writeln!(&mut out).unwrap();
111 writeln!(&mut out, "```text").unwrap();
112 writeln!(&mut out, " 7 6 5 4 3 2 1 0").unwrap();
113 writeln!(&mut out, "+-------+-----------+---+---+---+").unwrap();
114 writeln!(&mut out, "| VER | PKT TYPE | S | R | H |").unwrap();
115 writeln!(&mut out, "+-------+-----------+---+---+---+").unwrap();
116 writeln!(&mut out, "```").unwrap();
117 writeln!(&mut out).unwrap();
118 writeln!(&mut out, "### SCF Bit Layout Reference").unwrap();
119 writeln!(&mut out).unwrap();
120 writeln!(&mut out, "```text").unwrap();
121 writeln!(&mut out, " 7 6 5 4 3 2 1 0").unwrap();
122 writeln!(&mut out, "+---+-------+---+---------------+").unwrap();
123 writeln!(&mut out, "| E | MIC | S | RESERVED |").unwrap();
124 writeln!(&mut out, "+---+-------+---+---------------+").unwrap();
125 writeln!(&mut out, "```").unwrap();
126
127 render_example(
128 &mut out,
129 "Example 1: Broadcast Beacon (S=0)",
130 "A minimal beacon with a 3-byte source hint and no payload.",
131 &[
132 (
133 "FCF",
134 "VER=3, TYPE=0 (broadcast), S=0, R=0, H=0",
135 "C0".into(),
136 ),
137 (
138 "SRC",
139 "Node A hint",
140 hex_spaced(&vectors.node_a_public.hint().0),
141 ),
142 ],
143 &vectors.broadcast_hint,
144 );
145
146 render_example(
147 &mut out,
148 "Example 2: Broadcast Beacon (S=1)",
149 "A first-contact beacon carrying the sender's full 32-byte public key.",
150 &[
151 (
152 "FCF",
153 "VER=3, TYPE=0 (broadcast), S=1, R=0, H=0",
154 "C4".into(),
155 ),
156 (
157 "SRC",
158 "Node A full key",
159 hex_spaced(&vectors.node_a_public.0),
160 ),
161 ],
162 &vectors.broadcast_full,
163 );
164
165 render_example(
166 &mut out,
167 "Example 3: Encrypted Unicast (S=0)",
168 "An encrypted unicast from Node A to Node B using source hints and frame counter 42.",
169 &[
170 ("FCF", "VER=3, TYPE=2 (unicast), S=0, R=0, H=0", "D0".into()),
171 (
172 "DST",
173 "Node B hint",
174 hex_spaced(&vectors.node_b_public.hint().0),
175 ),
176 (
177 "SRC",
178 "Node A hint",
179 hex_spaced(&vectors.node_a_public.hint().0),
180 ),
181 ("SCF", "E=1, MIC=3 (16-byte), S=0", "E0".into()),
182 ("Frame Counter", "42", hex_spaced(&42u32.to_be_bytes())),
183 (
184 "Payload",
185 "Encrypted `48 65 6C 6C 6F` (`\"Hello\"`)",
186 hex_spaced(&vectors.unicast_encrypted[unicast_encrypted_header.body_range.clone()]),
187 ),
188 (
189 "MIC",
190 "16 bytes",
191 hex_spaced(&vectors.unicast_encrypted[unicast_encrypted_header.mic_range.clone()]),
192 ),
193 ],
194 &vectors.unicast_encrypted,
195 );
196
197 render_example(
198 &mut out,
199 "Example 4: Encrypted Unicast with Ack Requested (S=1)",
200 "A first-contact encrypted unicast from Node A to Node B requesting a MAC acknowledgement. The full 32-byte source key is included.",
201 &[
202 (
203 "FCF",
204 "VER=3, TYPE=3 (unicast ack-req), S=1, R=0, H=0",
205 "DC".into(),
206 ),
207 (
208 "DST",
209 "Node B hint",
210 hex_spaced(&vectors.node_b_public.hint().0),
211 ),
212 (
213 "SRC",
214 "Node A full key",
215 hex_spaced(&vectors.node_a_public.0),
216 ),
217 ("SCF", "E=1, MIC=3 (16-byte), S=0", "E0".into()),
218 ("Frame Counter", "1", hex_spaced(&1u32.to_be_bytes())),
219 (
220 "Payload",
221 "Encrypted `68 65 79` (`\"hey\"`)",
222 hex_spaced(
223 &vectors.unicast_ackreq_fullsrc[unicast_ackreq_header.body_range.clone()],
224 ),
225 ),
226 (
227 "MIC",
228 "16 bytes",
229 hex_spaced(
230 &vectors.unicast_ackreq_fullsrc[unicast_ackreq_header.mic_range.clone()],
231 ),
232 ),
233 ],
234 &vectors.unicast_ackreq_fullsrc,
235 );
236
237 render_example(
238 &mut out,
239 "Example 5: Encrypted Multicast (E=1)",
240 "An encrypted multicast from Node A on channel `B08D`. The encrypted body contains the source hint followed by the plaintext payload.",
241 &[
242 (
243 "FCF",
244 "VER=3, TYPE=4 (multicast), S=0, R=0, H=0",
245 "E0".into(),
246 ),
247 (
248 "CHANNEL",
249 "Derived channel identifier",
250 hex_spaced(&vectors.channel.channel_id.0),
251 ),
252 ("SCF", "E=1, MIC=3 (16-byte), S=0", "E0".into()),
253 ("Frame Counter", "5", hex_spaced(&5u32.to_be_bytes())),
254 (
255 "Encrypted data",
256 "ENCRYPT(`SRC || \"Hello\"`)",
257 hex_spaced(
258 &vectors.multicast_encrypted[multicast_encrypted_header.body_range.clone()],
259 ),
260 ),
261 (
262 "MIC",
263 "16 bytes",
264 hex_spaced(
265 &vectors.multicast_encrypted[multicast_encrypted_header.mic_range.clone()],
266 ),
267 ),
268 ],
269 &vectors.multicast_encrypted,
270 );
271
272 render_example(
273 &mut out,
274 "Example 6: Authenticated Multicast (E=0)",
275 "An authenticated but unencrypted multicast from Node A carrying payload type `03` followed by `\"Hello\"`.",
276 &[
277 (
278 "FCF",
279 "VER=3, TYPE=4 (multicast), S=0, R=0, H=0",
280 "E0".into(),
281 ),
282 (
283 "CHANNEL",
284 "Derived channel identifier",
285 hex_spaced(&vectors.channel.channel_id.0),
286 ),
287 ("SCF", "E=0, MIC=3 (16-byte), S=0", "60".into()),
288 ("Frame Counter", "3", hex_spaced(&3u32.to_be_bytes())),
289 (
290 "SRC",
291 "Node A hint",
292 hex_spaced(&vectors.node_a_public.hint().0),
293 ),
294 (
295 "Payload",
296 "`03 || \"Hello\"`",
297 hex_spaced(
298 &vectors.multicast_authenticated
299 [multicast_authenticated_header.body_range.clone()],
300 ),
301 ),
302 (
303 "MIC",
304 "16 bytes",
305 hex_spaced(
306 &vectors.multicast_authenticated
307 [multicast_authenticated_header.mic_range.clone()],
308 ),
309 ),
310 ],
311 &vectors.multicast_authenticated,
312 );
313
314 writeln!(&mut out).unwrap();
315 writeln!(
316 &mut out,
317 "## Example 7: Encrypted Unicast with Options and Flood Hops"
318 )
319 .unwrap();
320 writeln!(&mut out).unwrap();
321 writeln!(&mut out, "An encrypted unicast with a region code option, an empty trace-route option, and flood hop limit 4.").unwrap();
322 writeln!(&mut out).unwrap();
323 writeln!(&mut out, "**Options encoding:**").unwrap();
324 writeln!(&mut out).unwrap();
325 writeln!(&mut out, "| Option | Number | Delta | Length | Encoding |").unwrap();
326 writeln!(&mut out, "|---|---:|---:|---:|---|").unwrap();
327 writeln!(&mut out, "| Trace Route | 2 | 2 | 0 | `20` |").unwrap();
328 writeln!(
329 &mut out,
330 "| Region Code | 11 | 9 | 2 | `92` then value `78 53` |"
331 )
332 .unwrap();
333 writeln!(&mut out, "| End marker | — | — | — | `FF` |").unwrap();
334 writeln!(&mut out).unwrap();
335 writeln!(&mut out, "| Field | Value | Hex |").unwrap();
336 writeln!(&mut out, "|---|---|---|").unwrap();
337 writeln!(
338 &mut out,
339 "| FCF | VER=3, TYPE=2 (unicast), S=0, R=0, H=1 | `D1` |"
340 )
341 .unwrap();
342 writeln!(&mut out, "| FHOPS | FHOPS_REM=4, FHOPS_ACC=0 | `40` |").unwrap();
343 writeln!(
344 &mut out,
345 "| DST | Node B hint | `{}` |",
346 hex_spaced(&vectors.node_b_public.hint().0)
347 )
348 .unwrap();
349 writeln!(
350 &mut out,
351 "| SRC | Node A hint | `{}` |",
352 hex_spaced(&vectors.node_a_public.hint().0)
353 )
354 .unwrap();
355 writeln!(&mut out, "| SCF | E=1, MIC=3 (16-byte), S=0 | `E0` |").unwrap();
356 writeln!(&mut out, "| Frame Counter | 10 | `00 00 00 0A` |").unwrap();
357 writeln!(
358 &mut out,
359 "| Options | Trace route + region code + end marker | `20 92 78 53 FF` |"
360 )
361 .unwrap();
362 writeln!(
363 &mut out,
364 "| Payload | Encrypted `68 65 79` (`\"hey\"`) | `{}` |",
365 hex_spaced(&vectors.unicast_with_options[unicast_with_options_header.body_range.clone()])
366 )
367 .unwrap();
368 writeln!(
369 &mut out,
370 "| MIC | 16 bytes | `{}` |",
371 hex_spaced(&vectors.unicast_with_options[unicast_with_options_header.mic_range.clone()])
372 )
373 .unwrap();
374 writeln!(&mut out).unwrap();
375 writeln!(&mut out, "```text").unwrap();
376 writeln!(
377 &mut out,
378 "{}",
379 wrap_hex_lines(&vectors.unicast_with_options, 16)
380 )
381 .unwrap();
382 writeln!(&mut out, "```").unwrap();
383 writeln!(&mut out).unwrap();
384 writeln!(
385 &mut out,
386 "Total: {} bytes.",
387 vectors.unicast_with_options.len()
388 )
389 .unwrap();
390
391 render_example(
392 &mut out,
393 "Example 8: Blind Unicast (S=0)",
394 "A blind unicast on channel `B08D`. The destination hint and source hint are encrypted together in `ENC_DST_SRC`, while the payload is encrypted with the blind-unicast payload keys.",
395 &[
396 (
397 "FCF",
398 "VER=3, TYPE=6 (blind unicast), S=0, R=0, H=0",
399 "F0".into(),
400 ),
401 (
402 "CHANNEL",
403 "Derived channel identifier",
404 hex_spaced(&vectors.channel.channel_id.0),
405 ),
406 ("SCF", "E=1, MIC=3 (16-byte), S=0", "E0".into()),
407 ("Frame Counter", "7", hex_spaced(&7u32.to_be_bytes())),
408 (
409 "ENC_DST_SRC",
410 "ENCRYPT(`DST || SRC`)",
411 hex_spaced(
412 &vectors.blind_unicast[blind_unicast_header.options_range.end
413 ..blind_unicast_header.body_range.start],
414 ),
415 ),
416 (
417 "ENC_PAYLOAD",
418 "ENCRYPT(`\"Hello\"`)",
419 hex_spaced(&vectors.blind_unicast[blind_unicast_header.body_range.clone()]),
420 ),
421 (
422 "MIC",
423 "16 bytes",
424 hex_spaced(&vectors.blind_unicast[blind_unicast_header.mic_range.clone()]),
425 ),
426 ],
427 &vectors.blind_unicast,
428 );
429
430 out
431}
432
433struct Phase1Vectors {
434 node_a_private: [u8; 32],
435 node_b_private: [u8; 32],
436 node_a_public: PublicKey,
437 node_b_public: PublicKey,
438 shared_secret: SharedSecret,
439 channel_key: ChannelKey,
440 channel: DerivedChannelKeys,
441 broadcast_hint: Vec<u8>,
442 broadcast_full: Vec<u8>,
443 unicast_encrypted: Vec<u8>,
444 unicast_ackreq_fullsrc: Vec<u8>,
445 multicast_encrypted: Vec<u8>,
446 multicast_authenticated: Vec<u8>,
447 unicast_with_options: Vec<u8>,
448 blind_unicast: Vec<u8>,
449}
450
451fn generate_phase1_vectors() -> Phase1Vectors {
452 let engine = SoftwareCryptoEngine::new(SoftwareAes, SoftwareSha256);
453
454 let node_a_private = [
455 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
456 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E,
457 0x2F, 0x30,
458 ];
459 let node_b_private = [
460 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
461 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E,
462 0x4F, 0x50,
463 ];
464 let node_a_identity = SoftwareIdentity::from_secret_bytes(&node_a_private);
465 let node_b_identity = SoftwareIdentity::from_secret_bytes(&node_b_private);
466 let node_a_public = *node_a_identity.public_key();
467 let node_b_public = *node_b_identity.public_key();
468 let shared_secret = node_a_identity.shared_secret_with(&node_b_public).unwrap();
469 let pairwise = engine.derive_pairwise_keys(&shared_secret);
470 let channel_key = ChannelKey([0x5A; 32]);
471 let channel = engine.derive_channel_keys(&channel_key);
472 let blind_keys = engine.derive_blind_keys(&pairwise, &channel);
473
474 Phase1Vectors {
475 node_a_private,
476 node_b_private,
477 node_a_public,
478 node_b_public,
479 shared_secret,
480 channel_key,
481 broadcast_hint: build_broadcast_hint(&node_a_public),
482 broadcast_full: build_broadcast_full(&node_a_public),
483 unicast_encrypted: build_unicast_encrypted(
484 &engine,
485 &pairwise,
486 &node_a_public,
487 node_b_public.hint(),
488 ),
489 unicast_ackreq_fullsrc: build_unicast_ackreq_fullsrc(
490 &engine,
491 &pairwise,
492 &node_a_public,
493 node_b_public.hint(),
494 ),
495 multicast_encrypted: build_multicast_encrypted(&engine, &channel, &node_a_public),
496 multicast_authenticated: build_multicast_authenticated(&engine, &channel, &node_a_public),
497 unicast_with_options: build_unicast_with_options(
498 &engine,
499 &pairwise,
500 &node_a_public,
501 node_b_public.hint(),
502 ),
503 blind_unicast: build_blind_unicast(
504 &engine,
505 &blind_keys,
506 &channel,
507 &node_a_public,
508 node_b_public.hint(),
509 ),
510 channel,
511 }
512}
513
514fn build_broadcast_hint(node_a: &PublicKey) -> Vec<u8> {
515 let mut buf = [0u8; 64];
516 PacketBuilder::new(&mut buf)
517 .broadcast()
518 .source_hint(node_a.hint())
519 .build()
520 .unwrap()
521 .to_vec()
522}
523
524fn build_broadcast_full(node_a: &PublicKey) -> Vec<u8> {
525 let mut buf = [0u8; 64];
526 PacketBuilder::new(&mut buf)
527 .broadcast()
528 .source_full(node_a)
529 .build()
530 .unwrap()
531 .to_vec()
532}
533
534fn build_unicast_encrypted(
535 engine: &SoftwareCryptoEngine,
536 pairwise: &PairwiseKeys,
537 node_a: &PublicKey,
538 dst: NodeHint,
539) -> Vec<u8> {
540 let mut buf = [0u8; 128];
541 let mut packet = PacketBuilder::new(&mut buf)
542 .unicast(dst)
543 .source_hint(node_a.hint())
544 .frame_counter(42)
545 .encrypted()
546 .mic_size(MicSize::Mic16)
547 .payload(b"Hello")
548 .build()
549 .unwrap();
550 engine.seal_packet(&mut packet, pairwise).unwrap();
551 packet.as_bytes().to_vec()
552}
553
554fn build_unicast_ackreq_fullsrc(
555 engine: &SoftwareCryptoEngine,
556 pairwise: &PairwiseKeys,
557 node_a: &PublicKey,
558 dst: NodeHint,
559) -> Vec<u8> {
560 let mut buf = [0u8; 128];
561 let mut packet = PacketBuilder::new(&mut buf)
562 .unicast(dst)
563 .source_full(node_a)
564 .frame_counter(1)
565 .ack_requested()
566 .encrypted()
567 .mic_size(MicSize::Mic16)
568 .payload(b"hey")
569 .build()
570 .unwrap();
571 engine.seal_packet(&mut packet, pairwise).unwrap();
572 packet.as_bytes().to_vec()
573}
574
575fn build_multicast_encrypted(
576 engine: &SoftwareCryptoEngine,
577 channel: &DerivedChannelKeys,
578 node_a: &PublicKey,
579) -> Vec<u8> {
580 let mut buf = [0u8; 128];
581 let mut packet = PacketBuilder::new(&mut buf)
582 .multicast(channel.channel_id)
583 .source_hint(node_a.hint())
584 .frame_counter(5)
585 .encrypted()
586 .mic_size(MicSize::Mic16)
587 .payload(b"Hello")
588 .build()
589 .unwrap();
590 let keys = PairwiseKeys {
591 k_enc: channel.k_enc,
592 k_mic: channel.k_mic,
593 };
594 engine.seal_packet(&mut packet, &keys).unwrap();
595 packet.as_bytes().to_vec()
596}
597
598fn build_multicast_authenticated(
599 engine: &SoftwareCryptoEngine,
600 channel: &DerivedChannelKeys,
601 node_a: &PublicKey,
602) -> Vec<u8> {
603 let mut buf = [0u8; 128];
604 let mut packet = PacketBuilder::new(&mut buf)
605 .multicast(channel.channel_id)
606 .source_hint(node_a.hint())
607 .frame_counter(3)
608 .mic_size(MicSize::Mic16)
609 .payload(b"\x03Hello")
610 .build()
611 .unwrap();
612 let keys = PairwiseKeys {
613 k_enc: channel.k_enc,
614 k_mic: channel.k_mic,
615 };
616 engine.seal_packet(&mut packet, &keys).unwrap();
617 packet.as_bytes().to_vec()
618}
619
620fn build_unicast_with_options(
621 engine: &SoftwareCryptoEngine,
622 pairwise: &PairwiseKeys,
623 node_a: &PublicKey,
624 dst: NodeHint,
625) -> Vec<u8> {
626 let mut buf = [0u8; 128];
627 let mut packet = PacketBuilder::new(&mut buf)
628 .unicast(dst)
629 .source_hint(node_a.hint())
630 .frame_counter(10)
631 .encrypted()
632 .flood_hops(4)
633 .trace_route()
634 .region_code([0x78, 0x53])
635 .payload(b"hey")
636 .build()
637 .unwrap();
638 engine.seal_packet(&mut packet, pairwise).unwrap();
639 packet.as_bytes().to_vec()
640}
641
642fn build_blind_unicast(
643 engine: &SoftwareCryptoEngine,
644 blind_keys: &PairwiseKeys,
645 channel: &DerivedChannelKeys,
646 node_a: &PublicKey,
647 dst: NodeHint,
648) -> Vec<u8> {
649 let mut buf = [0u8; 160];
650 let mut packet = PacketBuilder::new(&mut buf)
651 .blind_unicast(channel.channel_id, dst)
652 .source_hint(node_a.hint())
653 .frame_counter(7)
654 .mic_size(MicSize::Mic16)
655 .payload(b"Hello")
656 .build()
657 .unwrap();
658 engine
659 .seal_blind_packet(&mut packet, blind_keys, channel)
660 .unwrap();
661 packet.as_bytes().to_vec()
662}
663
664fn render_example(
665 out: &mut String,
666 title: &str,
667 description: &str,
668 fields: &[(&str, &str, String)],
669 bytes: &[u8],
670) {
671 writeln!(out).unwrap();
672 writeln!(out, "## {}", title).unwrap();
673 writeln!(out).unwrap();
674 writeln!(out, "{}", description).unwrap();
675 writeln!(out).unwrap();
676 writeln!(out, "| Field | Value | Hex |").unwrap();
677 writeln!(out, "|---|---|---|").unwrap();
678 for (field, value, hex) in fields {
679 writeln!(out, "| {} | {} | `{}` |", field, value, hex).unwrap();
680 }
681 writeln!(out).unwrap();
682 writeln!(out, "```text").unwrap();
683 writeln!(out, "{}", wrap_hex_lines(bytes, 16)).unwrap();
684 writeln!(out, "```").unwrap();
685 writeln!(out).unwrap();
686 writeln!(out, "Total: {} bytes.", bytes.len()).unwrap();
687}
688
689fn hex_spaced(bytes: &[u8]) -> String {
690 bytes
691 .iter()
692 .map(|byte| format!("{:02X}", byte))
693 .collect::<Vec<_>>()
694 .join(" ")
695}
696
697fn hex_words(bytes: &[u8]) -> String {
698 bytes
699 .chunks(2)
700 .map(|chunk| {
701 chunk
702 .iter()
703 .map(|byte| format!("{:02X}", byte))
704 .collect::<String>()
705 })
706 .collect::<Vec<_>>()
707 .join(" ")
708}
709
710fn wrap_hex_lines(bytes: &[u8], line_width: usize) -> String {
711 bytes
712 .chunks(line_width)
713 .map(hex_spaced)
714 .collect::<Vec<_>>()
715 .join("\n")
716}
717
718fn parse_header(bytes: &[u8]) -> PacketHeader {
719 PacketHeader::parse(bytes).expect("parse generated vector")
720}
721
722#[cfg(test)]
723mod tests {
724 use std::fs;
725
726 use super::render_phase1_test_vectors_markdown;
727
728 #[test]
729 fn phase1_vectors_match_protocol_appendix() {
730 let expected = render_phase1_test_vectors_markdown();
731 let actual = fs::read_to_string(concat!(
732 env!("CARGO_MANIFEST_DIR"),
733 "/../docs/protocol/src/test-vectors.md"
734 ))
735 .expect("read protocol test vectors");
736 assert_eq!(actual, expected);
737 }
738}