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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
//! `#[bin]` on an enum — tagged-union dispatch.
//!
//! A protocol union picks one of several payloads. `#[bin]` on an enum expresses that
//! with two **orthogonal** concepts:
//!
//! - **`magic`** — a wire constant that is *read and written*: a byte string (`b"IHDR"`)
//! or a width-suffixed unsigned integer (`0x01u16`). It is the discriminant under
//! *magic dispatch*, or a verified signature on a tag-variant.
//! - **`tag`** — a read-only **selector** taken from `ctx`. It picks the variant but is
//! **never** on the wire.
//!
//! `#[catch_all]` preserves an unknown discriminant (dual-use); without one, a magic enum
//! is a *closed set* and an unknown discriminant is a decode error.
//!
//! A tagged-union enum encodes **verbatim** — the canonical/`encode_mode`/`validate`/`new`
//! surface that a [`#[bin]` struct](super::bin_codec#two-encode-forms-verbatim-vs-canonical)
//! gets for a `reserved`/`calc` field is **struct-only**. Those are properties of a concrete
//! record; for a union they belong to whichever variant payload is selected (each variant is
//! itself a `#[bin]`-style record), not to the dispatch. So canonicalize / validate the
//! payload type, then dispatch.
//!
//! # Magic dispatch — the discriminant is on the wire
//!
//! With no `tag`, each variant's `magic` is its discriminant: decode reads it once and
//! matches; encode writes it. Variants may be unit, tuple, or named.
//!
//! ```
//! use bnb::bin;
//!
//! #[bin(big)]
//! #[derive(Debug, PartialEq)]
//! enum Rdata {
//! #[bin(magic = 1u16)] A(u32),
//! #[bin(magic = 2u16)] Port { lo: u8, hi: u8 },
//! #[bin(magic = 0u16)] Ping,
//! #[catch_all]
//! Other { magic: u16, #[br(count = 2)] raw: Vec<u8> }, // captures an unknown magic
//! }
//!
//! assert_eq!(Rdata::A(0x0808_0808).to_bytes().unwrap(), [0x00, 0x01, 8, 8, 8, 8]);
//! assert_eq!(Rdata::decode_exact(&[0x00, 0x09, 0xAA, 0xBB]).unwrap(),
//! Rdata::Other { magic: 9, raw: vec![0xAA, 0xBB] });
//! assert_eq!(Rdata::A(0).magic(), 1); // the `magic()` accessor
//! ```
//!
//! Byte-string magics work the same way — the natural fit for PNG/RIFF-style signatures:
//!
//! ```
//! use bnb::bin;
//!
//! #[bin(big)]
//! #[derive(Debug, PartialEq)]
//! enum Chunk {
//! #[bin(magic = b"IHDR")] Header { width: u16, height: u16 },
//! #[bin(magic = b"IDAT")] Data(u8),
//! #[catch_all] Other { magic: [u8; 4], #[br(count = 1)] rest: Vec<u8> },
//! }
//!
//! let hdr = [b'I', b'H', b'D', b'R', 0x00, 0x10, 0x00, 0x20];
//! assert_eq!(Chunk::decode_exact(&hdr).unwrap(), Chunk::Header { width: 16, height: 32 });
//! assert_eq!(Chunk::Header { width: 16, height: 32 }.to_bytes().unwrap(), hdr);
//! ```
//!
//! A leading enum-level **`magic` prefix** is verified on read and written on encode,
//! once, before dispatch:
//!
//! ```
//! # use bnb::bin;
//! #[bin(big, magic = b"BNB")]
//! #[derive(Debug, PartialEq)]
//! enum Pre { #[bin(magic = 1u8)] A(u16), #[bin(magic = 2u8)] B }
//!
//! assert_eq!(Pre::A(0xCAFE).to_bytes().unwrap(), [b'B', b'N', b'B', 0x01, 0xCA, 0xFE]);
//! assert!(Pre::decode_exact(&[b'X', b'N', b'B', 0x01, 0, 0]).is_err()); // bad prefix
//! ```
//!
//! # Tag dispatch — a read-only selector, nothing on the wire
//!
//! Declare a selector with `tag = <ctx-param>` and give each variant `#[bin(tag = V)]`.
//! The enum reads/writes **no** discriminant; the parent passes the selector down with
//! `#[br(ctx { … })]`, and `tag()` recovers it (driving a no-drift `calc`).
//!
//! ```
//! use bnb::bin;
//!
//! #[bin(big, ctx(kind: u16), tag = kind)]
//! #[derive(Debug, PartialEq)]
//! enum Body {
//! #[bin(tag = 1)] Login(u32),
//! #[bin(tag = 2)] Data { n: u8 },
//! }
//!
//! #[bin(big)]
//! #[derive(Debug, PartialEq)]
//! struct Packet {
//! #[br(temp)]
//! #[bw(calc = self.body.tag())] // recompute the tag from the chosen variant
//! kind: u16,
//! #[br(ctx { kind })]
//! body: Body,
//! }
//!
//! let p = Packet { body: Body::Data { n: 7 } };
//! assert_eq!(p.to_bytes().unwrap(), [0x00, 0x02, 0x07]); // tag 2 then the payload
//! assert_eq!(Packet::decode_exact(&[0x00, 0x02, 0x07]).unwrap(), p);
//! ```
//!
//! # Composing `tag` + `magic`
//!
//! The two stack: the `tag` selects the variant, then its `magic` is a **signature** —
//! verified on read, written on encode (it *is* on the wire; the tag is not).
//!
//! ```
//! # use bnb::bin;
//! #[bin(big, ctx(kind: u8), tag = kind)]
//! #[derive(Debug, PartialEq)]
//! enum Msg {
//! #[bin(tag = 1, magic = b"LI")] Login(u32), // verify "LI" after the tag picks it
//! #[bin(tag = 2)] Ping, // no signature
//! }
//!
//! let li = [b'L', b'I', 0xAA, 0xBB, 0xCC, 0xDD];
//! assert_eq!(Msg::decode_with_exact(&li, MsgCtx { kind: 1 }).unwrap(), Msg::Login(0xAABB_CCDD));
//! assert!(Msg::decode_with_exact(&[b'X', b'X', 0, 0, 0, 0], MsgCtx { kind: 1 }).is_err());
//! ```
//!
//! # Variable-width magics and a typed fallback
//!
//! Byte-string magics may differ in length: dispatch then **peeks** the longest, matches
//! a prefix, and seeks past the winner (so it needs a seekable source). A variant with
//! **no** `tag`/`magic` is a *typed fallback*, parsed when nothing matched; use a
//! fallback **or** a `#[catch_all]` (which here reads from the unconsumed position), not
//! both. Where nothing matches, the unmatched bytes are still there to read.
//!
//! ```
//! use bnb::bin;
//!
//! #[bin(big)]
//! #[derive(Debug, PartialEq)]
//! enum Frame {
//! #[bin(magic = b"LOGIN")] Login { user: u32 }, // 5-byte magic
//! #[bin(magic = b"BYE")] Bye, // 3-byte magic
//! Raw { len: u8, #[br(count = len)] body: Vec<u8> }, // typed fallback
//! }
//!
//! assert_eq!(Frame::decode_exact(b"BYE").unwrap(), Frame::Bye);
//! assert_eq!(Frame::decode_exact(&[0x02, 0xAA, 0xBB]).unwrap(),
//! Frame::Raw { len: 2, body: vec![0xAA, 0xBB] });
//! ```
//!
//! # Hybrid: `tag` priority, then `magic`
//!
//! One enum can mix both — the selector picks a tag variant first, and an **unmatched**
//! selector falls through to magic dispatch:
//!
//! ```
//! use bnb::bin;
//!
//! #[bin(big, ctx(kind: u8), tag = kind)]
//! #[derive(Debug, PartialEq)]
//! enum Packet {
//! #[bin(tag = 1)] Known(u16), // chosen by kind == 1
//! #[bin(magic = b"EXT")] Extended { sub: u8 }, // else matched by wire magic
//! #[catch_all] Other { magic: [u8; 3], #[br(count = 1)] rest: Vec<u8> },
//! }
//!
//! assert_eq!(Packet::decode_with_exact(&[0xAB, 0xCD], PacketCtx { kind: 1 }).unwrap(),
//! Packet::Known(0xABCD));
//! assert_eq!(Packet::decode_with_exact(b"EXT\x05", PacketCtx { kind: 9 }).unwrap(),
//! Packet::Extended { sub: 5 });
//! ```
//!
//! # Decode helpers
//!
//! Beyond the usual entry points, a dispatched enum gets:
//!
//! - **`decode_as_<variant>(bytes)`** — parse the bytes *as one explicit variant* (its
//! magic, if any, then its payload), bypassing dispatch. Handy when the variant is
//! known out of band, and for tests. (A `ctx` enum takes the context too.)
//! - **`peek_variant(bytes) -> <Name>Kind`** (magic dispatch) — identify *which* variant
//! the bytes are, from the wire magic, **without** parsing the payload — for routing.
//! - **`decode_tagged(selector, bytes)`** (tag dispatch) — feed the selector directly.
//!
//! ```
//! # use bnb::bin;
//! #[bin(big)]
//! #[derive(Debug, PartialEq)]
//! enum Op {
//! #[bin(magic = 1u8)] Get(u16),
//! #[bin(magic = 2u8)] Set { key: u8, val: u8 },
//! }
//!
//! assert_eq!(Op::peek_variant(&[0x02, 9, 9]).unwrap(), OpKind::Set);
//! assert_eq!(Op::decode_as_get(&[0x01, 0xAB, 0xCD]).unwrap(), Op::Get(0xABCD));
//! ```
//!
//! # Notes
//!
//! - **`magic` values are literals**: a byte string, or a width-suffixed unsigned integer
//! (`1u16`, `0xCAu8`). Sub-byte and non-literal magics are rejected so the wire width is
//! always unambiguous. Variable-width / fallback dispatch needs **byte-string** magics
//! (so an unmatched discriminant can be re-read).
//! - A single-read `#[catch_all]` stores the discriminant in its first field (the captured
//! magic, or the selector under tag dispatch) so it can round-trip. On the peek path
//! (variable width / fallback) the magic stays in the catch-all's own fields instead.
//! - Variant fields support the full directive grammar (`count`, `if`, `map`, `ctx`,
//! `temp`/`calc`, `parse_with`, …), so a catch-all can read its own length and recompute
//! it on encode.
//! - `tag()` / `magic()` return the variant's discriminant — generated only when there is a
//! single one to report (so not for variable-width magic, a typed fallback, or a hybrid);
//! `peek_variant` likewise needs an on-wire magic (so not under tag/hybrid dispatch).
//! - With overlapping byte-string magics, declaration order decides — a magic that is a
//! prefix of another should come first, and a fallback must not begin like a magic.