Skip to main content

ytsaurus_rpc/
wire.rs

1//! Layer 4: the row wire format.
2//!
3//! Rows do not travel as protobuf fields. `api_service.proto` says so outright
4//! — "actual data is passed via attachments in the wire protocol" — and the
5//! request message carries only a `TRowsetDescriptor` naming the columns. The
6//! bytes themselves are this format, which is neither YSON nor Skiff.
7//!
8//! The layout, from `yt/yt/client/table_client/wire_protocol.cpp` and its
9//! second implementation in `yt/go/wire`:
10//!
11//! ```text
12//!   rowset:  u64 row count, then that many rows
13//!   row:     u64 value count, or 0xffff_ffff_ffff_ffff for a null row
14//!   value:   u64 header, then the payload the header's type calls for
15//!
16//!   value header (8 bytes):
17//!     0..2  u16  column id, an index into the rowset descriptor's name table
18//!     2..3  u8   EValueType
19//!     3..4  u8   aggregate flag
20//!     4..8  u32  payload length, for the string-like types only
21//! ```
22//!
23//! Everything is little-endian and every element is padded to an 8-byte
24//! boundary — `SerializationAlignment == sizeof(i64)`. A null value has no
25//! payload at all; a scalar has exactly 8 bytes; a string-like value has its
26//! bytes followed by zero to seven padding bytes.
27//!
28//! Sans-io and allocation-conscious: decoding borrows out of the caller's
29//! buffer through [`Bytes`], so a rowset is not copied to be read.
30
31use bytes::{Buf, BufMut, Bytes, BytesMut};
32
33/// `SerializationAlignment` — `yt/yt/client/table_client/wire_protocol.h`.
34pub const ALIGNMENT: usize = 8;
35
36/// The value-count word that marks a null row, distinct from a row with no
37/// values. `yt/go/wire/writer.go` calls it `nullRowMarker`.
38pub const NULL_ROW_MARKER: u64 = u64::MAX;
39
40/// `MaxRowsPerRowset` — `yt/yt/client/table_client/public.h`.
41pub const MAX_ROWS_PER_ROWSET: u64 = 5 * 1024 * 1024;
42
43/// `MaxValuesPerRow` — `yt/yt/client/table_client/public.h`.
44pub const MAX_VALUES_PER_ROW: u64 = 1024;
45
46/// `MaxStringValueLength` and `MaxAnyValueLength` — both 16 MB.
47pub const MAX_VALUE_LENGTH: u32 = 16 * 1024 * 1024;
48
49/// One rowset is one RPC attachment, so it cannot exceed the protocol's
50/// `MaxMessagePartSize`.
51pub const MAX_ROWSET_SIZE: usize = crate::bus::packet::MAX_PART_SIZE as usize;
52
53/// `EValueType` — `yt/yt/client/table_client/row_base.h`.
54///
55/// The numbering is not dense: the scalar types are 0x02..0x06 and the
56/// string-like ones start again at 0x10.
57#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
58#[repr(u8)]
59pub enum ValueType {
60    Null = 0x02,
61    Int64 = 0x03,
62    Uint64 = 0x04,
63    Double = 0x05,
64    Boolean = 0x06,
65    String = 0x10,
66    Any = 0x11,
67    Composite = 0x12,
68}
69
70impl ValueType {
71    fn from_wire(value: u8) -> Option<Self> {
72        match value {
73            0x02 => Some(Self::Null),
74            0x03 => Some(Self::Int64),
75            0x04 => Some(Self::Uint64),
76            0x05 => Some(Self::Double),
77            0x06 => Some(Self::Boolean),
78            0x10 => Some(Self::String),
79            0x11 => Some(Self::Any),
80            0x12 => Some(Self::Composite),
81            _ => None,
82        }
83    }
84
85    /// Whether values of this type carry their payload as a length-prefixed
86    /// blob rather than in an 8-byte word.
87    ///
88    /// `Composite` belongs here. The Go SDK's *reader* agrees, but its writer
89    /// omits `Composite` from the branch that writes the blob, so a composite
90    /// value it encodes loses its payload; this crate follows the C++, where
91    /// `IsStringLikeType` covers `String`, `Any` and `Composite` alike. See
92    /// `docs/rpc-compatibility.md`.
93    pub fn is_string_like(self) -> bool {
94        matches!(self, Self::String | Self::Any | Self::Composite)
95    }
96}
97
98/// One value of a row.
99#[derive(Debug, Clone, PartialEq)]
100pub enum Value {
101    Null,
102    Int64(i64),
103    Uint64(u64),
104    Double(f64),
105    Boolean(bool),
106    String(Bytes),
107    /// A YSON-encoded value of any shape.
108    Any(Bytes),
109    /// A YSON-encoded value of a composite column type.
110    Composite(Bytes),
111}
112
113impl Value {
114    pub fn value_type(&self) -> ValueType {
115        match self {
116            Self::Null => ValueType::Null,
117            Self::Int64(_) => ValueType::Int64,
118            Self::Uint64(_) => ValueType::Uint64,
119            Self::Double(_) => ValueType::Double,
120            Self::Boolean(_) => ValueType::Boolean,
121            Self::String(_) => ValueType::String,
122            Self::Any(_) => ValueType::Any,
123            Self::Composite(_) => ValueType::Composite,
124        }
125    }
126
127    fn blob(&self) -> Option<&Bytes> {
128        match self {
129            Self::String(bytes) | Self::Any(bytes) | Self::Composite(bytes) => Some(bytes),
130            _ => None,
131        }
132    }
133
134    /// The 8-byte payload word for the scalar types.
135    fn scalar(&self) -> Option<u64> {
136        match self {
137            Self::Int64(value) => Some(*value as u64),
138            Self::Uint64(value) => Some(*value),
139            Self::Double(value) => Some(value.to_bits()),
140            Self::Boolean(value) => Some(u64::from(*value)),
141            _ => None,
142        }
143    }
144
145    /// The number of bytes this value occupies on the wire, header included.
146    pub fn wire_size(&self) -> usize {
147        let payload = match self {
148            Self::Null => 0,
149            Self::String(bytes) | Self::Any(bytes) | Self::Composite(bytes) => {
150                bytes.len() + padding_for(bytes.len())
151            }
152            _ => ALIGNMENT,
153        };
154        ALIGNMENT + payload
155    }
156}
157
158/// A value together with the column it belongs to.
159#[derive(Debug, Clone, PartialEq)]
160pub struct UnversionedValue {
161    /// An index into the rowset descriptor's name table, not a column name.
162    pub id: u16,
163    /// Set on values of aggregate columns; a plain write leaves it false.
164    pub aggregate: bool,
165    pub value: Value,
166}
167
168impl UnversionedValue {
169    pub fn new(id: u16, value: Value) -> Self {
170        Self {
171            id,
172            aggregate: false,
173            value,
174        }
175    }
176
177    pub fn wire_size(&self) -> usize {
178        self.value.wire_size()
179    }
180}
181
182/// One row: its values, in the order they were written.
183pub type Row = Vec<UnversionedValue>;
184
185/// A row that may be absent. `None` is the null row, which is not the same as
186/// a row with no values — a lookup that finds no row for a key reports it as a
187/// null row, so collapsing the two loses the answer.
188pub type MaybeRow = Option<Row>;
189
190/// What went wrong reading or writing a rowset.
191#[derive(Debug, thiserror::Error, PartialEq, Eq)]
192pub enum WireError {
193    #[error("rowset is truncated: need {needed} more bytes at offset {offset}")]
194    Truncated { offset: usize, needed: usize },
195    #[error("rowset declares {count} rows, more than the {MAX_ROWS_PER_ROWSET} allowed")]
196    TooManyRows { count: u64 },
197    #[error("row {row} declares {count} values, more than the {MAX_VALUES_PER_ROW} allowed")]
198    TooManyValues { row: usize, count: u64 },
199    #[error("value is {length} bytes, more than the {MAX_VALUE_LENGTH} allowed")]
200    ValueTooLong { length: u32 },
201    #[error("rowset is {size} bytes, more than the {MAX_ROWSET_SIZE}-byte RPC attachment limit")]
202    RowsetTooLarge { size: usize },
203    #[error("could not reserve {size} bytes for a rowset")]
204    AllocationFailed { size: usize },
205    #[error("unknown value type {0:#04x}")]
206    UnknownValueType(u8),
207    #[error("{0} bytes are left over after the last row")]
208    TrailingBytes(usize),
209}
210
211const fn padding_for(length: usize) -> usize {
212    (ALIGNMENT - (length % ALIGNMENT)) % ALIGNMENT
213}
214
215/// The number of bytes [`encode_rowset`] will produce for a valid rowset.
216pub fn encoded_size(rows: &[MaybeRow]) -> usize {
217    let mut size = ALIGNMENT;
218    for row in rows {
219        size += ALIGNMENT;
220        if let Some(row) = row {
221            size += row.iter().map(UnversionedValue::wire_size).sum::<usize>();
222        }
223    }
224    size
225}
226
227/// Encodes a rowset into the wire format.
228///
229/// Fails rather than emitting a rowset the server will reject or, worse,
230/// misread: every limit is checked before allocating the output buffer. This
231/// matters because [`Bytes`] may share its backing storage: many values can
232/// name one 16 MiB buffer, while their encoded rowset would require far more
233/// memory. The Go writer validates the same three per-row limits before
234/// producing a byte; the attachment limit is this client's additional bound.
235pub fn encode_rowset(rows: &[MaybeRow]) -> Result<Bytes, WireError> {
236    let size = validate_and_measure(rows)?;
237    // `BytesMut::with_capacity` aborts on allocation failure. Reserve through
238    // `Vec` instead, so an impossible rowset is returned as an ordinary error.
239    let mut storage = Vec::new();
240    storage
241        .try_reserve_exact(size)
242        .map_err(|_| WireError::AllocationFailed { size })?;
243    // `Bytes::from(Vec)` and then `BytesMut::from(Bytes)` preserve a unique
244    // vector's allocation without a copy.
245    let mut buffer = BytesMut::from(Bytes::from(storage));
246    encode_rowset_unchecked(rows, &mut buffer);
247    Ok(buffer.freeze())
248}
249
250/// Encodes a rowset, appending to an existing buffer.
251///
252/// Does not modify `out` when validation fails.
253pub fn encode_rowset_into(rows: &[MaybeRow], out: &mut BytesMut) -> Result<(), WireError> {
254    validate_and_measure(rows)?;
255
256    encode_rowset_unchecked(rows, out);
257    Ok(())
258}
259
260/// Validates all of the limits the writer relies on and returns the exact
261/// encoded length. It deliberately precedes every allocation in
262/// [`encode_rowset`].
263fn validate_and_measure(rows: &[MaybeRow]) -> Result<usize, WireError> {
264    if rows.len() as u64 > MAX_ROWS_PER_ROWSET {
265        return Err(WireError::TooManyRows {
266            count: rows.len() as u64,
267        });
268    }
269
270    let mut size = ALIGNMENT;
271    for (index, row) in rows.iter().enumerate() {
272        add_to_rowset_size(&mut size, ALIGNMENT)?;
273        let Some(row) = row else {
274            continue;
275        };
276        if row.len() as u64 > MAX_VALUES_PER_ROW {
277            return Err(WireError::TooManyValues {
278                row: index,
279                count: row.len() as u64,
280            });
281        }
282        for value in row {
283            validate_value(value)?;
284            add_to_rowset_size(&mut size, value.wire_size())?;
285        }
286    }
287    Ok(size)
288}
289
290fn add_to_rowset_size(size: &mut usize, additional: usize) -> Result<(), WireError> {
291    *size = size
292        .checked_add(additional)
293        .ok_or(WireError::RowsetTooLarge { size: usize::MAX })?;
294    if *size > MAX_ROWSET_SIZE {
295        return Err(WireError::RowsetTooLarge { size: *size });
296    }
297    Ok(())
298}
299
300fn encode_rowset_unchecked(rows: &[MaybeRow], out: &mut BytesMut) {
301    out.put_u64_le(rows.len() as u64);
302    for row in rows {
303        let Some(row) = row else {
304            out.put_u64_le(NULL_ROW_MARKER);
305            continue;
306        };
307        out.put_u64_le(row.len() as u64);
308        for value in row {
309            encode_value_unchecked(value, out);
310        }
311    }
312}
313
314fn validate_value(value: &UnversionedValue) -> Result<(), WireError> {
315    let blob = value.value.blob();
316    if let Some(blob) = blob
317        && blob.len() as u64 > u64::from(MAX_VALUE_LENGTH)
318    {
319        // Checked against the protocol limit rather than against `u32::MAX`:
320        // a blob between the two would fit the length word and still be
321        // refused by the server, and one beyond `u32::MAX` would wrap the word
322        // and turn the rest of the payload into garbage value headers.
323        return Err(WireError::ValueTooLong {
324            length: blob.len().min(u32::MAX as usize) as u32,
325        });
326    }
327    Ok(())
328}
329
330fn encode_value_unchecked(value: &UnversionedValue, out: &mut BytesMut) {
331    let blob = value.value.blob();
332
333    out.put_u16_le(value.id);
334    out.put_u8(value.value.value_type() as u8);
335    out.put_u8(u8::from(value.aggregate));
336    // The length word is meaningful only for the string-like types; the C++
337    // and Go writers both leave it zero otherwise.
338    out.put_u32_le(blob.map_or(0, |bytes| bytes.len() as u32));
339
340    if let Some(scalar) = value.value.scalar() {
341        out.put_u64_le(scalar);
342    } else if let Some(blob) = blob {
343        out.put_slice(blob);
344        // Pad with zeroes. The padding bytes are never read back, but writing
345        // whatever happened to be in the buffer would make the encoding
346        // non-deterministic and the golden vectors meaningless.
347        out.put_bytes(0, padding_for(blob.len()));
348    }
349}
350
351/// Decodes a rowset from the wire format.
352///
353/// The returned values borrow `input` rather than copying it.
354pub fn decode_rowset(input: &Bytes) -> Result<Vec<MaybeRow>, WireError> {
355    let mut reader = Reader { input, offset: 0 };
356
357    let row_count = reader.read_u64()?;
358    if row_count > MAX_ROWS_PER_ROWSET {
359        return Err(WireError::TooManyRows { count: row_count });
360    }
361
362    // `row_count` is bounded, but 5M is still a large reservation to make on a
363    // peer's say-so, so grow into it instead of trusting it up front.
364    let mut rows = Vec::new();
365    for index in 0..row_count as usize {
366        let value_count = reader.read_u64()?;
367        if value_count == NULL_ROW_MARKER {
368            rows.push(None);
369            continue;
370        }
371        if value_count > MAX_VALUES_PER_ROW {
372            return Err(WireError::TooManyValues {
373                row: index,
374                count: value_count,
375            });
376        }
377        let mut row = Row::with_capacity(value_count as usize);
378        for _ in 0..value_count {
379            row.push(reader.read_value()?);
380        }
381        rows.push(Some(row));
382    }
383
384    if reader.offset != input.len() {
385        return Err(WireError::TrailingBytes(input.len() - reader.offset));
386    }
387
388    Ok(rows)
389}
390
391struct Reader<'a> {
392    input: &'a Bytes,
393    offset: usize,
394}
395
396impl Reader<'_> {
397    fn need(&self, count: usize) -> Result<(), WireError> {
398        if self.input.len() - self.offset < count {
399            return Err(WireError::Truncated {
400                offset: self.offset,
401                needed: count - (self.input.len() - self.offset),
402            });
403        }
404        Ok(())
405    }
406
407    fn read_u64(&mut self) -> Result<u64, WireError> {
408        self.need(8)?;
409        let mut slice = &self.input[self.offset..self.offset + 8];
410        self.offset += 8;
411        Ok(slice.get_u64_le())
412    }
413
414    fn read_value(&mut self) -> Result<UnversionedValue, WireError> {
415        self.need(8)?;
416        let header = &self.input[self.offset..self.offset + 8];
417        let id = u16::from_le_bytes(header[0..2].try_into().unwrap());
418        let raw_type = header[2];
419        let aggregate = header[3] != 0;
420        let length = u32::from_le_bytes(header[4..8].try_into().unwrap());
421        self.offset += 8;
422
423        let value_type =
424            ValueType::from_wire(raw_type).ok_or(WireError::UnknownValueType(raw_type))?;
425
426        let value = match value_type {
427            ValueType::Null => Value::Null,
428            ValueType::Int64 | ValueType::Uint64 | ValueType::Double | ValueType::Boolean => {
429                let word = self.read_u64()?;
430                match value_type {
431                    ValueType::Int64 => Value::Int64(word as i64),
432                    ValueType::Uint64 => Value::Uint64(word),
433                    ValueType::Double => Value::Double(f64::from_bits(word)),
434                    // Any non-zero word is true; the C++ writes 1.
435                    _ => Value::Boolean(word != 0),
436                }
437            }
438            ValueType::String | ValueType::Any | ValueType::Composite => {
439                if length > MAX_VALUE_LENGTH {
440                    return Err(WireError::ValueTooLong { length });
441                }
442                let length = length as usize;
443                let padded = length + padding_for(length);
444                self.need(padded)?;
445                let blob = self.input.slice(self.offset..self.offset + length);
446                self.offset += padded;
447                match value_type {
448                    ValueType::String => Value::String(blob),
449                    ValueType::Any => Value::Any(blob),
450                    _ => Value::Composite(blob),
451                }
452            }
453        };
454
455        Ok(UnversionedValue {
456            id,
457            aggregate,
458            value,
459        })
460    }
461}
462
463#[cfg(test)]
464mod tests {
465    use super::*;
466
467    /// Encodes a rowset that is known to be valid.
468    fn unwrap_encode(rows: &[MaybeRow]) -> Bytes {
469        encode_rowset(rows).expect("this rowset is within every limit")
470    }
471
472    fn round_trip(rows: &[MaybeRow]) -> Vec<MaybeRow> {
473        let encoded = unwrap_encode(rows);
474        assert_eq!(
475            encoded.len(),
476            encoded_size(rows),
477            "encoded_size disagrees with what encode_rowset wrote"
478        );
479        assert_eq!(
480            encoded.len() % ALIGNMENT,
481            0,
482            "a rowset is 8-byte aligned throughout"
483        );
484        decode_rowset(&encoded).expect("what this encoder wrote must decode")
485    }
486
487    fn sample_row() -> Row {
488        // The same row as `testRow` in `yt/go/wire/row_test.go`, so a reader
489        // comparing the two implementations is comparing like with like.
490        vec![
491            UnversionedValue::new(1, Value::Null),
492            UnversionedValue::new(2, Value::Boolean(true)),
493            UnversionedValue::new(3, Value::Boolean(false)),
494            UnversionedValue::new(4, Value::Int64(-42)),
495            UnversionedValue::new(5, Value::Uint64(42)),
496            UnversionedValue::new(6, Value::Double(1.25)),
497            UnversionedValue::new(7, Value::String(Bytes::from_static(b"foobar"))),
498            UnversionedValue::new(8, Value::Any(Bytes::from_static(b"[1;2;3]"))),
499            UnversionedValue::new(9, Value::String(Bytes::new())),
500        ]
501    }
502
503    #[test]
504    fn value_types_carry_the_documented_numbers() {
505        assert_eq!(ValueType::Null as u8, 0x02);
506        assert_eq!(ValueType::Int64 as u8, 0x03);
507        assert_eq!(ValueType::Uint64 as u8, 0x04);
508        assert_eq!(ValueType::Double as u8, 0x05);
509        assert_eq!(ValueType::Boolean as u8, 0x06);
510        assert_eq!(ValueType::String as u8, 0x10);
511        assert_eq!(ValueType::Any as u8, 0x11);
512        assert_eq!(ValueType::Composite as u8, 0x12);
513    }
514
515    #[test]
516    fn the_rowset_and_row_headers_are_single_words() {
517        let encoded = unwrap_encode(&[Some(vec![UnversionedValue::new(0, Value::Int64(7))])]);
518        assert_eq!(&encoded[0..8], &1u64.to_le_bytes(), "row count");
519        assert_eq!(&encoded[8..16], &1u64.to_le_bytes(), "value count");
520        assert_eq!(encoded.len(), 8 + 8 + 8 + 8);
521    }
522
523    #[test]
524    fn a_value_header_is_id_type_aggregate_length() {
525        let value = UnversionedValue {
526            id: 0x1234,
527            aggregate: true,
528            value: Value::String(Bytes::from_static(b"abc")),
529        };
530        let encoded = unwrap_encode(&[Some(vec![value])]);
531        let header = &encoded[16..24];
532        assert_eq!(&header[0..2], &0x1234u16.to_le_bytes(), "id");
533        assert_eq!(header[2], ValueType::String as u8, "type");
534        assert_eq!(header[3], 1, "aggregate");
535        assert_eq!(&header[4..8], &3u32.to_le_bytes(), "length");
536        assert_eq!(&encoded[24..27], b"abc");
537        assert_eq!(
538            &encoded[27..32],
539            &[0, 0, 0, 0, 0],
540            "padded to eight with zeroes"
541        );
542    }
543
544    #[test]
545    fn a_null_value_has_no_payload_at_all() {
546        let encoded = unwrap_encode(&[Some(vec![UnversionedValue::new(1, Value::Null)])]);
547        // rowset header + row header + one 8-byte value header, and nothing else.
548        assert_eq!(encoded.len(), 24);
549        assert_eq!(
550            &encoded[20..24],
551            &0u32.to_le_bytes(),
552            "length word stays zero"
553        );
554    }
555
556    #[test]
557    fn scalars_occupy_exactly_one_word() {
558        for value in [
559            Value::Int64(-1),
560            Value::Uint64(u64::MAX),
561            Value::Double(-0.0),
562            Value::Boolean(true),
563        ] {
564            let encoded = unwrap_encode(&[Some(vec![UnversionedValue::new(0, value.clone())])]);
565            assert_eq!(
566                encoded.len(),
567                32,
568                "{value:?} should be header plus one word"
569            );
570            assert_eq!(
571                &encoded[20..24],
572                &0u32.to_le_bytes(),
573                "{value:?} must leave the length word zero"
574            );
575        }
576    }
577
578    #[test]
579    fn everything_round_trips() {
580        let rows = vec![None, Some(Vec::new()), Some(sample_row())];
581        assert_eq!(round_trip(&rows), rows);
582    }
583
584    #[test]
585    fn a_null_row_is_not_an_empty_row() {
586        let encoded_null = unwrap_encode(&[None]);
587        let encoded_empty = unwrap_encode(&[Some(Vec::new())]);
588        assert_ne!(encoded_null, encoded_empty);
589        assert_eq!(&encoded_null[8..16], &NULL_ROW_MARKER.to_le_bytes());
590        assert_eq!(&encoded_empty[8..16], &0u64.to_le_bytes());
591
592        assert_eq!(decode_rowset(&encoded_null).unwrap(), vec![None]);
593        assert_eq!(
594            decode_rowset(&encoded_empty).unwrap(),
595            vec![Some(Vec::new())]
596        );
597    }
598
599    #[test]
600    fn strings_of_every_length_modulo_eight_round_trip() {
601        // The padding rule is where an implementation silently goes wrong, so
602        // walk every residue plus the boundary cases.
603        for length in 0..24usize {
604            let blob = Bytes::from(vec![b'x'; length]);
605            let rows = vec![Some(vec![UnversionedValue::new(
606                0,
607                Value::String(blob.clone()),
608            )])];
609            let encoded = unwrap_encode(&rows);
610            assert_eq!(
611                encoded.len() % ALIGNMENT,
612                0,
613                "length {length} left the stream unaligned"
614            );
615            assert_eq!(
616                round_trip(&rows),
617                rows,
618                "length {length} did not round-trip"
619            );
620        }
621    }
622
623    /// The Go SDK's writer omits `Composite` from the branch that writes the
624    /// blob, so a composite value it encodes arrives empty. The C++ treats
625    /// `Composite` as string-like everywhere, and so does this crate.
626    #[test]
627    fn composite_values_keep_their_payload() {
628        let rows = vec![Some(vec![UnversionedValue::new(
629            3,
630            Value::Composite(Bytes::from_static(b"[1;2;3]")),
631        )])];
632        let decoded = round_trip(&rows);
633        assert_eq!(decoded, rows);
634        match &decoded[0].as_ref().unwrap()[0].value {
635            Value::Composite(blob) => assert_eq!(blob, &Bytes::from_static(b"[1;2;3]")),
636            other => panic!("expected a composite value, got {other:?}"),
637        }
638    }
639
640    #[test]
641    fn doubles_survive_bit_for_bit() {
642        for value in [
643            0.0,
644            -0.0,
645            1.25,
646            f64::MIN,
647            f64::MAX,
648            f64::INFINITY,
649            f64::NEG_INFINITY,
650        ] {
651            let rows = vec![Some(vec![UnversionedValue::new(0, Value::Double(value))])];
652            let decoded = round_trip(&rows);
653            match decoded[0].as_ref().unwrap()[0].value {
654                Value::Double(read) => assert_eq!(read.to_bits(), value.to_bits()),
655                ref other => panic!("expected a double, got {other:?}"),
656            }
657        }
658        // NaN separately: it is never equal to itself, so compare the bits.
659        let rows = vec![Some(vec![UnversionedValue::new(
660            0,
661            Value::Double(f64::NAN),
662        )])];
663        let encoded = unwrap_encode(&rows);
664        match decode_rowset(&encoded).unwrap()[0].as_ref().unwrap()[0].value {
665            Value::Double(read) => assert!(read.is_nan()),
666            ref other => panic!("expected a double, got {other:?}"),
667        }
668    }
669
670    #[test]
671    fn negative_integers_use_two_s_complement_in_the_word() {
672        let rows = vec![Some(vec![UnversionedValue::new(0, Value::Int64(-42))])];
673        let encoded = unwrap_encode(&rows);
674        assert_eq!(&encoded[24..32], &(-42i64 as u64).to_le_bytes());
675        assert_eq!(round_trip(&rows), rows);
676    }
677
678    #[test]
679    fn the_aggregate_flag_survives() {
680        let rows = vec![Some(vec![UnversionedValue {
681            id: 5,
682            aggregate: true,
683            value: Value::Int64(1),
684        }])];
685        assert_eq!(round_trip(&rows), rows);
686    }
687
688    /// Every prefix of a valid rowset must be rejected, and rejected as a
689    /// *truncation* rather than as some incidental error — a decoder that
690    /// mistook a short buffer for a different fault would be reporting the
691    /// wrong thing to the caller.
692    #[test]
693    fn truncated_input_is_an_error_not_a_panic() {
694        let rows = vec![Some(sample_row())];
695        let whole = unwrap_encode(&rows);
696        for length in 0..whole.len() {
697            let truncated = whole.slice(0..length);
698            match decode_rowset(&truncated) {
699                Err(WireError::Truncated { offset, needed }) => {
700                    assert!(
701                        needed > 0,
702                        "a truncation that needs no more bytes is not one"
703                    );
704                    assert!(
705                        offset <= length,
706                        "reported offset {offset} is past the {length} bytes given"
707                    );
708                }
709                Err(other) => panic!("cut to {length} bytes gave {other:?}, not a truncation"),
710                Ok(rows) => panic!("a rowset cut to {length} bytes decoded to {rows:?}"),
711            }
712        }
713    }
714
715    #[test]
716    fn trailing_bytes_are_rejected() {
717        let mut encoded = BytesMut::from(&unwrap_encode(&[Some(sample_row())])[..]);
718        encoded.put_u64_le(0);
719        assert_eq!(
720            decode_rowset(&encoded.freeze()),
721            Err(WireError::TrailingBytes(8))
722        );
723    }
724
725    #[test]
726    fn an_unknown_value_type_is_rejected() {
727        let mut encoded = BytesMut::from(
728            &unwrap_encode(&[Some(vec![UnversionedValue::new(0, Value::Int64(1))])])[..],
729        );
730        encoded[18] = 0x7f;
731        assert_eq!(
732            decode_rowset(&encoded.freeze()),
733            Err(WireError::UnknownValueType(0x7f))
734        );
735    }
736
737    #[test]
738    fn an_absurd_row_count_is_rejected_before_anything_is_reserved() {
739        let mut buffer = BytesMut::new();
740        buffer.put_u64_le(MAX_ROWS_PER_ROWSET + 1);
741        assert_eq!(
742            decode_rowset(&buffer.freeze()),
743            Err(WireError::TooManyRows {
744                count: MAX_ROWS_PER_ROWSET + 1
745            })
746        );
747    }
748
749    #[test]
750    fn an_absurd_value_count_is_rejected() {
751        let mut buffer = BytesMut::new();
752        buffer.put_u64_le(1);
753        buffer.put_u64_le(MAX_VALUES_PER_ROW + 1);
754        assert_eq!(
755            decode_rowset(&buffer.freeze()),
756            Err(WireError::TooManyValues {
757                row: 0,
758                count: MAX_VALUES_PER_ROW + 1
759            })
760        );
761    }
762
763    #[test]
764    fn an_absurd_value_length_is_rejected_before_the_bytes_are_read() {
765        let mut buffer = BytesMut::new();
766        buffer.put_u64_le(1);
767        buffer.put_u64_le(1);
768        buffer.put_u16_le(0);
769        buffer.put_u8(ValueType::String as u8);
770        buffer.put_u8(0);
771        buffer.put_u32_le(MAX_VALUE_LENGTH + 1);
772        assert_eq!(
773            decode_rowset(&buffer.freeze()),
774            Err(WireError::ValueTooLong {
775                length: MAX_VALUE_LENGTH + 1
776            })
777        );
778    }
779
780    /// A row count that is plausible but unbacked must not make the decoder
781    /// reserve for it. This would take 5M rows' worth of allocation if the
782    /// count were trusted, and only 16 bytes of input exist.
783    #[test]
784    fn a_large_but_legal_row_count_with_no_rows_behind_it_fails_cheaply() {
785        let mut buffer = BytesMut::new();
786        buffer.put_u64_le(MAX_ROWS_PER_ROWSET);
787        assert!(matches!(
788            decode_rowset(&buffer.freeze()),
789            Err(WireError::Truncated { .. })
790        ));
791    }
792
793    /// The decoder refuses these; so must the encoder. A rowset this crate
794    /// emits and then cannot read back would be a bug the golden vectors would
795    /// never catch, because they only cover valid input.
796    #[test]
797    fn the_encoder_refuses_what_the_decoder_would() {
798        let too_many_values = vec![Some(
799            (0..MAX_VALUES_PER_ROW as u16 + 1)
800                .map(|id| UnversionedValue::new(id, Value::Int64(0)))
801                .collect::<Row>(),
802        )];
803        assert_eq!(
804            encode_rowset(&too_many_values),
805            Err(WireError::TooManyValues {
806                row: 0,
807                count: MAX_VALUES_PER_ROW + 1
808            })
809        );
810
811        let too_long = vec![Some(vec![UnversionedValue::new(
812            0,
813            Value::String(Bytes::from(vec![0u8; MAX_VALUE_LENGTH as usize + 1])),
814        )])];
815        assert_eq!(
816            encode_rowset(&too_long),
817            Err(WireError::ValueTooLong {
818                length: MAX_VALUE_LENGTH + 1
819            })
820        );
821    }
822
823    /// A [`Bytes`] clone shares its allocation, so this input holds one 16 MiB
824    /// blob while a naive pre-allocation would ask for more than 16 GiB. The
825    /// value-count check must therefore happen before sizing the output.
826    #[test]
827    fn too_many_shared_large_values_are_refused_before_allocation() {
828        let shared = Bytes::from(vec![0; MAX_VALUE_LENGTH as usize]);
829        let row = (0..=MAX_VALUES_PER_ROW)
830            .map(|_| UnversionedValue::new(0, Value::String(shared.clone())))
831            .collect::<Row>();
832
833        assert_eq!(
834            encode_rowset(&[Some(row)]),
835            Err(WireError::TooManyValues {
836                row: 0,
837                count: MAX_VALUES_PER_ROW + 1,
838            })
839        );
840    }
841
842    /// The row itself is legal, but its attachment is not. Without the
843    /// aggregate check this has one 16 MiB allocation on input and attempts a
844    /// one-gibibyte output allocation before packet framing rejects it.
845    #[test]
846    fn a_rowset_larger_than_one_rpc_attachment_is_refused_before_allocation() {
847        let shared = Bytes::from(vec![0; MAX_VALUE_LENGTH as usize]);
848        let row = (0..65)
849            .map(|_| UnversionedValue::new(0, Value::String(shared.clone())))
850            .collect::<Row>();
851
852        assert!(matches!(
853            encode_rowset(&[Some(row)]),
854            Err(WireError::RowsetTooLarge { .. })
855        ));
856    }
857
858    #[test]
859    fn a_rejected_encode_does_not_append_a_partial_rowset() {
860        let mut output = BytesMut::from(&b"prefix"[..]);
861        let rows = vec![Some(
862            (0..=MAX_VALUES_PER_ROW as u16)
863                .map(|id| UnversionedValue::new(id, Value::Int64(0)))
864                .collect::<Row>(),
865        )];
866
867        assert!(matches!(
868            encode_rowset_into(&rows, &mut output),
869            Err(WireError::TooManyValues { .. })
870        ));
871        assert_eq!(&output[..], b"prefix");
872    }
873
874    #[test]
875    fn a_big_rowset_round_trips() {
876        let rows: Vec<MaybeRow> = (0..1000)
877            .map(|index| {
878                Some(vec![
879                    UnversionedValue::new(0, Value::Int64(index)),
880                    UnversionedValue::new(1, Value::String(Bytes::from(format!("row {index}")))),
881                ])
882            })
883            .collect();
884        assert_eq!(round_trip(&rows), rows);
885    }
886}