1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
//! In-memory canonicalization helpers, generated alongside `to_canonical_bytes` when a
//! message has a `reserved` or non-`temp` `calc` field:
//! * `to_canonical(self) -> Self` — reserved → spec value, `calc` → recomputed;
//! * `canonical_diff(&self) -> Vec<&'static str>` — names of fields that aren't canonical;
//! * `is_canonical(&self) -> bool`.
mod macro_ {
use bnb::{EncodeExt, EncodeMode, bin, u4};
#[bin(big)]
#[derive(Debug, Clone, PartialEq, Eq)]
struct Msg {
tag: u4,
#[reserved]
rsv: u4, // spec value: 0
#[bw(calc = self.payload.wrapping_add(1))]
#[builder(default)]
check: u8, // canonical value: payload + 1
payload: u8,
}
#[test]
fn to_canonical_recomputes_calc_and_normalizes_reserved() {
// A deliberately non-canonical value: reserved bits set, a stale checksum. (Construction
// is builder-only now — the injected `encode_mode` field can't be named in a literal.)
let m = Msg::builder()
.tag(u4::new(0xA))
.rsv(u4::new(0xF))
.check(0x99)
.payload(0x10)
.build()
.unwrap();
// Verbatim `to_bytes` keeps the bogus values exactly (E1's verbatim contract).
assert_eq!(m.to_bytes().unwrap(), [0xAF, 0x99, 0x10]);
// `canonical_diff` names the off fields; `is_canonical` is false.
let mut diff = m.canonical_diff();
diff.sort_unstable();
assert_eq!(diff, ["check", "rsv"]);
assert!(!m.is_canonical());
// `to_canonical` produces the canonical form in memory.
let c = m.clone().to_canonical();
assert_eq!(c.rsv, u4::new(0)); // reserved → spec value
assert_eq!(c.check, 0x11); // calc → payload + 1
assert_eq!(c.tag, u4::new(0xA)); // ordinary fields unchanged
assert_eq!(c.payload, 0x10);
assert!(c.is_canonical());
assert!(c.canonical_diff().is_empty());
// The defining identity: `x.to_canonical().to_bytes() == x.to_canonical_bytes()`.
assert_eq!(c.to_bytes().unwrap(), m.to_canonical_bytes().unwrap());
assert_eq!(m.to_canonical_bytes().unwrap(), [0xA0, 0x11, 0x10]);
}
#[test]
fn an_already_canonical_value_has_no_diff() {
let m = Msg::builder()
.tag(u4::new(0x3))
.payload(0x20)
.build()
.unwrap(); // builder defaults rsv (spec) and omits check entirely
// The builder default leaves `check` at 0, which is *not* payload+1 (0x21), so the
// freshly-built value isn't canonical until canonicalized.
assert_eq!(m.canonical_diff(), ["check"]);
let c = m.to_canonical();
assert!(c.is_canonical());
assert_eq!(c.check, 0x21);
}
#[test]
fn encode_follows_the_values_mode() {
// The std-writer `encode(w)` follows the value's `encode_mode`; the Vec methods stay
// explicit (`to_bytes` verbatim, `to_canonical_bytes` canonical).
let m = Msg::builder()
.tag(u4::new(0xA))
.rsv(u4::new(0xF))
.check(0x99)
.payload(0x10)
.build()
.unwrap();
// The builder defaults the mode to Verbatim.
assert_eq!(m.encode_mode(), EncodeMode::Verbatim);
let mut verbatim = Vec::new();
m.encode(&mut verbatim).unwrap();
assert_eq!(verbatim, m.to_bytes().unwrap());
// `with_encode_mode(Canonical)` → `encode` now writes the canonical form.
let mut canonical = Vec::new();
m.clone()
.with_encode_mode(EncodeMode::Canonical)
.encode(&mut canonical)
.unwrap();
assert_eq!(canonical, m.to_canonical_bytes().unwrap());
assert_ne!(verbatim, canonical); // reserved + stale calc → the two forms differ
// `set_encode_mode` (in place) and the builder's `.encode_mode(…)` reach the same state.
let mut m2 = m.clone();
m2.set_encode_mode(EncodeMode::Canonical);
assert_eq!(m2.encode_mode(), EncodeMode::Canonical);
let built = Msg::builder()
.tag(u4::new(0xA))
.payload(0x10)
.encode_mode(EncodeMode::Canonical)
.build()
.unwrap();
assert_eq!(built.encode_mode(), EncodeMode::Canonical);
// The mode is excluded from equality: two values differing only in mode are equal.
assert_eq!(m, m.clone().with_encode_mode(EncodeMode::Canonical));
// A decoded value defaults to Verbatim (so decode -> encode round-trips).
let decoded = Msg::decode_exact(&verbatim).unwrap();
assert_eq!(decoded.encode_mode(), EncodeMode::Verbatim);
let mut re = Vec::new();
decoded.encode(&mut re).unwrap();
assert_eq!(re, verbatim);
}
#[test]
fn new_constructs_from_all_stored_fields() {
// `new` is the literal replacement: every stored field in declaration order (reserved +
// calc included), and the encode mode starts at Verbatim.
let m = Msg::new(u4::new(0xA), u4::new(0xF), 0x99, 0x10);
assert_eq!(m.encode_mode(), EncodeMode::Verbatim);
assert_eq!(m.to_bytes().unwrap(), [0xAF, 0x99, 0x10]);
// Same value as the all-fields-set builder.
let b = Msg::builder()
.tag(u4::new(0xA))
.rsv(u4::new(0xF))
.check(0x99)
.payload(0x10)
.build()
.unwrap();
assert_eq!(m, b);
}
#[test]
fn mode_is_excluded_from_debug() {
let m = Msg::builder()
.tag(u4::new(1))
.payload(2)
.encode_mode(EncodeMode::Canonical)
.build()
.unwrap();
let s = format!("{m:?}");
assert!(
!s.contains("encode_mode"),
"Debug must not show the render-preference field: {s}"
);
assert!(s.contains("tag") && s.contains("payload"), "{s}");
}
}