1use crate::wire::{BoundedReader, ProtocolLimits, TtcReader, TtcWriter};
30use crate::{ProtocolError, Result};
31
32pub const TNS_VECTOR_MAGIC_BYTE: u8 = 0xDB;
34
35pub const TNS_VECTOR_VERSION_BASE: u8 = 0;
37pub const TNS_VECTOR_VERSION_WITH_BINARY: u8 = 1;
38pub const TNS_VECTOR_VERSION_WITH_SPARSE: u8 = 2;
39
40pub const TNS_VECTOR_FLAG_NORM: u16 = 0x0002;
42pub const TNS_VECTOR_FLAG_NORM_RESERVED: u16 = 0x0010;
43pub const TNS_VECTOR_FLAG_SPARSE: u16 = 0x0020;
44
45pub const VECTOR_FORMAT_FLOAT32: u8 = 2;
47pub const VECTOR_FORMAT_FLOAT64: u8 = 3;
48pub const VECTOR_FORMAT_INT8: u8 = 4;
49pub const VECTOR_FORMAT_BINARY: u8 = 5;
50
51#[derive(Clone, Debug, PartialEq)]
56pub enum VectorValues {
57 Float32(Vec<f32>),
59 Float64(Vec<f64>),
61 Int8(Vec<i8>),
63 Binary(Vec<u8>),
65}
66
67impl VectorValues {
68 pub fn format(&self) -> u8 {
70 match self {
71 VectorValues::Float32(_) => VECTOR_FORMAT_FLOAT32,
72 VectorValues::Float64(_) => VECTOR_FORMAT_FLOAT64,
73 VectorValues::Int8(_) => VECTOR_FORMAT_INT8,
74 VectorValues::Binary(_) => VECTOR_FORMAT_BINARY,
75 }
76 }
77
78 pub fn len(&self) -> usize {
81 match self {
82 VectorValues::Float32(v) => v.len(),
83 VectorValues::Float64(v) => v.len(),
84 VectorValues::Int8(v) => v.len(),
85 VectorValues::Binary(v) => v.len(),
86 }
87 }
88
89 pub fn is_empty(&self) -> bool {
90 self.len() == 0
91 }
92}
93
94#[derive(Clone, Debug, PartialEq)]
97pub enum Vector {
98 Dense(VectorValues),
99 Sparse {
100 num_dimensions: u32,
101 indices: Vec<u32>,
102 values: VectorValues,
103 },
104}
105
106pub fn decode_vector(data: &[u8]) -> Result<Vector> {
108 decode_vector_with_limits(data, ProtocolLimits::DEFAULT)
109}
110
111pub fn decode_vector_with_limits(data: &[u8], limits: ProtocolLimits) -> Result<Vector> {
113 limits.check_response_bytes(data.len())?;
114 let mut reader = TtcReader::with_limits(data, limits)?;
115
116 let magic = reader.read_u8()?;
117 if magic != TNS_VECTOR_MAGIC_BYTE {
118 return Err(ProtocolError::TtcDecode("vector: bad magic byte"));
119 }
120 let version = reader.read_u8()?;
121 if version > TNS_VECTOR_VERSION_WITH_SPARSE {
122 return Err(ProtocolError::TtcDecode("vector: unsupported version"));
123 }
124 let flags = read_u16be(&mut reader)?;
125 let format = reader.read_u8()?;
126 let mut num_elements = read_u32be(&mut reader)?;
127 reader
128 .limits()
129 .check_vector_dimensions(num_elements as usize)?;
130 if flags & TNS_VECTOR_FLAG_NORM_RESERVED != 0 || flags & TNS_VECTOR_FLAG_NORM != 0 {
131 reader.skip(8)?;
132 }
133
134 if flags & TNS_VECTOR_FLAG_SPARSE != 0 {
135 let num_dimensions = num_elements;
136 let num_sparse = read_u16be(&mut reader)?;
137 reader
138 .limits()
139 .check_vector_dimensions(usize::from(num_sparse))?;
140 let mut indices: Vec<u32> = reader.with_capacity_limited(
144 usize::from(num_sparse),
145 4,
146 ProtocolLimits::check_vector_dimensions,
147 )?;
148 for _ in 0..num_sparse {
149 indices.push(read_u32be(&mut reader)?);
150 }
151 let values = decode_values(&mut reader, u32::from(num_sparse), format)?;
152 ensure_vector_image_fully_consumed(&reader)?;
153 return Ok(Vector::Sparse {
154 num_dimensions,
155 indices,
156 values,
157 });
158 }
159
160 if format == VECTOR_FORMAT_BINARY {
162 if num_elements % 8 != 0 {
163 return Err(ProtocolError::TtcDecode(
164 "vector: binary dimension count is not byte-aligned",
165 ));
166 }
167 num_elements /= 8;
168 }
169 let values = decode_values(&mut reader, num_elements, format)?;
170 ensure_vector_image_fully_consumed(&reader)?;
171 Ok(Vector::Dense(values))
172}
173
174fn ensure_vector_image_fully_consumed(reader: &TtcReader<'_>) -> Result<()> {
175 if reader.remaining() == 0 {
176 return Ok(());
177 }
178 Err(ProtocolError::TtcDecode(
179 "vector: trailing bytes after image payload",
180 ))
181}
182
183fn decode_values(reader: &mut TtcReader<'_>, count: u32, format: u8) -> Result<VectorValues> {
184 let count = count as usize;
185 reader.limits().check_vector_dimensions(count)?;
186 match format {
195 VECTOR_FORMAT_FLOAT32 => {
196 let mut out: Vec<f32> =
197 reader.with_capacity_limited(count, 4, ProtocolLimits::check_vector_dimensions)?;
198 for _ in 0..count {
199 let raw = reader.read_raw(4)?;
200 out.push(decode_binary_float([raw[0], raw[1], raw[2], raw[3]]));
201 }
202 Ok(VectorValues::Float32(out))
203 }
204 VECTOR_FORMAT_FLOAT64 => {
205 let mut out: Vec<f64> =
206 reader.with_capacity_limited(count, 8, ProtocolLimits::check_vector_dimensions)?;
207 for _ in 0..count {
208 let raw = reader.read_raw(8)?;
209 out.push(decode_binary_double([
210 raw[0], raw[1], raw[2], raw[3], raw[4], raw[5], raw[6], raw[7],
211 ]));
212 }
213 Ok(VectorValues::Float64(out))
214 }
215 VECTOR_FORMAT_INT8 => {
216 let mut out: Vec<i8> =
217 reader.with_capacity_limited(count, 1, ProtocolLimits::check_vector_dimensions)?;
218 for _ in 0..count {
219 out.push(reader.read_u8()? as i8);
220 }
221 Ok(VectorValues::Int8(out))
222 }
223 VECTOR_FORMAT_BINARY => Ok(VectorValues::Binary(reader.read_raw(count)?.to_vec())),
224 _ => Err(ProtocolError::TtcDecode(
225 "vector: unsupported element format",
226 )),
227 }
228}
229
230pub fn encode_vector(vector: &Vector) -> Vec<u8> {
241 match encode_vector_checked(vector) {
242 Ok(image) => image,
243 Err(err) => panic!("invalid VECTOR value for encoding: {err}"),
244 }
245}
246
247pub(crate) fn encode_vector_checked(vector: &Vector) -> Result<Vec<u8>> {
248 let mut buf = Vec::new();
249
250 let mut flags = TNS_VECTOR_FLAG_NORM_RESERVED;
251 let (format, version, num_elements) = match vector {
252 Vector::Sparse {
253 num_dimensions,
254 values,
255 ..
256 } => {
257 flags |= TNS_VECTOR_FLAG_SPARSE | TNS_VECTOR_FLAG_NORM;
258 (
259 values.format(),
260 TNS_VECTOR_VERSION_WITH_SPARSE,
261 *num_dimensions,
262 )
263 }
264 Vector::Dense(values) => {
265 let format = values.format();
266 if format == VECTOR_FORMAT_BINARY {
267 (
268 format,
269 TNS_VECTOR_VERSION_WITH_BINARY,
270 (values.len() as u32) * 8,
271 )
272 } else {
273 flags |= TNS_VECTOR_FLAG_NORM;
274 (format, TNS_VECTOR_VERSION_BASE, values.len() as u32)
275 }
276 }
277 };
278
279 buf.push(TNS_VECTOR_MAGIC_BYTE);
280 buf.push(version);
281 buf.extend_from_slice(&flags.to_be_bytes());
282 buf.push(format);
283 buf.extend_from_slice(&num_elements.to_be_bytes());
284 buf.extend_from_slice(&[0u8; 8]); match vector {
287 Vector::Dense(values) => encode_values(&mut buf, values),
288 Vector::Sparse {
289 indices, values, ..
290 } => {
291 if indices.len() != values.len() {
292 return Err(ProtocolError::TtcDecode(
293 "vector: sparse index/value count mismatch",
294 ));
295 }
296 let num_sparse =
297 u16::try_from(indices.len()).map_err(|_| ProtocolError::InvalidPacketLength {
298 length: indices.len(),
299 minimum: 0,
300 })?;
301 buf.extend_from_slice(&num_sparse.to_be_bytes());
302 for index in indices {
303 buf.extend_from_slice(&index.to_be_bytes());
304 }
305 encode_values(&mut buf, values);
306 }
307 }
308
309 Ok(buf)
310}
311
312fn encode_values(buf: &mut Vec<u8>, values: &VectorValues) {
313 match values {
314 VectorValues::Float32(v) => {
315 for value in v {
316 buf.extend_from_slice(&encode_binary_float(*value));
317 }
318 }
319 VectorValues::Float64(v) => {
320 for value in v {
321 buf.extend_from_slice(&encode_binary_double(*value));
322 }
323 }
324 VectorValues::Int8(v) => {
325 for value in v {
326 buf.push(*value as u8);
327 }
328 }
329 VectorValues::Binary(v) => buf.extend_from_slice(v),
330 }
331}
332
333fn decode_binary_double(bytes: [u8; 8]) -> f64 {
342 let mut decoded = bytes;
343 if decoded[0] & 0x80 != 0 {
344 decoded[0] &= 0x7f;
345 } else {
346 for byte in &mut decoded {
347 *byte = !*byte;
348 }
349 }
350 f64::from_bits(u64::from_be_bytes(decoded))
351}
352
353fn decode_binary_float(bytes: [u8; 4]) -> f32 {
355 let mut decoded = bytes;
356 if decoded[0] & 0x80 != 0 {
357 decoded[0] &= 0x7f;
358 } else {
359 for byte in &mut decoded {
360 *byte = !*byte;
361 }
362 }
363 f32::from_bits(u32::from_be_bytes(decoded))
364}
365
366fn encode_binary_double(value: f64) -> [u8; 8] {
368 let mut bytes = value.to_bits().to_be_bytes();
369 if bytes[0] & 0x80 == 0 {
370 bytes[0] |= 0x80;
371 } else {
372 for byte in &mut bytes {
373 *byte = !*byte;
374 }
375 }
376 bytes
377}
378
379fn encode_binary_float(value: f32) -> [u8; 4] {
381 let mut bytes = value.to_bits().to_be_bytes();
382 if bytes[0] & 0x80 == 0 {
383 bytes[0] |= 0x80;
384 } else {
385 for byte in &mut bytes {
386 *byte = !*byte;
387 }
388 }
389 bytes
390}
391
392fn read_u16be(reader: &mut TtcReader<'_>) -> Result<u16> {
395 let raw = reader.read_raw(2)?;
396 Ok(u16::from_be_bytes([raw[0], raw[1]]))
397}
398
399fn read_u32be(reader: &mut TtcReader<'_>) -> Result<u32> {
400 let raw = reader.read_raw(4)?;
401 Ok(u32::from_be_bytes([raw[0], raw[1], raw[2], raw[3]]))
402}
403
404pub fn write_vector_image(writer: &mut TtcWriter, image: &[u8]) -> Result<()> {
410 write_qlocator(writer, image.len() as u64, true);
411 writer.write_bytes_with_length(image)?;
412 Ok(())
413}
414
415pub fn write_oson_aq_payload(writer: &mut TtcWriter, image: &[u8]) -> Result<()> {
419 write_qlocator(writer, image.len() as u64, false);
420 writer.write_bytes_with_length(image)?;
421 Ok(())
422}
423
424fn write_qlocator(writer: &mut TtcWriter, data_length: u64, write_length: bool) {
429 const TNS_LOB_QLOCATOR_VERSION: u16 = 4;
430 const TNS_LOB_LOC_FLAGS_VALUE_BASED: u8 = 0x20;
431 const TNS_LOB_LOC_FLAGS_BLOB: u8 = 0x01;
432 const TNS_LOB_LOC_FLAGS_ABSTRACT: u8 = 0x40;
433 const TNS_LOB_LOC_FLAGS_INIT: u8 = 0x08;
434
435 writer.write_ub4(40); if write_length {
437 writer.write_u8(40); }
439 writer.write_u16be(38); writer.write_u16be(TNS_LOB_QLOCATOR_VERSION);
441 writer.write_u8(
442 TNS_LOB_LOC_FLAGS_VALUE_BASED | TNS_LOB_LOC_FLAGS_BLOB | TNS_LOB_LOC_FLAGS_ABSTRACT,
443 );
444 writer.write_u8(TNS_LOB_LOC_FLAGS_INIT);
445 writer.write_u16be(0); writer.write_u16be(1); writer.write_u64be(data_length);
448 writer.write_u16be(0); writer.write_u16be(0); writer.write_u16be(0); writer.write_u64be(0); writer.write_u64be(0); }
454
455#[cfg(test)]
456mod tests {
457 use super::*;
458 use serde_json::Value;
459
460 fn roundtrip(vector: Vector) {
461 let image = encode_vector(&vector);
462 let decoded = decode_vector(&image).expect("decode");
463 assert_eq!(decoded, vector);
464 }
465
466 #[test]
471 fn legitimate_large_vector_still_decodes_fully() {
472 let big_f32: Vec<f32> = (0..4096).map(|i| i as f32 * 0.5 - 1024.0).collect();
473 roundtrip(Vector::Dense(VectorValues::Float32(big_f32)));
474 let big_f64: Vec<f64> = (0..2048).map(|i| i as f64 * 0.25).collect();
475 roundtrip(Vector::Dense(VectorValues::Float64(big_f64)));
476 roundtrip(Vector::Sparse {
478 num_dimensions: 100_000,
479 indices: (0..1000).map(|i| i * 7).collect(),
480 values: VectorValues::Float32((0..1000).map(|i| i as f32).collect()),
481 });
482 }
483
484 #[test]
485 fn roundtrips_every_dense_format() {
486 roundtrip(Vector::Dense(VectorValues::Float32(vec![
487 1.5, -2.25, 3.0, 0.0,
488 ])));
489 roundtrip(Vector::Dense(VectorValues::Float64(vec![
490 6501.0, 25.25, 18.125, -3.5,
491 ])));
492 roundtrip(Vector::Dense(VectorValues::Int8(vec![
493 -5, 1, -2, 127, -128,
494 ])));
495 roundtrip(Vector::Dense(VectorValues::Binary(vec![0xA5, 0x3C])));
496 }
497
498 #[test]
499 fn roundtrips_every_sparse_format() {
500 roundtrip(Vector::Sparse {
501 num_dimensions: 8,
502 indices: vec![1, 4, 6],
503 values: VectorValues::Float64(vec![1.5, -2.0, 9.25]),
504 });
505 roundtrip(Vector::Sparse {
506 num_dimensions: 6,
507 indices: vec![0, 3],
508 values: VectorValues::Float32(vec![2.5, -7.0]),
509 });
510 roundtrip(Vector::Sparse {
511 num_dimensions: 5,
512 indices: vec![2],
513 values: VectorValues::Int8(vec![42]),
514 });
515 }
516
517 #[test]
518 fn sparse_int8_roundtrips_max_u16_count() {
519 let indices = (0..u16::MAX).map(u32::from).collect::<Vec<_>>();
520 let values = VectorValues::Int8((0..u16::MAX).map(|i| (i % 127) as i8).collect::<Vec<_>>());
521 let vector = Vector::Sparse {
522 num_dimensions: u32::from(u16::MAX),
523 indices,
524 values,
525 };
526
527 let image = encode_vector_checked(&vector).expect("encode max u16 sparse vector");
528 let decoded = decode_vector(&image).expect("decode max u16 sparse vector");
529 assert_eq!(decoded, vector);
530 }
531
532 #[test]
533 fn sparse_int8_rejects_count_that_exceeds_wire_field() {
534 let count = usize::from(u16::MAX) + 1;
535 let vector = Vector::Sparse {
536 num_dimensions: count as u32,
537 indices: (0..count as u32).collect(),
538 values: VectorValues::Int8(vec![1; count]),
539 };
540
541 let err = encode_vector_checked(&vector).expect_err("oversized sparse count must fail");
542 assert!(
543 matches!(
544 err,
545 ProtocolError::InvalidPacketLength {
546 length,
547 minimum: 0
548 } if length == count
549 ),
550 "got {err:?}"
551 );
552 }
553
554 #[test]
555 fn sparse_encode_rejects_mismatched_index_value_counts() {
556 let vector = Vector::Sparse {
557 num_dimensions: 4,
558 indices: vec![0, 1, 2],
559 values: VectorValues::Int8(vec![7, 8]),
560 };
561
562 let err = encode_vector_checked(&vector).expect_err("mismatched sparse vector must fail");
563 assert!(matches!(err, ProtocolError::TtcDecode(_)), "got {err:?}");
564 }
565
566 #[test]
573 fn float_elements_use_oracle_binary_transform() {
574 let image = encode_vector(&Vector::Dense(VectorValues::Float64(vec![1.0, -2.0])));
576 let body = &image[17..]; assert_eq!(&body[0..8], &[0xbf, 0xf0, 0, 0, 0, 0, 0, 0], "f64 +1.0");
578 assert_eq!(
580 &body[8..16],
581 &[0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff],
582 "f64 -2.0"
583 );
584
585 let image32 = encode_vector(&Vector::Dense(VectorValues::Float32(vec![1.0, -2.0])));
587 let body32 = &image32[17..];
588 assert_eq!(&body32[0..4], &[0xbf, 0x80, 0, 0], "f32 +1.0");
589 assert_eq!(&body32[4..8], &[0x3f, 0xff, 0xff, 0xff], "f32 -2.0");
590
591 assert_eq!(
593 decode_vector(&image).expect("decode f64"),
594 Vector::Dense(VectorValues::Float64(vec![1.0, -2.0]))
595 );
596 assert_eq!(
597 decode_vector(&image32).expect("decode f32"),
598 Vector::Dense(VectorValues::Float32(vec![1.0, -2.0]))
599 );
600 }
601
602 #[test]
603 fn rejects_bad_magic() {
604 let err = decode_vector(&[0x00, 0, 0, 0, 0, 0, 0, 0, 0]).expect_err("bad magic must fail");
605 assert!(matches!(err, ProtocolError::TtcDecode(_)));
606 }
607
608 #[test]
609 fn rejects_unsupported_version() {
610 let mut image = encode_vector(&Vector::Dense(VectorValues::Int8(vec![1])));
611 image[1] = 99; let err = decode_vector(&image).expect_err("bad version must fail");
613 assert!(matches!(err, ProtocolError::TtcDecode(_)));
614 }
615
616 #[test]
622 fn fuzz_regression_oom_oversized_element_count() {
623 let input = [219, 0, 0, 18, 3, 54, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
624 let err = decode_vector(&input).expect_err("oversized count must fail closed");
625 assert!(
626 matches!(
627 err,
628 ProtocolError::TtcDecode(_) | ProtocolError::ResourceLimit { .. }
629 ),
630 "got {err:?}"
631 );
632 }
633
634 #[test]
635 fn decode_vector_with_limits_rejects_dense_dimensions() {
636 let image = encode_vector(&Vector::Dense(VectorValues::Int8(vec![1, 2, 3, 4, 5])));
637 let limits = ProtocolLimits {
638 max_vector_dimensions: 4,
639 ..ProtocolLimits::DEFAULT
640 };
641 assert!(matches!(
642 decode_vector_with_limits(&image, limits),
643 Err(ProtocolError::ResourceLimit {
644 limit: "vector_dimensions",
645 observed: 5,
646 maximum: 4,
647 })
648 ));
649 }
650
651 #[test]
657 fn sparse_oversized_index_count_fails_closed_not_oom() {
658 let input = [
662 TNS_VECTOR_MAGIC_BYTE,
663 TNS_VECTOR_VERSION_WITH_SPARSE,
664 0x00,
665 0x20, VECTOR_FORMAT_FLOAT64,
667 0x00,
668 0x00,
669 0x00,
670 0x00, 0xFF,
672 0xFF, ];
674 let err = decode_vector(&input).expect_err("oversized sparse count must fail closed");
675 assert!(matches!(err, ProtocolError::TtcDecode(_)), "got {err:?}");
676 }
677
678 #[test]
679 fn binary_dense_bit_count_header() {
680 let image = encode_vector(&Vector::Dense(VectorValues::Binary(vec![0xA5, 0x3C])));
682 let num_elements = u32::from_be_bytes([image[5], image[6], image[7], image[8]]);
683 assert_eq!(num_elements, 16);
684 assert_eq!(image[1], TNS_VECTOR_VERSION_WITH_BINARY);
685 }
686
687 #[test]
688 fn binary_dense_rejects_non_byte_aligned_bit_count() {
689 let mut image = encode_vector(&Vector::Dense(VectorValues::Binary(vec![0xA5, 0x3C])));
690 image[5..9].copy_from_slice(&9_u32.to_be_bytes());
691 image.truncate(18); let err = decode_vector(&image).expect_err("9-bit binary vector must fail closed");
694 assert!(matches!(err, ProtocolError::TtcDecode(_)), "got {err:?}");
695 }
696
697 #[test]
698 fn decode_vector_rejects_trailing_image_bytes() {
699 let mut dense = encode_vector(&Vector::Dense(VectorValues::Float32(vec![1.0, 2.0])));
700 dense.push(0xAA);
701 let err = decode_vector(&dense).expect_err("dense image tail must fail closed");
702 assert!(matches!(err, ProtocolError::TtcDecode(_)), "got {err:?}");
703
704 let mut sparse = encode_vector(&Vector::Sparse {
705 num_dimensions: 8,
706 indices: vec![1, 4],
707 values: VectorValues::Int8(vec![7, 9]),
708 });
709 sparse.extend_from_slice(&[0xAA, 0xBB]);
710 let err = decode_vector(&sparse).expect_err("sparse image tail must fail closed");
711 assert!(matches!(err, ProtocolError::TtcDecode(_)), "got {err:?}");
712 }
713
714 fn build_from_golden(entry: &Value) -> Vector {
718 let typecode = entry["typecode"].as_str().expect("typecode");
719 let f64_at = |x: &Value| x.as_f64().expect("number");
720 let i64_at = |x: &Value| x.as_i64().expect("int");
721 let u64_at = |x: &Value| x.as_u64().expect("uint");
722 let make_values = |arr: &Value| -> VectorValues {
723 let v = arr.as_array().expect("array");
724 match typecode {
725 "f" => VectorValues::Float32(v.iter().map(|x| f64_at(x) as f32).collect()),
726 "d" => VectorValues::Float64(v.iter().map(f64_at).collect()),
727 "b" => VectorValues::Int8(v.iter().map(|x| i64_at(x) as i8).collect()),
728 "B" => VectorValues::Binary(v.iter().map(|x| u64_at(x) as u8).collect()),
729 other => panic!("unknown typecode {other}"),
730 }
731 };
732 if entry["kind"] == "sparse" {
733 Vector::Sparse {
734 num_dimensions: u64_at(&entry["num_dimensions"]) as u32,
735 indices: entry["indices"]
736 .as_array()
737 .expect("indices array")
738 .iter()
739 .map(|x| u64_at(x) as u32)
740 .collect(),
741 values: make_values(&entry["values"]),
742 }
743 } else {
744 Vector::Dense(make_values(&entry["values"]))
745 }
746 }
747
748 #[test]
749 fn matches_golden_capture() {
750 let raw = include_str!("../tests/golden/vectors.json");
751 let golden: Value = serde_json::from_str(raw).expect("parse golden json");
752 let obj = golden.as_object().expect("golden is an object");
753 assert!(!obj.is_empty(), "golden capture must not be empty");
754 for (name, entry) in obj {
755 let expected_hex = entry["image_hex"].as_str().expect("image_hex");
756 let expected = hex::decode(expected_hex).expect("decode golden hex");
757
758 let vector = build_from_golden(entry);
760 let image = encode_vector(&vector);
761 assert_eq!(
762 hex::encode(&image),
763 expected_hex,
764 "encode mismatch for golden case {name}"
765 );
766
767 let decoded = decode_vector(&expected).expect("decode golden image");
769 assert_eq!(decoded, vector, "decode mismatch for golden case {name}");
770 }
771 }
772}