Skip to main content

riffdb_types/
codec.rs

1//! Versioned canonical encoding for [`CanonicalValue`](crate::CanonicalValue).
2
3use std::fmt;
4
5use crate::{
6    CanonicalBytes, CanonicalList, CanonicalRecord, CanonicalString, CanonicalValue,
7    CanonicalVector, CurrencyCode, Date, Decimal, DecimalSpec, EnumTypeId, EnumVariantId, FieldId,
8    Money, Timestamp,
9    limits::{
10        MAX_ATOMIC_COMMAND_REQUEST_BYTES_V2, MAX_BYTES_VALUE_BYTES, MAX_CANONICAL_DOCUMENT_BYTES,
11        MAX_LIST_ENTRIES, MAX_NESTING_DEPTH, MAX_RECORD_FIELDS, MAX_STRING_BYTES,
12    },
13};
14
15#[path = "codec_decode_capacity.rs"]
16mod decode_capacity;
17pub use decode_capacity::*;
18
19/// Canonical value encoding version defined by ADR-0011.
20pub const CANONICAL_VALUE_VERSION: u8 = 0x01;
21
22const TAG_NULL: u8 = 0x00;
23const TAG_BOOL: u8 = 0x01;
24const TAG_I64: u8 = 0x02;
25const TAG_U64: u8 = 0x03;
26const TAG_DECIMAL: u8 = 0x04;
27const TAG_MONEY: u8 = 0x05;
28const TAG_STRING: u8 = 0x06;
29const TAG_BYTES: u8 = 0x07;
30const TAG_TIMESTAMP: u8 = 0x08;
31const TAG_DATE: u8 = 0x09;
32const TAG_UUID: u8 = 0x0a;
33const TAG_ENUM: u8 = 0x0b;
34const TAG_LIST: u8 = 0x0c;
35const TAG_RECORD: u8 = 0x0d;
36const TAG_VECTOR: u8 = 0x0e;
37
38/// Encodes one value using canonical value encoding v1.
39///
40/// Thin wrapper around [`encode_canonical_value_into`]. Prefer the into form
41/// when appending many cells into a pre-reserved buffer.
42pub fn encode_canonical_value(value: &CanonicalValue) -> Result<Vec<u8>, CanonicalCodecError> {
43    let mut output = Vec::with_capacity(reserve_hint(canonical_value_encoded_len(value)));
44    encode_canonical_value_into(&mut output, value)?;
45    Ok(output)
46}
47
48/// Turns an exact-length result into a capacity request.
49///
50/// [`canonical_value_encoded_len`] and [`canonical_record_encoded_len`] reject
51/// exactly the faults the encoder rejects, so a length failure means the encode
52/// that follows will fail too. Requesting zero capacity in that case keeps the
53/// reported error the encoder's own, byte-for-byte as before pre-reservation,
54/// and `Vec::with_capacity(0)` does not allocate.
55const fn reserve_hint(length: Result<usize, CanonicalCodecError>) -> usize {
56    match length {
57        Ok(length) => length,
58        Err(_) => 0,
59    }
60}
61
62/// Appends one complete canonical value encoding v1 document onto `output`.
63///
64/// Byte-identical to [`encode_canonical_value`]; document-size accounting is
65/// relative to the start of this append so callers may pack many cells into
66/// one contiguous buffer without false `DocumentTooLarge` faults.
67///
68/// All-or-nothing: on `Err` the buffer is restored to exactly its contents
69/// before the call, so a caller that keeps packing after a rejected cell never
70/// emits partial-document bytes under a valid offset.
71pub fn encode_canonical_value_into(
72    output: &mut Vec<u8>,
73    value: &CanonicalValue,
74) -> Result<(), CanonicalCodecError> {
75    let document_start = output.len();
76    let mut encoder = Encoder {
77        output: std::mem::take(output),
78        document_start,
79        maximum_document_bytes: MAX_CANONICAL_DOCUMENT_BYTES,
80    };
81    let result = encoder.encode_value(value, 0);
82    *output = encoder.output;
83    if result.is_err() {
84        output.truncate(document_start);
85    }
86    result
87}
88
89/// Exact encoded byte length of one value under canonical value encoding v1.
90///
91/// Matches [`encode_canonical_value`] without allocating the document. Rejects
92/// the same bound, nesting, and structural faults so callers can treat length
93/// and encode as interchangeable for fuel and charge accounting.
94pub fn canonical_value_encoded_len(value: &CanonicalValue) -> Result<usize, CanonicalCodecError> {
95    let mut counter = LengthCounter::new(MAX_CANONICAL_DOCUMENT_BYTES);
96    counter.encode_value(value, 0)?;
97    Ok(counter.len)
98}
99
100/// Exact encoded byte length of one record document under canonical value
101/// encoding v1.
102///
103/// The record-shaped mirror of [`canonical_value_encoded_len`]: matches
104/// [`encode_canonical_record`] without cloning the record into a
105/// [`CanonicalValue::Record`] first, and rejects the same bound, ordering, and
106/// nesting faults.
107pub fn canonical_record_encoded_len(
108    record: &CanonicalRecord,
109) -> Result<usize, CanonicalCodecError> {
110    let mut counter = LengthCounter::new(MAX_CANONICAL_DOCUMENT_BYTES);
111    counter.encode_record_value(record, 0)?;
112    Ok(counter.len)
113}
114
115/// Encodes one borrowed record as a complete canonical `Value::Record` document.
116///
117/// This produces exactly the same v1 bytes as [`encode_canonical_value`] without
118/// cloning the record and its potentially large value graph first. The output
119/// buffer is reserved once at its exact final length, so a record encode never
120/// grows its buffer.
121pub fn encode_canonical_record(record: &CanonicalRecord) -> Result<Vec<u8>, CanonicalCodecError> {
122    let mut encoder = Encoder {
123        output: Vec::with_capacity(reserve_hint(canonical_record_encoded_len(record))),
124        document_start: 0,
125        maximum_document_bytes: MAX_CANONICAL_DOCUMENT_BYTES,
126    };
127    encoder.encode_record_value(record, 0)?;
128    Ok(encoder.output)
129}
130
131/// Appends one borrowed canonical record without cloning its value graph.
132/// A caller can preflight [`canonical_record_encoded_len`] and admit the output
133/// capacity first. Errors restore the original output length.
134pub fn encode_canonical_record_into(
135    output: &mut Vec<u8>,
136    record: &CanonicalRecord,
137) -> Result<(), CanonicalCodecError> {
138    let document_start = output.len();
139    let mut encoder = Encoder {
140        output: std::mem::take(output),
141        document_start,
142        maximum_document_bytes: MAX_CANONICAL_DOCUMENT_BYTES,
143    };
144    let result = encoder.encode_record_value(record, 0);
145    *output = encoder.output;
146    if result.is_err() {
147        output.truncate(document_start);
148    }
149    result
150}
151
152/// Exact encoded byte length of one canonical atomic-command input record.
153///
154/// Canonical value encoding remains V1 and every nested scalar retains its
155/// ordinary bound. Only the complete command-input record receives the
156/// independently bounded V2 envelope.
157pub fn canonical_command_input_encoded_len(
158    record: &CanonicalRecord,
159) -> Result<usize, CanonicalCodecError> {
160    let mut counter = LengthCounter::new(MAX_ATOMIC_COMMAND_REQUEST_BYTES_V2);
161    counter.encode_record_value(record, 0)?;
162    Ok(counter.len)
163}
164
165/// Encodes one canonical atomic-command input under its fixed V2 envelope.
166///
167/// Inputs that fit the legacy 1 MiB envelope produce byte-identical output to
168/// [`encode_canonical_record`].
169pub fn encode_canonical_command_input(
170    record: &CanonicalRecord,
171) -> Result<Vec<u8>, CanonicalCodecError> {
172    let mut encoder = Encoder {
173        output: Vec::with_capacity(reserve_hint(canonical_command_input_encoded_len(record))),
174        document_start: 0,
175        maximum_document_bytes: MAX_ATOMIC_COMMAND_REQUEST_BYTES_V2,
176    };
177    encoder.encode_record_value(record, 0)?;
178    Ok(encoder.output)
179}
180
181/// Decodes exactly one canonical value encoding v1 document.
182pub fn decode_canonical_value(input: &[u8]) -> Result<CanonicalValue, CanonicalCodecError> {
183    if input.len() > MAX_CANONICAL_DOCUMENT_BYTES {
184        return Err(CanonicalCodecError::DocumentTooLarge {
185            actual: input.len(),
186            maximum: MAX_CANONICAL_DOCUMENT_BYTES,
187        });
188    }
189
190    let mut decoder = Decoder { input, position: 0 };
191    let value = decoder.decode_value(0)?;
192    if decoder.position != input.len() {
193        return Err(CanonicalCodecError::TrailingBytes {
194            count: input.len() - decoder.position,
195        });
196    }
197    Ok(value)
198}
199
200struct Encoder {
201    output: Vec<u8>,
202    /// Start index of the document currently being written (for size bounds).
203    document_start: usize,
204    maximum_document_bytes: usize,
205}
206
207struct LengthCounter {
208    len: usize,
209    maximum_document_bytes: usize,
210}
211
212impl LengthCounter {
213    const fn new(maximum_document_bytes: usize) -> Self {
214        Self {
215            len: 0,
216            maximum_document_bytes,
217        }
218    }
219}
220
221impl Encoder {
222    fn validate_depth(depth: usize) -> Result<(), CanonicalCodecError> {
223        if depth > MAX_NESTING_DEPTH {
224            return Err(CanonicalCodecError::NestingTooDeep {
225                depth,
226                maximum: MAX_NESTING_DEPTH,
227            });
228        }
229        Ok(())
230    }
231
232    fn encode_value(
233        &mut self,
234        value: &CanonicalValue,
235        depth: usize,
236    ) -> Result<(), CanonicalCodecError> {
237        Self::validate_depth(depth)?;
238
239        self.write(&[CANONICAL_VALUE_VERSION])?;
240        match value {
241            CanonicalValue::Null => self.write(&[TAG_NULL]),
242            CanonicalValue::Bool(value) => self.write(&[TAG_BOOL, u8::from(*value)]),
243            CanonicalValue::I64(value) => {
244                self.write(&[TAG_I64])?;
245                self.write(&value.to_be_bytes())
246            }
247            CanonicalValue::U64(value) => {
248                self.write(&[TAG_U64])?;
249                self.write(&value.to_be_bytes())
250            }
251            CanonicalValue::Decimal(value) => {
252                self.write(&[TAG_DECIMAL])?;
253                self.encode_decimal(value)
254            }
255            CanonicalValue::Money(value) => {
256                self.write(&[TAG_MONEY])?;
257                self.write(value.currency().as_bytes())?;
258                self.encode_decimal(&value.amount())
259            }
260            CanonicalValue::String(value) => {
261                self.write(&[TAG_STRING])?;
262                self.encode_bounded_bytes(value.as_str().as_bytes(), BoundedKind::String)
263            }
264            CanonicalValue::Bytes(value) => {
265                self.write(&[TAG_BYTES])?;
266                self.encode_bounded_bytes(value.as_bytes(), BoundedKind::Bytes)
267            }
268            CanonicalValue::Timestamp(value) => {
269                self.write(&[TAG_TIMESTAMP])?;
270                self.write(&value.seconds().to_be_bytes())?;
271                self.write(&value.nanoseconds().to_be_bytes())
272            }
273            CanonicalValue::Date(value) => {
274                self.write(&[TAG_DATE])?;
275                self.write(&value.days_since_unix_epoch().to_be_bytes())
276            }
277            CanonicalValue::Uuid(value) => {
278                self.write(&[TAG_UUID])?;
279                self.write(value)
280            }
281            CanonicalValue::Enum {
282                type_id,
283                variant_id,
284            } => {
285                self.write(&[TAG_ENUM])?;
286                self.write(&type_id.get().to_be_bytes())?;
287                self.write(&variant_id.get().to_be_bytes())
288            }
289            CanonicalValue::List(values) => {
290                if values.len() > MAX_LIST_ENTRIES {
291                    return Err(CanonicalCodecError::TooManyEntries {
292                        kind: CollectionKind::List,
293                        actual: values.len(),
294                        maximum: MAX_LIST_ENTRIES,
295                    });
296                }
297                self.write(&[TAG_LIST])?;
298                self.write(&(values.len() as u32).to_be_bytes())?;
299                for value in values.values() {
300                    self.encode_value(value, depth + 1)?;
301                }
302                Ok(())
303            }
304            CanonicalValue::Record(record) => self.encode_record_payload(record, depth),
305            CanonicalValue::Vector(vector) => {
306                self.write(&[TAG_VECTOR])?;
307                self.write(&vector.dimension().to_be_bytes())?;
308                for component in vector.components() {
309                    self.write(&component.to_be_bytes())?;
310                }
311                Ok(())
312            }
313        }
314    }
315
316    fn encode_record_value(
317        &mut self,
318        record: &CanonicalRecord,
319        depth: usize,
320    ) -> Result<(), CanonicalCodecError> {
321        Self::validate_depth(depth)?;
322        self.write(&[CANONICAL_VALUE_VERSION])?;
323        self.encode_record_payload(record, depth)
324    }
325
326    fn encode_record_payload(
327        &mut self,
328        record: &CanonicalRecord,
329        depth: usize,
330    ) -> Result<(), CanonicalCodecError> {
331        if record.len() > MAX_RECORD_FIELDS {
332            return Err(CanonicalCodecError::TooManyEntries {
333                kind: CollectionKind::Record,
334                actual: record.len(),
335                maximum: MAX_RECORD_FIELDS,
336            });
337        }
338        self.write(&[TAG_RECORD])?;
339        self.write(&(record.len() as u32).to_be_bytes())?;
340        let mut previous = None;
341        for (field_id, value) in record.fields() {
342            if previous.is_some_and(|id| id >= field_id.get()) {
343                return Err(CanonicalCodecError::NonCanonicalRecordOrder);
344            }
345            previous = Some(field_id.get());
346            self.write(&field_id.get().to_be_bytes())?;
347            self.encode_value(value, depth + 1)?;
348        }
349        Ok(())
350    }
351
352    fn encode_decimal(&mut self, value: &Decimal) -> Result<(), CanonicalCodecError> {
353        self.write(&[value.spec().precision(), value.spec().scale()])?;
354        self.write(&value.coefficient().to_be_bytes())
355    }
356
357    fn encode_bounded_bytes(
358        &mut self,
359        value: &[u8],
360        kind: BoundedKind,
361    ) -> Result<(), CanonicalCodecError> {
362        let maximum = match kind {
363            BoundedKind::String => MAX_STRING_BYTES,
364            BoundedKind::Bytes => MAX_BYTES_VALUE_BYTES,
365        };
366        if value.len() > maximum {
367            return Err(match kind {
368                BoundedKind::String => CanonicalCodecError::StringTooLarge {
369                    actual: value.len(),
370                    maximum,
371                },
372                BoundedKind::Bytes => CanonicalCodecError::BytesTooLarge {
373                    actual: value.len(),
374                    maximum,
375                },
376            });
377        }
378        self.write(&(value.len() as u32).to_be_bytes())?;
379        self.write(value)
380    }
381
382    fn write(&mut self, bytes: &[u8]) -> Result<(), CanonicalCodecError> {
383        let document_len = self.output.len().saturating_sub(self.document_start);
384        let new_length =
385            document_len
386                .checked_add(bytes.len())
387                .ok_or(CanonicalCodecError::DocumentTooLarge {
388                    actual: usize::MAX,
389                    maximum: self.maximum_document_bytes,
390                })?;
391        if new_length > self.maximum_document_bytes {
392            return Err(CanonicalCodecError::DocumentTooLarge {
393                actual: new_length,
394                maximum: self.maximum_document_bytes,
395            });
396        }
397        self.output.extend_from_slice(bytes);
398        Ok(())
399    }
400}
401
402impl LengthCounter {
403    fn validate_depth(depth: usize) -> Result<(), CanonicalCodecError> {
404        Encoder::validate_depth(depth)
405    }
406
407    /// Mirrors [`Encoder::encode_record_value`] step for step.
408    fn encode_record_value(
409        &mut self,
410        record: &CanonicalRecord,
411        depth: usize,
412    ) -> Result<(), CanonicalCodecError> {
413        Self::validate_depth(depth)?;
414        self.add(1)?;
415        self.encode_record_payload(record, depth)
416    }
417
418    fn encode_value(
419        &mut self,
420        value: &CanonicalValue,
421        depth: usize,
422    ) -> Result<(), CanonicalCodecError> {
423        Self::validate_depth(depth)?;
424        self.add(1)?; // version
425        match value {
426            CanonicalValue::Null => self.add(1),
427            CanonicalValue::Bool(_) => self.add(2),
428            CanonicalValue::I64(_) => self.add(1 + 8),
429            CanonicalValue::U64(_) => self.add(1 + 8),
430            CanonicalValue::Decimal(value) => {
431                self.add(1)?;
432                self.encode_decimal(value)
433            }
434            CanonicalValue::Money(value) => {
435                self.add(1 + 3)?;
436                self.encode_decimal(&value.amount())
437            }
438            CanonicalValue::String(value) => {
439                self.add(1)?;
440                self.encode_bounded_bytes(value.as_str().as_bytes(), BoundedKind::String)
441            }
442            CanonicalValue::Bytes(value) => {
443                self.add(1)?;
444                self.encode_bounded_bytes(value.as_bytes(), BoundedKind::Bytes)
445            }
446            CanonicalValue::Timestamp(_) => self.add(1 + 8 + 4),
447            CanonicalValue::Date(_) => self.add(1 + 4),
448            CanonicalValue::Uuid(_) => self.add(1 + 16),
449            CanonicalValue::Enum { .. } => self.add(1 + 4 + 4),
450            CanonicalValue::List(values) => {
451                if values.len() > MAX_LIST_ENTRIES {
452                    return Err(CanonicalCodecError::TooManyEntries {
453                        kind: CollectionKind::List,
454                        actual: values.len(),
455                        maximum: MAX_LIST_ENTRIES,
456                    });
457                }
458                self.add(1 + 4)?;
459                for value in values.values() {
460                    self.encode_value(value, depth + 1)?;
461                }
462                Ok(())
463            }
464            CanonicalValue::Record(record) => self.encode_record_payload(record, depth),
465            CanonicalValue::Vector(vector) => {
466                // tag + 4 bytes dimension + 4 bytes per component
467                self.add(1 + 4 + (vector.dimension() as usize) * 4)?;
468                Ok(())
469            }
470        }
471    }
472
473    fn encode_record_payload(
474        &mut self,
475        record: &CanonicalRecord,
476        depth: usize,
477    ) -> Result<(), CanonicalCodecError> {
478        if record.len() > MAX_RECORD_FIELDS {
479            return Err(CanonicalCodecError::TooManyEntries {
480                kind: CollectionKind::Record,
481                actual: record.len(),
482                maximum: MAX_RECORD_FIELDS,
483            });
484        }
485        self.add(1 + 4)?;
486        let mut previous = None;
487        for (field_id, value) in record.fields() {
488            if previous.is_some_and(|id| id >= field_id.get()) {
489                return Err(CanonicalCodecError::NonCanonicalRecordOrder);
490            }
491            previous = Some(field_id.get());
492            self.add(4)?;
493            self.encode_value(value, depth + 1)?;
494        }
495        Ok(())
496    }
497
498    fn encode_decimal(&mut self, value: &Decimal) -> Result<(), CanonicalCodecError> {
499        let _ = value;
500        self.add(2 + 16)
501    }
502
503    fn encode_bounded_bytes(
504        &mut self,
505        value: &[u8],
506        kind: BoundedKind,
507    ) -> Result<(), CanonicalCodecError> {
508        let maximum = match kind {
509            BoundedKind::String => MAX_STRING_BYTES,
510            BoundedKind::Bytes => MAX_BYTES_VALUE_BYTES,
511        };
512        if value.len() > maximum {
513            return Err(match kind {
514                BoundedKind::String => CanonicalCodecError::StringTooLarge {
515                    actual: value.len(),
516                    maximum,
517                },
518                BoundedKind::Bytes => CanonicalCodecError::BytesTooLarge {
519                    actual: value.len(),
520                    maximum,
521                },
522            });
523        }
524        self.add(4 + value.len())
525    }
526
527    fn add(&mut self, bytes: usize) -> Result<(), CanonicalCodecError> {
528        let new_length =
529            self.len
530                .checked_add(bytes)
531                .ok_or(CanonicalCodecError::DocumentTooLarge {
532                    actual: usize::MAX,
533                    maximum: self.maximum_document_bytes,
534                })?;
535        if new_length > self.maximum_document_bytes {
536            return Err(CanonicalCodecError::DocumentTooLarge {
537                actual: new_length,
538                maximum: self.maximum_document_bytes,
539            });
540        }
541        self.len = new_length;
542        Ok(())
543    }
544}
545
546struct Decoder<'a> {
547    input: &'a [u8],
548    position: usize,
549}
550
551impl Decoder<'_> {
552    fn decode_value(&mut self, depth: usize) -> Result<CanonicalValue, CanonicalCodecError> {
553        if depth > MAX_NESTING_DEPTH {
554            return Err(CanonicalCodecError::NestingTooDeep {
555                depth,
556                maximum: MAX_NESTING_DEPTH,
557            });
558        }
559
560        let version = self.read_u8()?;
561        if version != CANONICAL_VALUE_VERSION {
562            return Err(CanonicalCodecError::UnsupportedVersion { version });
563        }
564        let tag = self.read_u8()?;
565        match tag {
566            TAG_NULL => Ok(CanonicalValue::Null),
567            TAG_BOOL => match self.read_u8()? {
568                0 => Ok(CanonicalValue::Bool(false)),
569                1 => Ok(CanonicalValue::Bool(true)),
570                value => Err(CanonicalCodecError::InvalidBoolean { value }),
571            },
572            TAG_I64 => Ok(CanonicalValue::I64(i64::from_be_bytes(self.read_array()?))),
573            TAG_U64 => Ok(CanonicalValue::U64(u64::from_be_bytes(self.read_array()?))),
574            TAG_DECIMAL => Ok(CanonicalValue::Decimal(self.decode_decimal()?)),
575            TAG_MONEY => {
576                let currency_bytes: [u8; 3] = self.read_array()?;
577                let currency_text = std::str::from_utf8(&currency_bytes)
578                    .map_err(|_| CanonicalCodecError::InvalidCurrency)?;
579                let currency = CurrencyCode::new(currency_text)
580                    .map_err(|_| CanonicalCodecError::InvalidCurrency)?;
581                let amount = self.decode_decimal()?;
582                Ok(CanonicalValue::Money(Money::new(currency, amount)))
583            }
584            TAG_STRING => {
585                let bytes = self.decode_bounded_bytes(BoundedKind::String)?;
586                let value = std::str::from_utf8(bytes)
587                    .map_err(|_| CanonicalCodecError::InvalidUtf8)?
588                    .to_owned();
589                Ok(CanonicalValue::String(
590                    CanonicalString::new(value).map_err(|_| {
591                        CanonicalCodecError::StringTooLarge {
592                            actual: bytes.len(),
593                            maximum: MAX_STRING_BYTES,
594                        }
595                    })?,
596                ))
597            }
598            TAG_BYTES => {
599                let bytes = self.decode_bounded_bytes(BoundedKind::Bytes)?;
600                Ok(CanonicalValue::Bytes(
601                    CanonicalBytes::new(bytes.to_vec()).map_err(|_| {
602                        CanonicalCodecError::BytesTooLarge {
603                            actual: bytes.len(),
604                            maximum: MAX_BYTES_VALUE_BYTES,
605                        }
606                    })?,
607                ))
608            }
609            TAG_TIMESTAMP => {
610                let seconds = i64::from_be_bytes(self.read_array()?);
611                let nanos = u32::from_be_bytes(self.read_array()?);
612                Timestamp::new(seconds, nanos)
613                    .map(CanonicalValue::Timestamp)
614                    .map_err(|_| CanonicalCodecError::InvalidTimestamp)
615            }
616            TAG_DATE => Ok(CanonicalValue::Date(Date::from_days_since_unix_epoch(
617                i32::from_be_bytes(self.read_array()?),
618            ))),
619            TAG_UUID => Ok(CanonicalValue::Uuid(self.read_array()?)),
620            TAG_ENUM => {
621                let type_id = EnumTypeId::new(u32::from_be_bytes(self.read_array()?))
622                    .ok_or(CanonicalCodecError::ZeroEnumTypeId)?;
623                let variant_id = EnumVariantId::new(u32::from_be_bytes(self.read_array()?))
624                    .ok_or(CanonicalCodecError::ZeroEnumVariantId)?;
625                Ok(CanonicalValue::Enum {
626                    type_id,
627                    variant_id,
628                })
629            }
630            TAG_LIST => {
631                let count = self.read_collection_count(CollectionKind::List)?;
632                let mut values = Vec::with_capacity(count);
633                for _ in 0..count {
634                    values.push(self.decode_value(depth + 1)?);
635                }
636                Ok(CanonicalValue::List(CanonicalList::new(values).map_err(
637                    |_| CanonicalCodecError::TooManyEntries {
638                        kind: CollectionKind::List,
639                        actual: count,
640                        maximum: MAX_LIST_ENTRIES,
641                    },
642                )?))
643            }
644            TAG_RECORD => {
645                let count = self.read_collection_count(CollectionKind::Record)?;
646                let mut fields = Vec::with_capacity(count);
647                let mut previous = None;
648                for _ in 0..count {
649                    let raw_field_id = u32::from_be_bytes(self.read_array()?);
650                    if previous.is_some_and(|id| id >= raw_field_id) {
651                        return Err(CanonicalCodecError::NonCanonicalRecordOrder);
652                    }
653                    previous = Some(raw_field_id);
654                    let field_id =
655                        FieldId::new(raw_field_id).ok_or(CanonicalCodecError::ZeroFieldId)?;
656                    fields.push((field_id, self.decode_value(depth + 1)?));
657                }
658                Ok(CanonicalValue::Record(
659                    CanonicalRecord::from_canonical_fields(fields),
660                ))
661            }
662            TAG_VECTOR => {
663                let dimension = u32::from_be_bytes(self.read_array()?);
664                if dimension == 0 || dimension > crate::MAX_VECTOR_DIMENSION {
665                    // The tag is known; the payload is out of range.
666                    return Err(CanonicalCodecError::VectorDimensionOutOfRange {
667                        actual: dimension,
668                        maximum: crate::MAX_VECTOR_DIMENSION,
669                    });
670                }
671                let mut components = Vec::with_capacity(dimension as usize);
672                for index in 0..dimension as usize {
673                    let bits = u32::from_be_bytes(self.read_array()?);
674                    let component = f32::from_bits(bits);
675                    // Strict canonical decode: NaN, infinities, and the -0.0
676                    // bit pattern are not canonical bytes. Rejecting (rather
677                    // than canonicalizing) keeps encode/decode an
678                    // isomorphism, so distinct durable byte strings never
679                    // decode to equal values.
680                    if !component.is_finite() || bits == (-0.0_f32).to_bits() {
681                        return Err(CanonicalCodecError::NonCanonicalVectorComponent { index });
682                    }
683                    components.push(component);
684                }
685                // Unreachable today (dimension and every component were
686                // validated above), but if either check ever drifts the
687                // constructor's error must keep its meaning instead of
688                // mislabeling a component fault as a dimension fault.
689                CanonicalVector::new(components)
690                    .map(CanonicalValue::Vector)
691                    .map_err(|error| match error {
692                        crate::CanonicalVectorError::DimensionOutOfRange { .. } => {
693                            CanonicalCodecError::VectorDimensionOutOfRange {
694                                actual: dimension,
695                                maximum: crate::MAX_VECTOR_DIMENSION,
696                            }
697                        }
698                        crate::CanonicalVectorError::NonFiniteComponent { index } => {
699                            CanonicalCodecError::NonCanonicalVectorComponent { index }
700                        }
701                    })
702            }
703            tag => Err(CanonicalCodecError::UnknownTag { tag }),
704        }
705    }
706
707    fn decode_decimal(&mut self) -> Result<Decimal, CanonicalCodecError> {
708        let precision = self.read_u8()?;
709        let scale = self.read_u8()?;
710        let coefficient = i128::from_be_bytes(self.read_array()?);
711        let spec =
712            DecimalSpec::new(precision, scale).map_err(|_| CanonicalCodecError::InvalidDecimal)?;
713        Decimal::new(spec, coefficient).map_err(|_| CanonicalCodecError::InvalidDecimal)
714    }
715
716    fn decode_bounded_bytes(&mut self, kind: BoundedKind) -> Result<&[u8], CanonicalCodecError> {
717        let length = u32::from_be_bytes(self.read_array()?) as usize;
718        let maximum = match kind {
719            BoundedKind::String => MAX_STRING_BYTES,
720            BoundedKind::Bytes => MAX_BYTES_VALUE_BYTES,
721        };
722        if length > maximum {
723            return Err(match kind {
724                BoundedKind::String => CanonicalCodecError::StringTooLarge {
725                    actual: length,
726                    maximum,
727                },
728                BoundedKind::Bytes => CanonicalCodecError::BytesTooLarge {
729                    actual: length,
730                    maximum,
731                },
732            });
733        }
734        self.read(length)
735    }
736
737    fn read_collection_count(
738        &mut self,
739        kind: CollectionKind,
740    ) -> Result<usize, CanonicalCodecError> {
741        let count = u32::from_be_bytes(self.read_array()?) as usize;
742        let maximum = match kind {
743            CollectionKind::List => MAX_LIST_ENTRIES,
744            CollectionKind::Record => MAX_RECORD_FIELDS,
745        };
746        if count > maximum {
747            return Err(CanonicalCodecError::TooManyEntries {
748                kind,
749                actual: count,
750                maximum,
751            });
752        }
753        Ok(count)
754    }
755
756    fn read_u8(&mut self) -> Result<u8, CanonicalCodecError> {
757        Ok(self.read(1)?[0])
758    }
759
760    fn read_array<const N: usize>(&mut self) -> Result<[u8; N], CanonicalCodecError> {
761        self.read(N)?
762            .try_into()
763            .map_err(|_| CanonicalCodecError::UnexpectedEnd)
764    }
765
766    fn read(&mut self, length: usize) -> Result<&[u8], CanonicalCodecError> {
767        let end = self
768            .position
769            .checked_add(length)
770            .ok_or(CanonicalCodecError::UnexpectedEnd)?;
771        let value = self
772            .input
773            .get(self.position..end)
774            .ok_or(CanonicalCodecError::UnexpectedEnd)?;
775        self.position = end;
776        Ok(value)
777    }
778}
779
780#[derive(Clone, Copy, Debug, Eq, PartialEq)]
781enum BoundedKind {
782    String,
783    Bytes,
784}
785
786/// A collection kind used in bounded decoder errors.
787#[derive(Clone, Copy, Debug, Eq, PartialEq)]
788pub enum CollectionKind {
789    /// Canonical list.
790    List,
791    /// Canonical record.
792    Record,
793}
794
795/// A safe canonical encoding or decoding failure.
796#[derive(Clone, Debug, Eq, PartialEq)]
797pub enum CanonicalCodecError {
798    /// The document exceeds the hard encoded-size limit.
799    DocumentTooLarge {
800        /// Observed or attempted encoded length.
801        actual: usize,
802        /// Maximum encoded length.
803        maximum: usize,
804    },
805    /// A string exceeds its hard byte-length limit.
806    StringTooLarge {
807        /// Declared or observed UTF-8 byte length.
808        actual: usize,
809        /// Maximum UTF-8 byte length.
810        maximum: usize,
811    },
812    /// A byte value exceeds its hard length limit.
813    BytesTooLarge {
814        /// Declared or observed byte length.
815        actual: usize,
816        /// Maximum byte length.
817        maximum: usize,
818    },
819    /// A list or record exceeds its hard entry limit.
820    TooManyEntries {
821        /// Collection kind.
822        kind: CollectionKind,
823        /// Declared or observed entry count.
824        actual: usize,
825        /// Maximum entry count.
826        maximum: usize,
827    },
828    /// The value graph exceeds the hard nesting-depth limit.
829    NestingTooDeep {
830        /// Observed depth below the root value.
831        depth: usize,
832        /// Maximum depth below the root value.
833        maximum: usize,
834    },
835    /// The first byte is not a supported encoding version.
836    UnsupportedVersion {
837        /// Unsupported format byte.
838        version: u8,
839    },
840    /// The type tag is not registered in encoding v1.
841    UnknownTag {
842        /// Unknown tag byte.
843        tag: u8,
844    },
845    /// A vector payload declares a dimension outside `1..=MAX_VECTOR_DIMENSION`.
846    VectorDimensionOutOfRange {
847        /// Declared dimension.
848        actual: u32,
849        /// Maximum supported dimension.
850        maximum: u32,
851    },
852    /// A vector payload carries a non-canonical component (NaN, infinity, or
853    /// the negative-zero bit pattern).
854    NonCanonicalVectorComponent {
855        /// Zero-based index of the offending component.
856        index: usize,
857    },
858    /// A Boolean payload is neither zero nor one.
859    InvalidBoolean {
860        /// Invalid payload byte.
861        value: u8,
862    },
863    /// A decimal specification or coefficient is invalid.
864    InvalidDecimal,
865    /// A money currency is not three uppercase ASCII bytes.
866    InvalidCurrency,
867    /// Timestamp nanoseconds are outside the accepted range.
868    InvalidTimestamp,
869    /// A string payload is not valid UTF-8.
870    InvalidUtf8,
871    /// Record fields are duplicated or not in strictly increasing ID order.
872    NonCanonicalRecordOrder,
873    /// An enum payload contains the reserved zero enum-type ID.
874    ZeroEnumTypeId,
875    /// An enum payload contains the reserved zero variant ID.
876    ZeroEnumVariantId,
877    /// A record payload contains the reserved zero field ID.
878    ZeroFieldId,
879    /// The document ended before the declared value was complete.
880    UnexpectedEnd,
881    /// Bytes remain after the first complete value.
882    TrailingBytes {
883        /// Number of unused bytes.
884        count: usize,
885    },
886}
887
888impl fmt::Display for CanonicalCodecError {
889    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
890        match self {
891            Self::DocumentTooLarge { actual, maximum } => write!(
892                formatter,
893                "canonical document has {actual} bytes; maximum is {maximum}"
894            ),
895            Self::StringTooLarge { actual, maximum } => {
896                write!(formatter, "string has {actual} bytes; maximum is {maximum}")
897            }
898            Self::BytesTooLarge { actual, maximum } => {
899                write!(
900                    formatter,
901                    "byte value has {actual} bytes; maximum is {maximum}"
902                )
903            }
904            Self::TooManyEntries {
905                kind,
906                actual,
907                maximum,
908            } => write!(
909                formatter,
910                "{kind:?} has {actual} entries; maximum is {maximum}"
911            ),
912            Self::NestingTooDeep { depth, maximum } => {
913                write!(formatter, "nesting depth is {depth}; maximum is {maximum}")
914            }
915            Self::UnsupportedVersion { version } => {
916                write!(formatter, "unsupported canonical value version {version}")
917            }
918            Self::UnknownTag { tag } => write!(formatter, "unknown canonical value tag {tag}"),
919            Self::VectorDimensionOutOfRange { actual, maximum } => write!(
920                formatter,
921                "vector dimension {actual} is outside 1..={maximum}"
922            ),
923            Self::NonCanonicalVectorComponent { index } => write!(
924                formatter,
925                "vector component {index} is not in canonical form (NaN, infinity, or -0.0)"
926            ),
927            Self::InvalidBoolean { value } => {
928                write!(formatter, "invalid Boolean payload {value}")
929            }
930            Self::InvalidDecimal => formatter.write_str("invalid canonical decimal"),
931            Self::InvalidCurrency => formatter.write_str("invalid canonical currency"),
932            Self::InvalidTimestamp => formatter.write_str("invalid canonical timestamp"),
933            Self::InvalidUtf8 => formatter.write_str("invalid canonical UTF-8"),
934            Self::NonCanonicalRecordOrder => {
935                formatter.write_str("record fields are not in canonical order")
936            }
937            Self::ZeroEnumTypeId => formatter.write_str("enum type ID must be nonzero"),
938            Self::ZeroEnumVariantId => formatter.write_str("enum variant ID must be nonzero"),
939            Self::ZeroFieldId => formatter.write_str("record field ID must be nonzero"),
940            Self::UnexpectedEnd => formatter.write_str("canonical document ended unexpectedly"),
941            Self::TrailingBytes { count } => {
942                write!(formatter, "canonical document has {count} trailing bytes")
943            }
944        }
945    }
946}
947
948impl std::error::Error for CanonicalCodecError {}
949
950#[cfg(test)]
951mod tests {
952    use super::*;
953
954    fn one_bytes_field(length: usize) -> CanonicalRecord {
955        CanonicalRecord::new(vec![(
956            FieldId::new(1).expect("nonzero field ID"),
957            CanonicalValue::bytes(vec![0xa5; length]).expect("bounded byte value"),
958        )])
959        .expect("bounded canonical record")
960    }
961
962    #[test]
963    fn borrowed_record_encoder_preserves_complete_value_record_bytes() {
964        let record = CanonicalRecord::new(vec![
965            (
966                FieldId::new(2).expect("field ID"),
967                CanonicalValue::Bool(true),
968            ),
969            (FieldId::new(1).expect("field ID"), CanonicalValue::U64(42)),
970        ])
971        .expect("canonical record");
972
973        assert_eq!(
974            encode_canonical_record(&record),
975            encode_canonical_value(&CanonicalValue::Record(record.clone()))
976        );
977        assert_eq!(
978            encode_canonical_record(&record).expect("borrowed encoding")[..2],
979            [CANONICAL_VALUE_VERSION, TAG_RECORD]
980        );
981    }
982
983    #[test]
984    fn atomic_command_input_envelope_is_additive_and_preserves_v1_bytes() {
985        let legacy = one_bytes_field(128);
986        assert_eq!(
987            encode_canonical_command_input(&legacy).expect("command input"),
988            encode_canonical_record(&legacy).expect("legacy record")
989        );
990
991        let large = CanonicalRecord::new(vec![
992            (
993                FieldId::new(1).expect("field ID"),
994                CanonicalValue::bytes(vec![0xa5; 900_000]).expect("first value"),
995            ),
996            (
997                FieldId::new(2).expect("field ID"),
998                CanonicalValue::bytes(vec![0x5a; 900_000]).expect("second value"),
999            ),
1000        ])
1001        .expect("large command input");
1002        assert!(matches!(
1003            encode_canonical_record(&large),
1004            Err(CanonicalCodecError::DocumentTooLarge {
1005                maximum: MAX_CANONICAL_DOCUMENT_BYTES,
1006                ..
1007            })
1008        ));
1009        let encoded = encode_canonical_command_input(&large).expect("large command input encode");
1010        assert_eq!(
1011            canonical_command_input_encoded_len(&large).expect("large command input length"),
1012            encoded.len()
1013        );
1014        assert!(encoded.len() > MAX_CANONICAL_DOCUMENT_BYTES);
1015        assert!(encoded.len() <= MAX_ATOMIC_COMMAND_REQUEST_BYTES_V2);
1016    }
1017
1018    #[test]
1019    fn borrowed_record_encoder_accepts_exact_document_maximum_and_rejects_one_over() {
1020        // Root version/tag/count (6), field ID (4), and nested bytes
1021        // version/tag/length (6) leave exactly this much byte payload.
1022        const RECORD_OVERHEAD: usize = 16;
1023        let exact = one_bytes_field(MAX_CANONICAL_DOCUMENT_BYTES - RECORD_OVERHEAD);
1024        assert_eq!(
1025            encode_canonical_record(&exact)
1026                .expect("exact maximum canonical record")
1027                .len(),
1028            MAX_CANONICAL_DOCUMENT_BYTES
1029        );
1030
1031        let over = one_bytes_field(MAX_CANONICAL_DOCUMENT_BYTES - RECORD_OVERHEAD + 1);
1032        assert_eq!(
1033            encode_canonical_record(&over),
1034            Err(CanonicalCodecError::DocumentTooLarge {
1035                actual: MAX_CANONICAL_DOCUMENT_BYTES + 1,
1036                maximum: MAX_CANONICAL_DOCUMENT_BYTES,
1037            })
1038        );
1039    }
1040
1041    #[test]
1042    fn vector_decode_rejects_non_canonical_bytes() {
1043        // dimension 0
1044        let zero_dimension = [1u8, TAG_VECTOR, 0, 0, 0, 0];
1045        assert_eq!(
1046            decode_canonical_value(&zero_dimension),
1047            Err(CanonicalCodecError::VectorDimensionOutOfRange {
1048                actual: 0,
1049                maximum: crate::MAX_VECTOR_DIMENSION,
1050            })
1051        );
1052        // dimension above the maximum, declared before any component bytes
1053        let mut oversized = vec![1u8, TAG_VECTOR];
1054        oversized.extend((crate::MAX_VECTOR_DIMENSION + 1).to_be_bytes());
1055        assert_eq!(
1056            decode_canonical_value(&oversized),
1057            Err(CanonicalCodecError::VectorDimensionOutOfRange {
1058                actual: crate::MAX_VECTOR_DIMENSION + 1,
1059                maximum: crate::MAX_VECTOR_DIMENSION,
1060            })
1061        );
1062        // NaN, infinities, and -0.0 bit patterns are not canonical bytes.
1063        for (bits, index) in [
1064            (f32::NAN.to_bits(), 1u32),
1065            (f32::INFINITY.to_bits(), 1),
1066            (f32::NEG_INFINITY.to_bits(), 1),
1067            ((-0.0_f32).to_bits(), 1),
1068        ] {
1069            let mut document = vec![1u8, TAG_VECTOR, 0, 0, 0, 2];
1070            document.extend(1.0_f32.to_be_bytes());
1071            document.extend(bits.to_be_bytes());
1072            assert_eq!(
1073                decode_canonical_value(&document),
1074                Err(CanonicalCodecError::NonCanonicalVectorComponent {
1075                    index: index as usize
1076                }),
1077                "bit pattern {bits:#010x} must be rejected"
1078            );
1079        }
1080        // Distinct NaN payloads must both be rejected, so no NaN bit pattern
1081        // ever round-trips into durable bytes.
1082        for nan_bits in [0x7fc0_0000u32, 0x7fc0_0001] {
1083            let mut document = vec![1u8, TAG_VECTOR, 0, 0, 0, 1];
1084            document.extend(nan_bits.to_be_bytes());
1085            assert!(decode_canonical_value(&document).is_err());
1086        }
1087    }
1088
1089    #[test]
1090    fn rejects_malformed_documents_without_panicking() {
1091        let cases: &[&[u8]] = &[
1092            &[],
1093            &[2, TAG_NULL],
1094            &[1, 0xff],
1095            &[1, TAG_BOOL, 2],
1096            &[1, TAG_U64],
1097            &[1, TAG_STRING, 0, 0, 0, 1, 0xff],
1098            &[1, TAG_NULL, 0],
1099        ];
1100        for case in cases {
1101            assert!(decode_canonical_value(case).is_err(), "accepted {case:?}");
1102        }
1103    }
1104
1105    #[test]
1106    fn encoded_len_matches_encode_across_variants_and_nesting() {
1107        use crate::{
1108            CurrencyCode, Date, Decimal, DecimalSpec, EnumTypeId, EnumVariantId, Money, Timestamp,
1109        };
1110
1111        let decimal = Decimal::new(DecimalSpec::new(10, 2).expect("spec"), 1_234).expect("decimal");
1112        let money = Money::new(CurrencyCode::new("USD").expect("currency"), decimal);
1113        let nested_record = CanonicalRecord::new(vec![
1114            (FieldId::new(1).expect("id"), CanonicalValue::U64(7)),
1115            (
1116                FieldId::new(3).expect("id"),
1117                CanonicalValue::String(CanonicalString::new("nested").expect("string")),
1118            ),
1119        ])
1120        .expect("record");
1121        let nested_list = CanonicalList::new(vec![
1122            CanonicalValue::Bool(true),
1123            CanonicalValue::I64(-9),
1124            CanonicalValue::Record(nested_record.clone()),
1125        ])
1126        .expect("list");
1127        let samples = [
1128            CanonicalValue::Null,
1129            CanonicalValue::Bool(false),
1130            CanonicalValue::Bool(true),
1131            CanonicalValue::I64(i64::MIN),
1132            CanonicalValue::I64(0),
1133            CanonicalValue::I64(i64::MAX),
1134            CanonicalValue::U64(0),
1135            CanonicalValue::U64(u64::MAX),
1136            CanonicalValue::Decimal(decimal),
1137            CanonicalValue::Money(money),
1138            CanonicalValue::String(CanonicalString::new("").expect("empty")),
1139            CanonicalValue::String(CanonicalString::new("hello ✓").expect("utf8")),
1140            CanonicalValue::Bytes(CanonicalBytes::new(vec![]).expect("empty bytes")),
1141            CanonicalValue::Bytes(CanonicalBytes::new(vec![0, 1, 2, 255]).expect("bytes")),
1142            CanonicalValue::Timestamp(Timestamp::new(1_700_000_000, 123).expect("ts")),
1143            CanonicalValue::Date(Date::from_days_since_unix_epoch(20_000)),
1144            CanonicalValue::Uuid([0x11; 16]),
1145            CanonicalValue::Enum {
1146                type_id: EnumTypeId::new(4).expect("type"),
1147                variant_id: EnumVariantId::new(2).expect("variant"),
1148            },
1149            CanonicalValue::List(CanonicalList::new(vec![]).expect("empty list")),
1150            CanonicalValue::List(nested_list),
1151            CanonicalValue::Record(CanonicalRecord::new(vec![]).expect("empty record")),
1152            CanonicalValue::Record(nested_record),
1153            CanonicalValue::Vector(CanonicalVector::new(vec![1.0]).expect("finite")),
1154            CanonicalValue::Vector(
1155                CanonicalVector::new(vec![0.0, -2.5, 3.25, f32::MIN, f32::MAX]).expect("finite"),
1156            ),
1157        ];
1158        for value in samples {
1159            let encoded = encode_canonical_value(&value).expect("encode");
1160            let len = canonical_value_encoded_len(&value).expect("length");
1161            assert_eq!(
1162                len,
1163                encoded.len(),
1164                "encoded_len diverged for {value:?}: len={len} encode={}",
1165                encoded.len()
1166            );
1167            // Byte-identity: into form matches the allocating wrapper.
1168            let mut into = Vec::new();
1169            encode_canonical_value_into(&mut into, &value).expect("encode_into");
1170            assert_eq!(
1171                into, encoded,
1172                "encode_into diverged from encode_canonical_value for {value:?}"
1173            );
1174            // Append onto a non-empty buffer remains document-byte-identical.
1175            let mut packed = vec![0xAA, 0xBB];
1176            encode_canonical_value_into(&mut packed, &value).expect("append encode_into");
1177            assert_eq!(&packed[2..], encoded.as_slice());
1178            // Pre-reservation is exact: the wrapper never grows its buffer.
1179            assert_eq!(
1180                encoded.capacity(),
1181                encoded.len(),
1182                "encode_canonical_value over-or-under reserved for {value:?}"
1183            );
1184        }
1185    }
1186
1187    /// The record-shaped length function is exact, not a lower bound, so
1188    /// [`encode_canonical_record`] reserves once and never grows.
1189    #[test]
1190    fn record_encoded_len_is_exact_and_reserves_once() {
1191        use crate::{
1192            CurrencyCode, Date, Decimal, DecimalSpec, EnumTypeId, EnumVariantId, Money, Timestamp,
1193        };
1194
1195        let decimal = Decimal::new(DecimalSpec::new(10, 2).expect("spec"), 1_234).expect("decimal");
1196        let inner = CanonicalRecord::new(vec![
1197            (FieldId::new(2).expect("id"), CanonicalValue::I64(-1)),
1198            (
1199                FieldId::new(5).expect("id"),
1200                CanonicalValue::Vector(CanonicalVector::new(vec![1.0, -2.0, 3.5]).expect("finite")),
1201            ),
1202        ])
1203        .expect("inner record");
1204        let records = [
1205            CanonicalRecord::new(vec![]).expect("empty record"),
1206            CanonicalRecord::new(vec![(FieldId::new(1).expect("id"), CanonicalValue::Null)])
1207                .expect("single null"),
1208            CanonicalRecord::new(vec![
1209                (FieldId::new(1).expect("id"), CanonicalValue::Uuid([7; 16])),
1210                (
1211                    FieldId::new(2).expect("id"),
1212                    CanonicalValue::String(
1213                        CanonicalString::new("wide title ✓".repeat(24)).expect("string"),
1214                    ),
1215                ),
1216                (
1217                    FieldId::new(3).expect("id"),
1218                    CanonicalValue::Timestamp(Timestamp::new(1_700_000_000, 7).expect("ts")),
1219                ),
1220                (
1221                    FieldId::new(4).expect("id"),
1222                    CanonicalValue::Decimal(decimal),
1223                ),
1224                (
1225                    FieldId::new(5).expect("id"),
1226                    CanonicalValue::Money(Money::new(
1227                        CurrencyCode::new("EUR").expect("currency"),
1228                        decimal,
1229                    )),
1230                ),
1231                (
1232                    FieldId::new(6).expect("id"),
1233                    CanonicalValue::Date(Date::from_days_since_unix_epoch(19_000)),
1234                ),
1235                (
1236                    FieldId::new(7).expect("id"),
1237                    CanonicalValue::Enum {
1238                        type_id: EnumTypeId::new(3).expect("type"),
1239                        variant_id: EnumVariantId::new(1).expect("variant"),
1240                    },
1241                ),
1242                (
1243                    FieldId::new(8).expect("id"),
1244                    CanonicalValue::Bytes(CanonicalBytes::new(vec![9; 300]).expect("bytes")),
1245                ),
1246                (
1247                    FieldId::new(9).expect("id"),
1248                    CanonicalValue::List(
1249                        CanonicalList::new(vec![
1250                            CanonicalValue::Record(inner.clone()),
1251                            CanonicalValue::Bool(true),
1252                        ])
1253                        .expect("list"),
1254                    ),
1255                ),
1256            ])
1257            .expect("wide record"),
1258        ];
1259        for record in records {
1260            let encoded = encode_canonical_record(&record).expect("encode");
1261            let len = canonical_record_encoded_len(&record).expect("length");
1262            assert_eq!(len, encoded.len(), "record length diverged from encode");
1263            assert_eq!(
1264                encoded.capacity(),
1265                encoded.len(),
1266                "encode_canonical_record grew or over-reserved its buffer"
1267            );
1268            // The record form and the value form agree byte for byte.
1269            let as_value = encode_canonical_value(&CanonicalValue::Record(record.clone()))
1270                .expect("value encode");
1271            assert_eq!(as_value, encoded, "record and value encodings diverged");
1272            assert_eq!(
1273                canonical_value_encoded_len(&CanonicalValue::Record(record)).expect("value length"),
1274                len,
1275                "record and value lengths diverged"
1276            );
1277        }
1278    }
1279
1280    /// Falsifiability (b): if encode_into diverged (e.g. appended garbage), the
1281    /// identity check against the wrapper must fail.
1282    #[test]
1283    fn encode_into_byte_identity_detects_divergence() {
1284        let value = CanonicalValue::I64(42);
1285        let expected = encode_canonical_value(&value).expect("encode");
1286        let mut into = Vec::new();
1287        encode_canonical_value_into(&mut into, &value).expect("encode_into");
1288        assert_eq!(into, expected);
1289        // Simulated divergence probe: a corrupt encoder would fail this.
1290        let mut corrupt = into.clone();
1291        corrupt.push(0xFF);
1292        assert_ne!(
1293            corrupt, expected,
1294            "appended garbage must break byte-identity"
1295        );
1296    }
1297
1298    /// A rejected cell must not leave partial-document bytes in a packer's
1299    /// buffer: a caller that continues packing would otherwise publish garbage
1300    /// under offsets that look valid.
1301    #[test]
1302    fn failed_encode_into_leaves_the_buffer_untouched() {
1303        const RECORD_OVERHEAD: usize = 16;
1304        let over = CanonicalValue::Record(one_bytes_field(
1305            MAX_CANONICAL_DOCUMENT_BYTES - RECORD_OVERHEAD + 1,
1306        ));
1307        let prefix = vec![0xAA, 0xBB, 0xCC];
1308        let mut buffer = prefix.clone();
1309        let error = encode_canonical_value_into(&mut buffer, &over)
1310            .expect_err("oversized document must be rejected");
1311        assert_eq!(
1312            error,
1313            CanonicalCodecError::DocumentTooLarge {
1314                actual: MAX_CANONICAL_DOCUMENT_BYTES + 1,
1315                maximum: MAX_CANONICAL_DOCUMENT_BYTES,
1316            }
1317        );
1318        assert_eq!(
1319            buffer, prefix,
1320            "failed encode must restore the caller's buffer exactly"
1321        );
1322
1323        // A following successful cell then starts at the untouched prefix.
1324        encode_canonical_value_into(&mut buffer, &CanonicalValue::I64(7))
1325            .expect("encode after err");
1326        assert_eq!(
1327            &buffer[..3],
1328            prefix.as_slice(),
1329            "prefix must survive the recovery append"
1330        );
1331        assert_eq!(
1332            &buffer[3..],
1333            encode_canonical_value(&CanonicalValue::I64(7))
1334                .expect("encode")
1335                .as_slice()
1336        );
1337    }
1338}