1use std::fmt;
16
17pub const MAX_PACKET_LEN: usize = 4096;
19pub const MAGIC: [u8; 8] = *b"HLNEXEC1";
21pub const VERSION: u16 = 1;
23pub const INITIAL_HEADER_LEN: usize = 200;
25pub const SUCCESSOR_HEADER_LEN: usize = 232;
27const TOKEN_START: usize = 160;
28const TOKEN_END: usize = 192;
29const LENGTH_BYTES: usize = 8;
30
31#[derive(Clone, Copy, Debug, Eq, PartialEq)]
33#[repr(u16)]
34pub enum Kind {
35 Initial = 1,
37 Successor = 2,
39}
40
41#[derive(Clone, Copy, Eq, PartialEq)]
43pub enum Image<'a> {
44 Initial {
46 nonce: &'a [u8; 32],
48 },
49 Successor {
51 ticket: &'a [u8; 32],
53 composite_digest: &'a [u8; 32],
55 },
56}
57impl fmt::Debug for Image<'_> {
58 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59 match self {
61 Self::Initial { .. } => f.write_str("Initial(<private nonce>)"),
62 Self::Successor {
63 composite_digest, ..
64 } => f
65 .debug_struct("Successor")
66 .field("ticket", &"<private>")
67 .field("composite_digest", composite_digest)
68 .finish(),
69 }
70 }
71}
72impl Image<'_> {
73 pub fn kind(&self) -> Kind {
75 match self {
76 Self::Initial { .. } => Kind::Initial,
77 Self::Successor { .. } => Kind::Successor,
78 }
79 }
80 fn header_len(&self) -> usize {
81 match self {
82 Self::Initial { .. } => INITIAL_HEADER_LEN,
83 Self::Successor { .. } => SUCCESSOR_HEADER_LEN,
84 }
85 }
86}
87
88#[derive(Clone, Copy, Debug, Eq, PartialEq)]
90pub struct Packet<'a> {
91 pub config_digest: &'a [u8; 32],
93 pub config_schema: &'a [u8; 16],
95 pub native_source_digest: &'a [u8; 32],
97 pub target_digest: &'a [u8; 32],
99 pub bootstrap_digest: &'a [u8; 32],
101 pub image: Image<'a>,
103 pub coordinator: &'a [u8],
105 pub detlog_identity: &'a str,
107 pub forward_policy: &'a str,
109 pub observer_identity: &'a str,
113}
114
115#[derive(Clone, Copy, Debug, Eq, PartialEq)]
117pub struct ExpectedBindings<'a> {
118 pub config_digest: &'a [u8; 32],
120 pub config_schema: &'a [u8; 16],
122 pub native_source_digest: &'a [u8; 32],
124 pub target_digest: &'a [u8; 32],
126 pub bootstrap_digest: &'a [u8; 32],
128}
129
130#[derive(Clone, Copy, Debug, Eq, PartialEq)]
132pub enum BindingMismatch {
133 ConfigDigest,
134 ConfigSchema,
135 NativeSourceDigest,
136 TargetDigest,
137 BootstrapDigest,
138}
139impl Packet<'_> {
140 pub fn check_bindings(&self, expected: ExpectedBindings<'_>) -> Result<(), BindingMismatch> {
144 for (actual, expected, error) in [
145 (
146 self.config_digest,
147 expected.config_digest,
148 BindingMismatch::ConfigDigest,
149 ),
150 (
151 self.native_source_digest,
152 expected.native_source_digest,
153 BindingMismatch::NativeSourceDigest,
154 ),
155 (
156 self.target_digest,
157 expected.target_digest,
158 BindingMismatch::TargetDigest,
159 ),
160 (
161 self.bootstrap_digest,
162 expected.bootstrap_digest,
163 BindingMismatch::BootstrapDigest,
164 ),
165 ] {
166 if actual != expected {
167 return Err(error);
168 }
169 }
170 if self.config_schema != expected.config_schema {
171 return Err(BindingMismatch::ConfigSchema);
172 }
173 Ok(())
174 }
175 fn variable_fields(&self) -> [&[u8]; 4] {
176 [
177 self.coordinator,
178 self.detlog_identity.as_bytes(),
179 self.forward_policy.as_bytes(),
180 self.observer_identity.as_bytes(),
181 ]
182 }
183}
184
185#[derive(Clone, Copy, Debug, Eq, PartialEq)]
187pub enum PacketError {
188 Size,
189 Magic,
190 Version,
191 Kind,
192 Reserved,
193 Length,
194 Empty,
195 Nul,
196 Utf8,
197 Schema,
198}
199impl fmt::Display for PacketError {
200 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201 f.write_str(match self {
202 Self::Size => "native input complete size mismatch",
203 Self::Magic => "native input magic mismatch",
204 Self::Version => "native input version mismatch",
205 Self::Kind => "native input kind mismatch",
206 Self::Reserved => "native input reserved bits are nonzero",
207 Self::Length => "native input field lengths do not consume exactly",
208 Self::Empty => "native input mandatory field is empty",
209 Self::Nul => "native input field contains NUL",
210 Self::Utf8 => "native input identity or policy is not UTF-8",
211 Self::Schema => "native input Config schema is not sixteen ASCII hex characters",
212 })
213 }
214}
215impl std::error::Error for PacketError {}
216
217fn word(bytes: &[u8], start: usize) -> usize {
218 usize::from(u16::from_be_bytes([bytes[start], bytes[start + 1]]))
219}
220fn check_field(bytes: &[u8]) -> Result<(), PacketError> {
221 if bytes.is_empty() {
222 return Err(PacketError::Empty);
223 }
224 if bytes.contains(&0) {
225 return Err(PacketError::Nul);
226 }
227 Ok(())
228}
229fn check_schema(bytes: &[u8; 16]) -> Result<(), PacketError> {
230 if bytes.iter().all(u8::is_ascii_hexdigit) {
231 Ok(())
232 } else {
233 Err(PacketError::Schema)
234 }
235}
236
237pub fn decode(bytes: &[u8]) -> Result<Packet<'_>, PacketError> {
239 if !(INITIAL_HEADER_LEN..=MAX_PACKET_LEN).contains(&bytes.len()) {
240 return Err(PacketError::Size);
241 }
242 if bytes[..8] != MAGIC {
243 return Err(PacketError::Magic);
244 }
245 if word(bytes, 8) != usize::from(VERSION) {
246 return Err(PacketError::Version);
247 }
248 if word(bytes, 10) != bytes.len() {
249 return Err(PacketError::Size);
250 }
251 let kind = match word(bytes, 12) {
252 1 => Kind::Initial,
253 2 => Kind::Successor,
254 _ => return Err(PacketError::Kind),
255 };
256 if word(bytes, 14) != 0 {
257 return Err(PacketError::Reserved);
258 }
259 let header = match kind {
260 Kind::Initial => INITIAL_HEADER_LEN,
261 Kind::Successor => SUCCESSOR_HEADER_LEN,
262 };
263 if bytes.len() < header {
264 return Err(PacketError::Size);
265 }
266 let lengths_start = header - LENGTH_BYTES;
267 let mut ends = [header; 5];
268 for index in 0..4 {
269 ends[index + 1] = ends[index]
270 .checked_add(word(bytes, lengths_start + index * 2))
271 .filter(|end| *end <= bytes.len())
272 .ok_or(PacketError::Length)?;
273 }
274 if ends[4] != bytes.len() {
275 return Err(PacketError::Length);
276 }
277 let fields = std::array::from_fn::<_, 4, _>(|index| &bytes[ends[index]..ends[index + 1]]);
278 for field in fields {
279 check_field(field)?;
280 }
281 let token = bytes[TOKEN_START..TOKEN_END]
282 .try_into()
283 .map_err(|_| PacketError::Length)?;
284 let image = match kind {
285 Kind::Initial => Image::Initial { nonce: token },
286 Kind::Successor => Image::Successor {
287 ticket: token,
288 composite_digest: bytes[TOKEN_END..TOKEN_END + 32]
289 .try_into()
290 .map_err(|_| PacketError::Length)?,
291 },
292 };
293 let packet = Packet {
294 config_digest: bytes[16..48].try_into().map_err(|_| PacketError::Length)?,
295 config_schema: bytes[48..64].try_into().map_err(|_| PacketError::Schema)?,
296 native_source_digest: bytes[64..96].try_into().map_err(|_| PacketError::Length)?,
297 target_digest: bytes[96..128].try_into().map_err(|_| PacketError::Length)?,
298 bootstrap_digest: bytes[128..160]
299 .try_into()
300 .map_err(|_| PacketError::Length)?,
301 image,
302 coordinator: fields[0],
303 detlog_identity: std::str::from_utf8(fields[1]).map_err(|_| PacketError::Utf8)?,
304 forward_policy: std::str::from_utf8(fields[2]).map_err(|_| PacketError::Utf8)?,
305 observer_identity: std::str::from_utf8(fields[3]).map_err(|_| PacketError::Utf8)?,
306 };
307 check_schema(packet.config_schema)?;
308 Ok(packet)
309}
310
311pub fn encode(packet: Packet<'_>) -> Result<Vec<u8>, PacketError> {
313 let fields = packet.variable_fields();
314 let mut total = packet.image.header_len();
315 for field in fields {
316 if field.len() > MAX_PACKET_LEN {
317 return Err(PacketError::Size);
318 }
319 check_field(field)?;
320 total = total
321 .checked_add(field.len())
322 .filter(|total| *total <= MAX_PACKET_LEN)
323 .ok_or(PacketError::Size)?;
324 }
325 check_schema(packet.config_schema)?;
326 let mut bytes = Vec::with_capacity(total);
327 bytes.extend_from_slice(&MAGIC);
328 bytes.extend_from_slice(&VERSION.to_be_bytes());
329 bytes.extend_from_slice(&(total as u16).to_be_bytes());
330 bytes.extend_from_slice(&(packet.image.kind() as u16).to_be_bytes());
331 bytes.extend_from_slice(&0_u16.to_be_bytes());
332 bytes.extend_from_slice(packet.config_digest);
333 bytes.extend_from_slice(packet.config_schema);
334 bytes.extend_from_slice(packet.native_source_digest);
335 bytes.extend_from_slice(packet.target_digest);
336 bytes.extend_from_slice(packet.bootstrap_digest);
337 match packet.image {
338 Image::Initial { nonce } => bytes.extend_from_slice(nonce),
339 Image::Successor {
340 ticket,
341 composite_digest,
342 } => {
343 bytes.extend_from_slice(ticket);
344 bytes.extend_from_slice(composite_digest);
345 }
346 }
347 for field in fields {
348 bytes.extend_from_slice(&(field.len() as u16).to_be_bytes());
349 }
350 for field in fields {
351 bytes.extend_from_slice(field);
352 }
353 Ok(bytes)
354}
355
356pub fn encode_into(packet: Packet<'_>, output: &mut [u8]) -> Result<usize, PacketError> {
360 let fields = packet.variable_fields();
361 let mut total = packet.image.header_len();
362 for field in fields {
363 if field.len() > MAX_PACKET_LEN {
364 return Err(PacketError::Size);
365 }
366 check_field(field)?;
367 total = total
368 .checked_add(field.len())
369 .filter(|total| *total <= MAX_PACKET_LEN)
370 .ok_or(PacketError::Size)?;
371 }
372 check_schema(packet.config_schema)?;
373 if output.len() < total {
374 return Err(PacketError::Size);
375 }
376 let mut offset = 0;
377 let mut append = |bytes: &[u8]| {
378 output[offset..offset + bytes.len()].copy_from_slice(bytes);
379 offset += bytes.len();
380 };
381 append(&MAGIC);
382 append(&VERSION.to_be_bytes());
383 append(&(total as u16).to_be_bytes());
384 append(&(packet.image.kind() as u16).to_be_bytes());
385 append(&0_u16.to_be_bytes());
386 append(packet.config_digest);
387 append(packet.config_schema);
388 append(packet.native_source_digest);
389 append(packet.target_digest);
390 append(packet.bootstrap_digest);
391 match packet.image {
392 Image::Initial { nonce } => append(nonce),
393 Image::Successor {
394 ticket,
395 composite_digest,
396 } => {
397 append(ticket);
398 append(composite_digest);
399 }
400 }
401 for field in fields {
402 append(&(field.len() as u16).to_be_bytes());
403 }
404 for field in fields {
405 append(field);
406 }
407 if offset != total {
408 return Err(PacketError::Length);
409 }
410 Ok(total)
411}
412
413#[cfg(test)]
414mod tests {
415 use super::*;
416
417 fn packet(successor: bool) -> Packet<'static> {
418 Packet {
419 config_digest: &[2; 32],
420 config_schema: b"0123456789abcdef",
421 native_source_digest: &[3; 32],
422 target_digest: &[4; 32],
423 bootstrap_digest: &[5; 32],
424 image: if successor {
425 Image::Successor {
426 ticket: &[6; 32],
427 composite_digest: &[7; 32],
428 }
429 } else {
430 Image::Initial { nonce: &[1; 32] }
431 },
432 coordinator: b"/tmp/native-\xff.sock",
433 detlog_identity: "209:0000000000000007:0000000000001234",
434 forward_policy: "INFO",
435 observer_identity: "210:0000000000000007:0000000000005678",
436 }
437 }
438 fn expected(packet: Packet<'_>) -> ExpectedBindings<'_> {
439 ExpectedBindings {
440 config_digest: packet.config_digest,
441 config_schema: packet.config_schema,
442 native_source_digest: packet.native_source_digest,
443 target_digest: packet.target_digest,
444 bootstrap_digest: packet.bootstrap_digest,
445 }
446 }
447 fn advertised(bytes: &mut [u8]) {
448 let total = bytes.len() as u16;
449 bytes[10..12].copy_from_slice(&total.to_be_bytes());
450 }
451 fn field_start(packet: Packet<'_>, index: usize) -> usize {
452 packet.image.header_len()
453 + packet.variable_fields()[..index]
454 .iter()
455 .map(|field| field.len())
456 .sum::<usize>()
457 }
458
459 #[test]
460 fn exact_layout_both_kinds_roundtrip_and_borrow_all_fields() {
461 assert_eq!(MAX_PACKET_LEN, 4096);
462 assert_eq!(INITIAL_HEADER_LEN, 200);
463 assert_eq!(SUCCESSOR_HEADER_LEN, 232);
464 for successor in [false, true] {
465 let original = packet(successor);
466 let bytes = encode(original).unwrap();
467 assert_eq!(&bytes[..8], b"HLNEXEC1");
468 assert_eq!(&bytes[8..10], &[0, 1]);
469 assert_eq!(word(&bytes, 10), bytes.len());
470 assert_eq!(word(&bytes, 12), if successor { 2 } else { 1 });
471 assert_eq!(&bytes[14..16], &[0, 0]);
472 for (range, wanted) in [
473 (16..48, original.config_digest.as_slice()),
474 (48..64, original.config_schema.as_slice()),
475 (64..96, original.native_source_digest.as_slice()),
476 (96..128, original.target_digest.as_slice()),
477 (128..160, original.bootstrap_digest.as_slice()),
478 (160..192, if successor { &[6; 32] } else { &[1; 32] }),
479 ] {
480 assert_eq!(&bytes[range], wanted);
481 }
482 if successor {
483 assert_eq!(&bytes[192..224], &[7; 32]);
484 }
485 let header = original.image.header_len();
486 for (index, field) in original.variable_fields().into_iter().enumerate() {
487 assert_eq!(word(&bytes, header - LENGTH_BYTES + 2 * index), field.len());
488 let start = field_start(original, index);
489 assert_eq!(&bytes[start..start + field.len()], field);
490 }
491 let decoded = decode(&bytes).unwrap();
492 assert_eq!(decoded, original);
493 assert_eq!(decoded.config_digest.as_ptr(), bytes[16..].as_ptr());
494 assert_eq!(decoded.config_schema.as_ptr(), bytes[48..].as_ptr());
495 assert_eq!(decoded.native_source_digest.as_ptr(), bytes[64..].as_ptr());
496 assert_eq!(decoded.target_digest.as_ptr(), bytes[96..].as_ptr());
497 assert_eq!(decoded.bootstrap_digest.as_ptr(), bytes[128..].as_ptr());
498 assert_eq!(decoded.coordinator.as_ptr(), bytes[header..].as_ptr());
499 assert_eq!(
500 decoded.detlog_identity.as_ptr(),
501 bytes[field_start(original, 1)..].as_ptr()
502 );
503 assert_eq!(
504 decoded.forward_policy.as_ptr(),
505 bytes[field_start(original, 2)..].as_ptr()
506 );
507 assert_eq!(
508 decoded.observer_identity.as_ptr(),
509 bytes[field_start(original, 3)..].as_ptr()
510 );
511 match decoded.image {
512 Image::Initial { nonce } => assert_eq!(nonce.as_ptr(), bytes[160..].as_ptr()),
513 Image::Successor {
514 ticket,
515 composite_digest,
516 } => {
517 assert_eq!(ticket.as_ptr(), bytes[160..].as_ptr());
518 assert_eq!(composite_digest.as_ptr(), bytes[192..].as_ptr());
519 }
520 }
521 assert_eq!(decoded.check_bindings(expected(original)), Ok(()));
522 }
523 }
524
525 #[test]
526 fn every_truncated_prefix_and_wrong_complete_size_refuse() {
527 for successor in [false, true] {
528 let valid = encode(packet(successor)).unwrap();
529 for length in 0..valid.len() {
530 assert!(
531 decode(&valid[..length]).is_err(),
532 "kind={successor} prefix={length}"
533 );
534 if length >= 12 {
535 let mut short = valid[..length].to_vec();
536 advertised(&mut short);
537 assert!(
538 decode(&short).is_err(),
539 "advertised kind={successor} prefix={length}"
540 );
541 }
542 }
543 let mut wrong = valid.clone();
544 wrong[10..12].copy_from_slice(&0_u16.to_be_bytes());
545 assert_eq!(decode(&wrong), Err(PacketError::Size));
546 wrong[10..12].copy_from_slice(&4096_u16.to_be_bytes());
547 assert_eq!(decode(&wrong), Err(PacketError::Size));
548 let mut extra = valid;
549 extra.push(9);
550 assert_eq!(decode(&extra), Err(PacketError::Size));
551 advertised(&mut extra);
552 assert_eq!(decode(&extra), Err(PacketError::Length));
553 }
554 }
555
556 #[test]
557 fn magic_version_kind_reserved_and_every_length_are_exact() {
558 for successor in [false, true] {
559 let original = packet(successor);
560 let valid = encode(original).unwrap();
561 for index in 0..8 {
562 let mut changed = valid.clone();
563 changed[index] ^= 1;
564 assert_eq!(decode(&changed), Err(PacketError::Magic));
565 }
566 for version in [0_u16, 2, u16::MAX] {
567 let mut changed = valid.clone();
568 changed[8..10].copy_from_slice(&version.to_be_bytes());
569 assert_eq!(decode(&changed), Err(PacketError::Version));
570 }
571 for kind in [0_u16, 3, u16::MAX] {
572 let mut changed = valid.clone();
573 changed[12..14].copy_from_slice(&kind.to_be_bytes());
574 assert_eq!(decode(&changed), Err(PacketError::Kind));
575 }
576 for index in [14, 15] {
577 let mut changed = valid.clone();
578 changed[index] = 1;
579 assert_eq!(decode(&changed), Err(PacketError::Reserved));
580 }
581 let lengths = original.image.header_len() - LENGTH_BYTES;
582 for field in 0..4 {
583 let index = lengths + 2 * field;
584 for length in [
585 word(&valid, index) - 1,
586 word(&valid, index) + 1,
587 u16::MAX as usize,
588 ] {
589 let mut changed = valid.clone();
590 changed[index..index + 2].copy_from_slice(&(length as u16).to_be_bytes());
591 assert_eq!(
592 decode(&changed),
593 Err(PacketError::Length),
594 "kind={successor} field={field} length={length}"
595 );
596 }
597 }
598 }
599 }
600
601 #[test]
602 fn every_empty_nul_and_nonutf8_text_field_refuses_raw_path_stays_raw() {
603 for successor in [false, true] {
604 let original = packet(successor);
605 let valid = encode(original).unwrap();
606 assert!(std::str::from_utf8(original.coordinator).is_err());
607 assert_eq!(decode(&valid).unwrap().coordinator, original.coordinator);
608 for index in 0..4 {
609 let start = field_start(original, index);
610 let length = original.variable_fields()[index].len();
611 let mut empty = valid.clone();
612 empty.drain(start..start + length);
613 let lengths = original.image.header_len() - LENGTH_BYTES + 2 * index;
614 empty[lengths..lengths + 2].copy_from_slice(&0_u16.to_be_bytes());
615 advertised(&mut empty);
616 assert_eq!(decode(&empty), Err(PacketError::Empty), "field {index}");
617 let mut nul = valid.clone();
618 nul[start] = 0;
619 assert_eq!(decode(&nul), Err(PacketError::Nul), "field {index}");
620 if index != 0 {
621 let mut nonutf8 = valid.clone();
622 nonutf8[start] = 0xff;
623 assert_eq!(decode(&nonutf8), Err(PacketError::Utf8), "field {index}");
624 }
625 }
626 }
627 }
628
629 #[test]
630 fn exact_bound_both_kinds_and_each_host_validation_refuse_without_widening() {
631 for successor in [false, true] {
632 let mut original = packet(successor);
633 let used = original.image.header_len()
634 + original.variable_fields()[1..]
635 .iter()
636 .map(|field| field.len())
637 .sum::<usize>();
638 let path = vec![b'x'; MAX_PACKET_LEN - used];
639 original.coordinator = &path;
640 let full = encode(original).unwrap();
641 assert_eq!(full.len(), 4096);
642 assert_eq!(decode(&full).unwrap(), original);
643 let oversized = vec![b'x'; path.len() + 1];
644 original.coordinator = &oversized;
645 assert_eq!(encode(original), Err(PacketError::Size));
646 assert_eq!(decode(&vec![1; 4097]), Err(PacketError::Size));
647 for field in 0..4 {
648 for empty in [false, true] {
649 let value = if empty { "" } else { "x\0" };
650 let mut invalid = packet(successor);
651 match field {
652 0 => invalid.coordinator = value.as_bytes(),
653 1 => invalid.detlog_identity = value,
654 2 => invalid.forward_policy = value,
655 3 => invalid.observer_identity = value,
656 _ => unreachable!(),
657 }
658 assert_eq!(
659 encode(invalid),
660 Err(if empty {
661 PacketError::Empty
662 } else {
663 PacketError::Nul
664 })
665 );
666 }
667 }
668 }
669 }
670
671 #[test]
672 fn schema_all_positions_are_ascii_hex_and_expected_match_never_uses_a_prefix() {
673 for successor in [false, true] {
674 let original = packet(successor);
675 let valid = encode(original).unwrap();
676 for index in 0..16 {
677 for byte in [b'g', 0, 0xff, b'/', b':'] {
678 let mut changed = valid.clone();
679 changed[48 + index] = byte;
680 assert_eq!(decode(&changed), Err(PacketError::Schema));
681 let schema: &[u8; 16] = changed[48..64].try_into().unwrap();
682 let mut packet = original;
683 packet.config_schema = schema;
684 assert_eq!(encode(packet), Err(PacketError::Schema));
685 }
686 }
687 let uppercase = *b"0123456789ABCDEF";
688 let mut packet = original;
689 packet.config_schema = &uppercase;
690 let bytes = encode(packet).unwrap();
691 assert_eq!(
692 decode(&bytes).unwrap().check_bindings(expected(original)),
693 Err(BindingMismatch::ConfigSchema)
694 );
695 let mut last = *original.config_schema;
696 last[15] = b'0';
697 assert_eq!(&last[..15], &original.config_schema[..15]);
698 let mut independent = expected(original);
699 independent.config_schema = &last;
700 assert_eq!(
701 decode(&valid).unwrap().check_bindings(independent),
702 Err(BindingMismatch::ConfigSchema)
703 );
704 }
705 }
706
707 #[test]
708 fn all_digest_bytes_compare_exactly_with_independent_expectations() {
709 for successor in [false, true] {
710 let original = packet(successor);
711 let bytes = encode(original).unwrap();
712 let decoded = decode(&bytes).unwrap();
713 for field in 0..4 {
714 for index in 0..32 {
715 let mut changed = *[
716 original.config_digest,
717 original.native_source_digest,
718 original.target_digest,
719 original.bootstrap_digest,
720 ][field];
721 changed[index] ^= 1;
722 let mut bindings = expected(original);
723 let error = match field {
724 0 => {
725 bindings.config_digest = &changed;
726 BindingMismatch::ConfigDigest
727 }
728 1 => {
729 bindings.native_source_digest = &changed;
730 BindingMismatch::NativeSourceDigest
731 }
732 2 => {
733 bindings.target_digest = &changed;
734 BindingMismatch::TargetDigest
735 }
736 3 => {
737 bindings.bootstrap_digest = &changed;
738 BindingMismatch::BootstrapDigest
739 }
740 _ => unreachable!(),
741 };
742 assert_eq!(
743 decoded.check_bindings(bindings),
744 Err(error),
745 "field={field} byte={index}"
746 );
747 }
748 }
749 }
750 }
751
752 #[test]
753 fn kinds_have_distinct_token_shapes_and_no_hliinit1_reader_fallback() {
754 for successor in [false, true] {
755 let valid = encode(packet(successor)).unwrap();
756 let mut opposite = valid.clone();
757 opposite[12..14]
758 .copy_from_slice(&(if successor { 1_u16 } else { 2_u16 }).to_be_bytes());
759 assert!(decode(&opposite).is_err());
760 assert_eq!(
761 crate::liteinst_initial_image::decode(&valid),
762 Err(crate::liteinst_initial_image::PacketError::Magic)
763 );
764 }
765 let mut missing_composite = encode(packet(true)).unwrap();
766 missing_composite.drain(192..224);
767 advertised(&mut missing_composite);
768 assert!(decode(&missing_composite).is_err());
769 let mut spurious_composite = encode(packet(false)).unwrap();
770 spurious_composite.splice(192..192, [7; 32]);
771 advertised(&mut spurious_composite);
772 assert!(decode(&spurious_composite).is_err());
773 let path = vec![b'x'; 200];
774 let legacy = crate::liteinst_initial_image::Packet {
775 nonce: &[1; 32],
776 config_digest: &[2; 32],
777 schema_fingerprint: b"0123456789abcdef",
778 coordinator: &path,
779 detlog_identity: "19:7:1234",
780 forward_policy: "INFO",
781 };
782 let legacy_bytes = crate::liteinst_initial_image::encode(legacy).unwrap();
783 assert_eq!(
784 crate::liteinst_initial_image::decode(&legacy_bytes),
785 Ok(legacy)
786 );
787 assert_eq!(decode(&legacy_bytes), Err(PacketError::Magic));
788 }
789
790 #[test]
791 fn packet_tokens_and_identity_text_are_data_not_socket_or_redemption_proof() {
792 let mut original = packet(false);
793 original.image = Image::Initial { nonce: &[0; 32] };
794 original.observer_identity = original.detlog_identity;
796 let bytes = encode(original).unwrap();
797 assert_eq!(decode(&bytes), Ok(original));
798 let mut successor = packet(true);
799 successor.image = Image::Successor {
800 ticket: &[0; 32],
801 composite_digest: &[0; 32],
802 };
803 let bytes = encode(successor).unwrap();
804 assert_eq!(decode(&bytes), Ok(successor));
805 assert!(!format!("{:?}", original.image).contains("[0,"));
806 assert!(!format!("{:?}", successor.image).contains("ticket: ["));
807 }
808 #[test]
809 fn preallocated_encode_preserves_every_existing_wire_byte_and_unused_tail() {
810 for successor in [false, true] {
811 let value = packet(successor);
812 let expected = encode(value).unwrap();
813 let mut output = [0xa5; MAX_PACKET_LEN];
814 let length = encode_into(value, &mut output).unwrap();
815 assert_eq!(&output[..length], expected);
816 assert!(output[length..].iter().all(|byte| *byte == 0xa5));
817 assert_eq!(decode(&output[..length]).unwrap(), value);
818 for short in 0..length {
819 let mut output = vec![0xa5; short];
820 assert_eq!(encode_into(value, &mut output), Err(PacketError::Size));
821 assert!(output.iter().all(|byte| *byte == 0xa5));
822 }
823 }
824 }
825
826 #[test]
827 fn preallocated_invalid_fields_fail_before_writing_any_output() {
828 for successor in [false, true] {
829 for mode in 0..5 {
830 let mut value = packet(successor);
831 match mode {
832 0 => value.coordinator = b"",
833 1 => value.coordinator = b"bad\0path",
834 2 => value.config_schema = b"not-hexadecimal!",
835 3 => value.forward_policy = "",
836 4 => value.observer_identity = "bad\0identity",
837 _ => unreachable!(),
838 }
839 let expected = encode(value).unwrap_err();
840 let mut output = [0xa5; MAX_PACKET_LEN];
841 assert_eq!(encode_into(value, &mut output), Err(expected));
842 assert!(output.iter().all(|byte| *byte == 0xa5));
843 }
844 }
845 }
846}