Skip to main content

oracledb_protocol/
wire.rs

1#![forbid(unsafe_code)]
2
3use crate::{ProtocolError, Result};
4
5pub const TNS_MAX_SHORT_LENGTH: usize = 252;
6pub const TNS_LONG_LENGTH_INDICATOR: u8 = 0xfe;
7pub const TNS_NULL_LENGTH_INDICATOR: u8 = 0xff;
8
9#[derive(Clone, Copy, Debug, Eq, PartialEq)]
10pub enum PacketLengthWidth {
11    Legacy16,
12    Large32,
13}
14
15#[derive(Clone, Debug, Default, Eq, PartialEq)]
16pub struct TtcWriter {
17    bytes: Vec<u8>,
18    seq_num: u8,
19}
20
21impl TtcWriter {
22    pub fn new() -> Self {
23        Self::default()
24    }
25
26    /// A writer whose backing buffer is preallocated to `capacity` bytes. A
27    /// `TtcWriter::new()` starts at zero capacity, so a payload built from many
28    /// small `write_*` pushes grows the `Vec` through several doublings — each a
29    /// separate heap allocation. Sizing the buffer once (to a small-payload
30    /// default or an exact known length) collapses those growth reallocs to a
31    /// single allocation. The written bytes are byte-identical either way; this
32    /// is a pure allocation optimization.
33    pub fn with_capacity(capacity: usize) -> Self {
34        Self {
35            bytes: Vec::with_capacity(capacity),
36            seq_num: 0,
37        }
38    }
39
40    pub fn into_bytes(self) -> Vec<u8> {
41        self.bytes
42    }
43
44    pub fn write_u8(&mut self, value: u8) {
45        self.bytes.push(value);
46    }
47
48    pub fn write_u16be(&mut self, value: u16) {
49        self.bytes.extend_from_slice(&value.to_be_bytes());
50    }
51
52    pub fn write_u16le(&mut self, value: u16) {
53        self.bytes.extend_from_slice(&value.to_le_bytes());
54    }
55
56    pub fn write_u32be(&mut self, value: u32) {
57        self.bytes.extend_from_slice(&value.to_be_bytes());
58    }
59
60    pub fn write_u64be(&mut self, value: u64) {
61        self.bytes.extend_from_slice(&value.to_be_bytes());
62    }
63
64    pub fn write_ub2(&mut self, value: u16) {
65        if value == 0 {
66            self.write_u8(0);
67        } else if value <= u16::from(u8::MAX) {
68            self.write_u8(1);
69            self.write_u8(value as u8);
70        } else {
71            self.write_u8(2);
72            self.write_u16be(value);
73        }
74    }
75
76    pub fn write_ub4(&mut self, value: u32) {
77        if value == 0 {
78            self.write_u8(0);
79        } else if value <= u32::from(u8::MAX) {
80            self.write_u8(1);
81            self.write_u8(value as u8);
82        } else if value <= u32::from(u16::MAX) {
83            self.write_u8(2);
84            self.write_u16be(value as u16);
85        } else {
86            self.write_u8(4);
87            self.write_u32be(value);
88        }
89    }
90
91    pub fn write_ub8(&mut self, value: u64) {
92        if value == 0 {
93            self.write_u8(0);
94        } else if value <= u64::from(u8::MAX) {
95            self.write_u8(1);
96            self.write_u8(value as u8);
97        } else if value <= u64::from(u16::MAX) {
98            self.write_u8(2);
99            self.write_u16be(value as u16);
100        } else if value <= u64::from(u32::MAX) {
101            self.write_u8(4);
102            self.write_u32be(value as u32);
103        } else {
104            self.write_u8(8);
105            self.write_u64be(value);
106        }
107    }
108
109    pub fn write_seq_num(&mut self) {
110        self.seq_num = self.seq_num.wrapping_add(1);
111        if self.seq_num == 0 {
112            self.seq_num = 1;
113        }
114        self.write_u8(self.seq_num);
115    }
116
117    pub fn write_raw(&mut self, value: &[u8]) {
118        self.bytes.extend_from_slice(value);
119    }
120
121    pub fn write_bytes_with_length(&mut self, value: &[u8]) -> Result<()> {
122        if value.len() <= TNS_MAX_SHORT_LENGTH {
123            self.write_u8(value.len() as u8);
124            self.write_raw(value);
125            return Ok(());
126        }
127        self.write_u8(TNS_LONG_LENGTH_INDICATOR);
128        for chunk in value.chunks(32_767) {
129            self.write_ub4(u32::try_from(chunk.len()).map_err(|_| {
130                ProtocolError::InvalidPacketLength {
131                    length: chunk.len(),
132                    minimum: 0,
133                }
134            })?);
135            self.write_raw(chunk);
136        }
137        self.write_ub4(0);
138        Ok(())
139    }
140
141    pub fn write_bytes_with_two_lengths(&mut self, value: Option<&[u8]>) -> Result<()> {
142        match value {
143            Some(bytes) => {
144                self.write_ub4(u32::try_from(bytes.len()).map_err(|_| {
145                    ProtocolError::InvalidPacketLength {
146                        length: bytes.len(),
147                        minimum: 0,
148                    }
149                })?);
150                if !bytes.is_empty() {
151                    self.write_bytes_with_length(bytes)?;
152                }
153            }
154            None => self.write_ub4(0),
155        }
156        Ok(())
157    }
158
159    pub fn write_str_two_lengths(&mut self, value: &str) -> Result<()> {
160        self.write_bytes_with_two_lengths(Some(value.as_bytes()))
161    }
162
163    /// Writes a 32-bit signed integer in Oracle universal (sign-magnitude)
164    /// format: a length byte whose high bit (`0x80`) is set for negatives,
165    /// followed by the big-endian magnitude bytes. Mirrors the reference
166    /// `WriteBuffer.write_sb4` (impl/base/buffer.pyx).
167    pub fn write_sb4(&mut self, value: i32) {
168        let (sign, magnitude) = if value < 0 {
169            (0x80u8, value.unsigned_abs())
170        } else {
171            (0u8, value as u32)
172        };
173        if magnitude == 0 {
174            self.write_u8(0);
175        } else if magnitude <= u32::from(u8::MAX) {
176            self.write_u8(1 | sign);
177            self.write_u8(magnitude as u8);
178        } else if magnitude <= u32::from(u16::MAX) {
179            self.write_u8(2 | sign);
180            self.write_u16be(magnitude as u16);
181        } else {
182            self.write_u8(4 | sign);
183            self.write_u32be(magnitude);
184        }
185    }
186
187    /// Writes a keyword/value pair (text and binary values plus a ub2 keyword)
188    /// as used by the AQ message-property extension list. Mirrors the reference
189    /// `WriteBuffer.write_keyword_value_pair` (impl/thin/packet.pyx:859).
190    pub fn write_keyword_value_pair(
191        &mut self,
192        text_value: Option<&[u8]>,
193        binary_value: Option<&[u8]>,
194        keyword: u16,
195    ) -> Result<()> {
196        self.write_bytes_with_two_lengths(text_value)?;
197        self.write_bytes_with_two_lengths(binary_value)?;
198        self.write_ub2(keyword);
199        Ok(())
200    }
201
202    pub fn write_function_code(&mut self, function_code: u8) {
203        self.write_u8(crate::thin::TNS_MSG_TYPE_FUNCTION);
204        self.write_u8(function_code);
205        self.write_seq_num();
206    }
207
208    pub fn write_function_code_with_seq(&mut self, function_code: u8, seq_num: u8) {
209        self.write_u8(crate::thin::TNS_MSG_TYPE_FUNCTION);
210        self.write_u8(function_code);
211        self.write_u8(seq_num);
212    }
213}
214
215/// The structural OOM-from-length invariant for every wire decoder.
216///
217/// A length/count field read from the wire can **never** drive an allocation
218/// larger than the bytes actually remaining in the current message buffer: you
219/// cannot have `N` elements if fewer than `N * min_bytes_per_elem` bytes remain.
220/// Every reader over an untrusted buffer (`TtcReader`, the OSON / DbObject /
221/// notification cursors, the VECTOR reader) implements this trait, and every
222/// count-driven `Vec::with_capacity` / `reserve` in the decoders routes through
223/// one of its two methods instead of trusting a raw `u16`/`u32`/`u64` count.
224///
225/// This closes the OOM-from-length bug class *by construction*: a new decoder
226/// physically cannot pre-allocate from a wire count without going through a
227/// bound, because the raw `Vec::with_capacity(count)` shape is the thing we
228/// audit against (see `docs/FUZZING.md`).
229///
230/// Two flavors, both anchored on [`remaining`](Self::remaining):
231///
232/// * [`alloc_count_checked`](Self::alloc_count_checked) — fail *closed* early:
233///   returns an `Err` if the declared count cannot possibly fit, before any
234///   allocation. Use where an oversized count is unambiguously malformed.
235/// * [`with_capacity_bounded`](Self::with_capacity_bounded) — cap *the
236///   pre-allocation* at what the buffer could hold while still returning a
237///   normal growable `Vec`. Use where the loop body itself fails closed on the
238///   first truncated element read; legitimate large payloads keep working
239///   because the cap equals the honest count whenever the bytes are really
240///   there.
241pub trait BoundedReader {
242    /// Bytes still unread in the current message buffer. The ceiling on any
243    /// count-driven allocation.
244    fn remaining(&self) -> usize;
245
246    /// Validate a server-declared element `count` against the buffer: a run of
247    /// `count` elements must carry at least `count * min_bytes_per_elem` bytes,
248    /// so a count whose minimum byte footprint exceeds [`remaining`] is a lie.
249    /// Returns the (unchanged) `count` when it fits, or a fail-closed
250    /// [`ProtocolError::TtcDecode`] otherwise — never a panic, never an OOM.
251    ///
252    /// `min_bytes_per_elem` is the *minimum* on-wire size of one element (e.g.
253    /// 4 for a `u32` index, 8 for an `f64`, 1 for a length-prefixed field whose
254    /// shortest legal form is a single length byte). A zero is treated as 1.
255    fn alloc_count_checked(&self, count: usize, min_bytes_per_elem: usize) -> Result<usize> {
256        let per_elem = min_bytes_per_elem.max(1);
257        match count.checked_mul(per_elem) {
258            Some(needed) if needed <= self.remaining() => Ok(count),
259            _ => Err(ProtocolError::TtcDecode(
260                "declared element count exceeds remaining buffer",
261            )),
262        }
263    }
264
265    /// Pre-size a `Vec` for `count` elements *without* trusting `count`: the
266    /// reserved capacity is capped at `remaining() / min_bytes_per_elem`, the
267    /// largest number of elements the buffer could actually hold. The returned
268    /// `Vec` is a normal growable `Vec`, so a legitimately large payload (where
269    /// `count` really fits) is pre-sized to the honest count, and a streamed /
270    /// chunked field that grows past the initial buffer still appends correctly
271    /// — the cap only governs the *speculative* up-front reservation.
272    fn with_capacity_bounded<T>(&self, count: usize, min_bytes_per_elem: usize) -> Vec<T> {
273        let per_elem = min_bytes_per_elem.max(1);
274        Vec::with_capacity(count.min(self.remaining() / per_elem))
275    }
276}
277
278#[derive(Clone, Debug)]
279pub struct TtcReader<'a> {
280    bytes: &'a [u8],
281    pos: usize,
282}
283
284impl BoundedReader for TtcReader<'_> {
285    fn remaining(&self) -> usize {
286        TtcReader::remaining(self)
287    }
288}
289
290/// Outcome of [`TtcReader::read_bytes_borrowed`]: a borrowed run of the wire
291/// buffer for the common contiguous short-value case, an owned fallback for the
292/// non-contiguous chunked long form, or NULL.
293#[derive(Clone, Debug, PartialEq, Eq)]
294pub enum BorrowedBytes<'a> {
295    /// SQL NULL (length byte `0` or `0xff`).
296    Null,
297    /// A contiguous run borrowed directly from the buffer (zero-copy).
298    Slice(&'a [u8]),
299    /// The chunked long form (`0xfe`), reassembled into an owned `Vec` because
300    /// the chunks are not contiguous on the wire. The rare path.
301    Chunked(Vec<u8>),
302}
303
304impl<'a> TtcReader<'a> {
305    pub fn new(bytes: &'a [u8]) -> Self {
306        Self { bytes, pos: 0 }
307    }
308
309    pub fn remaining(&self) -> usize {
310        self.bytes.len().saturating_sub(self.pos)
311    }
312
313    pub fn position(&self) -> usize {
314        self.pos
315    }
316
317    pub fn remaining_slice(&self) -> &[u8] {
318        &self.bytes[self.pos.min(self.bytes.len())..]
319    }
320
321    pub fn peek_u8(&self) -> Result<u8> {
322        self.bytes
323            .get(self.pos)
324            .copied()
325            .ok_or(ProtocolError::TtcDecode("missing u8"))
326    }
327
328    pub fn read_u8(&mut self) -> Result<u8> {
329        let value = *self
330            .bytes
331            .get(self.pos)
332            .ok_or(ProtocolError::TtcDecode("missing u8"))?;
333        self.pos += 1;
334        Ok(value)
335    }
336
337    pub fn read_i8(&mut self) -> Result<i8> {
338        Ok(self.read_u8()? as i8)
339    }
340
341    pub fn read_u16be(&mut self) -> Result<u16> {
342        let bytes = self.read_raw(2)?;
343        Ok(u16::from_be_bytes(
344            bytes
345                .try_into()
346                .map_err(|_| ProtocolError::TtcDecode("invalid u16"))?,
347        ))
348    }
349
350    pub fn read_u16le(&mut self) -> Result<u16> {
351        let bytes = self.read_raw(2)?;
352        Ok(u16::from_le_bytes(
353            bytes
354                .try_into()
355                .map_err(|_| ProtocolError::TtcDecode("invalid u16"))?,
356        ))
357    }
358
359    pub fn read_u32be(&mut self) -> Result<u32> {
360        let bytes = self.read_raw(4)?;
361        Ok(u32::from_be_bytes(
362            bytes
363                .try_into()
364                .map_err(|_| ProtocolError::TtcDecode("invalid u32"))?,
365        ))
366    }
367
368    pub fn read_raw(&mut self, len: usize) -> Result<&'a [u8]> {
369        let end = self
370            .pos
371            .checked_add(len)
372            .ok_or(ProtocolError::TtcDecode("read offset overflow"))?;
373        let bytes = self
374            .bytes
375            .get(self.pos..end)
376            .ok_or(ProtocolError::TtcDecode("truncated TTC payload"))?;
377        self.pos = end;
378        Ok(bytes)
379    }
380
381    pub fn skip(&mut self, len: usize) -> Result<()> {
382        self.read_raw(len).map(|_| ())
383    }
384
385    pub fn read_ub2(&mut self) -> Result<u16> {
386        let len = self.read_u8()?;
387        match len {
388            0 => Ok(0),
389            1 => Ok(u16::from(self.read_u8()?)),
390            2 => self.read_u16be(),
391            _ => Err(ProtocolError::TtcDecode("invalid ub2 length")),
392        }
393    }
394
395    pub fn read_ub4(&mut self) -> Result<u32> {
396        let len = self.read_u8()?;
397        if len == 0 {
398            return Ok(0);
399        }
400        if len > 4 {
401            return Err(ProtocolError::TtcDecode("invalid ub4 length"));
402        }
403        let mut value = 0u32;
404        for byte in self.read_raw(usize::from(len))? {
405            value = (value << 8) | u32::from(*byte);
406        }
407        Ok(value)
408    }
409
410    pub fn read_sb4(&mut self) -> Result<i32> {
411        let len = self.read_u8()?;
412        let is_negative = len & 0x80 != 0;
413        let len = len & 0x7f;
414        if len == 0 {
415            return Ok(0);
416        }
417        if len > 4 {
418            return Err(ProtocolError::TtcDecode("invalid sb4 length"));
419        }
420        // Accumulate in the unsigned width and reinterpret as signed: a server
421        // can send four bytes whose high bit is set (so the signed value is
422        // i32::MIN) and flag the length as negative. Negating i32::MIN — or even
423        // the intermediate `value << 8` — would overflow and panic under the
424        // debug/overflow-checked fuzz build. `wrapping_neg` matches the
425        // reference C decoder's two's-complement behavior and never panics.
426        let mut value = 0u32;
427        for byte in self.read_raw(usize::from(len))? {
428            value = (value << 8) | u32::from(*byte);
429        }
430        let value = value as i32;
431        Ok(if is_negative {
432            value.wrapping_neg()
433        } else {
434            value
435        })
436    }
437
438    pub fn read_sb8(&mut self) -> Result<i64> {
439        let len = self.read_u8()?;
440        let is_negative = len & 0x80 != 0;
441        let len = len & 0x7f;
442        if len == 0 {
443            return Ok(0);
444        }
445        if len > 8 {
446            return Err(ProtocolError::TtcDecode("invalid sb8 length"));
447        }
448        // See `read_sb4`: unsigned accumulation plus `wrapping_neg` avoids the
449        // i64::MIN negate-overflow panic on adversarial input.
450        let mut value = 0u64;
451        for byte in self.read_raw(usize::from(len))? {
452            value = (value << 8) | u64::from(*byte);
453        }
454        let value = value as i64;
455        Ok(if is_negative {
456            value.wrapping_neg()
457        } else {
458            value
459        })
460    }
461
462    pub fn read_ub8(&mut self) -> Result<u64> {
463        let len = self.read_u8()?;
464        if len == 0 {
465            return Ok(0);
466        }
467        if len > 8 {
468            return Err(ProtocolError::TtcDecode("invalid ub8 length"));
469        }
470        let mut value = 0u64;
471        for byte in self.read_raw(usize::from(len))? {
472            value = (value << 8) | u64::from(*byte);
473        }
474        Ok(value)
475    }
476
477    /// Zero-copy companion to [`read_bytes`](Self::read_bytes) for the borrowed
478    /// fetch path. The common short-value form (length byte 1..=253) is a single
479    /// contiguous run in the buffer, so it is returned as a borrowed slice with
480    /// no allocation. The chunked long form (`0xfe`) is *not* contiguous on the
481    /// wire (it is a sequence of length-prefixed chunks), so it cannot be
482    /// borrowed and falls back to an owned `Vec` — the rare path. `0`/`0xff`
483    /// signal SQL NULL.
484    ///
485    /// Consumes exactly the same number of bytes as `read_bytes` for every
486    /// input, so the two are interchangeable mid-stream.
487    pub fn read_bytes_borrowed(&mut self) -> Result<BorrowedBytes<'a>> {
488        let len = self.read_u8()?;
489        if len == TNS_LONG_LENGTH_INDICATOR {
490            let mut out = Vec::new();
491            loop {
492                let chunk_len = self.read_ub4()?;
493                if chunk_len == 0 {
494                    break;
495                }
496                let chunk = self.read_raw(chunk_len as usize)?;
497                out.extend_from_slice(chunk);
498            }
499            Ok(BorrowedBytes::Chunked(out))
500        } else if len == 0 || len == TNS_NULL_LENGTH_INDICATOR {
501            Ok(BorrowedBytes::Null)
502        } else {
503            Ok(BorrowedBytes::Slice(self.read_raw(usize::from(len))?))
504        }
505    }
506
507    /// Advance past one length-prefixed TTC byte field (short, NULL, or chunked
508    /// long form) **without allocating** — the zero-copy skip used by the
509    /// borrowed fetch offset-capture pass. Consumes exactly the bytes
510    /// [`read_bytes`](Self::read_bytes) would.
511    pub fn skip_bytes_field(&mut self) -> Result<()> {
512        let len = self.read_u8()?;
513        if len == TNS_LONG_LENGTH_INDICATOR {
514            loop {
515                let chunk_len = self.read_ub4()?;
516                if chunk_len == 0 {
517                    break;
518                }
519                self.skip(chunk_len as usize)?;
520            }
521            Ok(())
522        } else if len == 0 || len == TNS_NULL_LENGTH_INDICATOR {
523            Ok(())
524        } else {
525            self.skip(usize::from(len))
526        }
527    }
528
529    pub fn read_bytes(&mut self) -> Result<Option<Vec<u8>>> {
530        let len = self.read_u8()?;
531        if len == TNS_LONG_LENGTH_INDICATOR {
532            let mut out = Vec::new();
533            loop {
534                let chunk_len = self.read_ub4()?;
535                if chunk_len == 0 {
536                    break;
537                }
538                let chunk = self.read_raw(chunk_len as usize)?;
539                out.extend_from_slice(chunk);
540            }
541            Ok(Some(out))
542        } else if len == 0 || len == TNS_NULL_LENGTH_INDICATOR {
543            Ok(None)
544        } else {
545            Ok(Some(self.read_raw(usize::from(len))?.to_vec()))
546        }
547    }
548
549    pub fn read_bytes_with_length(&mut self) -> Result<Option<Vec<u8>>> {
550        let len =
551            usize::try_from(self.read_ub4()?).map_err(|_| ProtocolError::InvalidPacketLength {
552                length: usize::MAX,
553                minimum: 0,
554            })?;
555        if len == 0 {
556            return Ok(None);
557        }
558        let value_start = self.pos;
559        match self.read_bytes() {
560            Ok(Some(bytes)) if bytes.len() == len => Ok(Some(bytes)),
561            Ok(_) | Err(_) => {
562                self.pos = value_start;
563                Ok(Some(self.read_raw(len)?.to_vec()))
564            }
565        }
566    }
567
568    pub fn read_string_with_length(&mut self) -> Result<Option<String>> {
569        let Some(bytes) = self.read_bytes_with_length()? else {
570            return Ok(None);
571        };
572        String::from_utf8(bytes)
573            .map(Some)
574            .map_err(|_| ProtocolError::TtcDecode("server sent non-UTF8 string"))
575    }
576
577    pub fn read_string(&mut self) -> Result<Option<String>> {
578        let Some(bytes) = self.read_bytes()? else {
579            return Ok(None);
580        };
581        String::from_utf8(bytes)
582            .map(Some)
583            .map_err(|_| ProtocolError::TtcDecode("server sent non-UTF8 string"))
584    }
585}
586
587pub fn encode_packet(
588    packet_type: u8,
589    packet_flags: u8,
590    data_flags: Option<u16>,
591    payload: &[u8],
592    width: PacketLengthWidth,
593) -> Result<Vec<u8>> {
594    let data_flags_len = usize::from(data_flags.is_some()) * 2;
595    let length = crate::packet::TNS_HEADER_LEN + data_flags_len + payload.len();
596    let mut out = Vec::with_capacity(length);
597    match width {
598        PacketLengthWidth::Legacy16 => {
599            let wire_length =
600                u16::try_from(length).map_err(|_| ProtocolError::PacketTooLarge { length })?;
601            out.extend_from_slice(&wire_length.to_be_bytes());
602            out.extend_from_slice(&0u16.to_be_bytes());
603        }
604        PacketLengthWidth::Large32 => {
605            let wire_length =
606                u32::try_from(length).map_err(|_| ProtocolError::PacketTooLarge { length })?;
607            out.extend_from_slice(&wire_length.to_be_bytes());
608        }
609    }
610    out.push(packet_type);
611    out.push(packet_flags);
612    out.extend_from_slice(&0u16.to_be_bytes());
613    if let Some(flags) = data_flags {
614        out.extend_from_slice(&flags.to_be_bytes());
615    }
616    out.extend_from_slice(payload);
617    Ok(out)
618}
619
620#[cfg(test)]
621mod tests {
622    use super::*;
623
624    // --- BoundedReader invariant (l2p) -----------------------------------
625    // A length/count field read from the wire can NEVER drive an allocation
626    // larger than the bytes actually remaining in the buffer. These tests pin
627    // both flavors of the bounded-allocation primitive: the early-erroring
628    // `alloc_count_checked` and the cap-and-grow `with_capacity_bounded`.
629
630    #[test]
631    fn alloc_count_checked_errs_when_count_exceeds_remaining() {
632        // 4 bytes left in the buffer, but a declared count of ~4 billion 8-byte
633        // elements. The honest minimum is 8 bytes per element, so the claim is
634        // a lie and must fail closed rather than reserving ~32 GB.
635        let bytes = [0u8; 4];
636        let reader = TtcReader::new(&bytes);
637        assert!(reader.alloc_count_checked(u32::MAX as usize, 8).is_err());
638        // count * min_bytes that overflows usize must also fail closed.
639        assert!(reader.alloc_count_checked(usize::MAX, 8).is_err());
640    }
641
642    #[test]
643    fn alloc_count_checked_ok_when_count_fits() {
644        // 16 bytes remaining, two 8-byte elements declared: legitimate.
645        let bytes = [0u8; 16];
646        let reader = TtcReader::new(&bytes);
647        assert_eq!(
648            reader.alloc_count_checked(2, 8).expect("fits"),
649            2,
650            "a count whose bytes fit must pass through unchanged"
651        );
652        // A zero-minimum element size is treated as 1 byte (defensive) and a
653        // zero count is always fine.
654        assert_eq!(reader.alloc_count_checked(0, 0).expect("zero"), 0);
655    }
656
657    #[test]
658    fn with_capacity_bounded_caps_preallocation_but_still_grows() {
659        // 8 bytes remaining; a hostile count of ~4 billion 4-byte elements.
660        let bytes = [0u8; 8];
661        let reader = TtcReader::new(&bytes);
662        let v: Vec<u32> = reader.with_capacity_bounded(u32::MAX as usize, 4);
663        // The pre-allocation is capped at remaining()/elem = 8/4 = 2, NOT 4e9.
664        assert_eq!(
665            v.capacity(),
666            2,
667            "pre-allocation must be capped by remaining"
668        );
669        // But the vec is still a normal growable Vec: pushing past the cap is
670        // fine (legitimate large payloads keep working as chunks arrive).
671        let mut v = v;
672        for i in 0..100u32 {
673            v.push(i);
674        }
675        assert_eq!(v.len(), 100);
676    }
677
678    #[test]
679    fn with_capacity_bounded_uses_full_count_when_buffer_is_large() {
680        // 400 bytes remaining, 10 four-byte elements: the real count fits, so
681        // the pre-allocation is the honest count, not an arbitrary small cap.
682        let bytes = [0u8; 400];
683        let reader = TtcReader::new(&bytes);
684        let v: Vec<u32> = reader.with_capacity_bounded(10, 4);
685        assert_eq!(v.capacity(), 10);
686    }
687
688    // Regression (w6-fuzz, query_response target): a negative-flagged sb4/sb8
689    // whose magnitude is i32::MIN / i64::MIN made `-value` overflow and panic
690    // ("attempt to negate with overflow") under the overflow-checked fuzz
691    // build. `read_sb4`/`read_sb8` must now wrap instead of panicking.
692    #[test]
693    fn sb4_sb8_negate_overflow_does_not_panic() {
694        // len byte 0x84 => negative, 4 bytes; value bytes 80 00 00 00 => i32::MIN.
695        let bytes = [0x84u8, 0x80, 0x00, 0x00, 0x00];
696        let mut reader = TtcReader::new(&bytes);
697        assert_eq!(reader.read_sb4().expect("sb4 must not panic"), i32::MIN);
698
699        // len byte 0x88 => negative, 8 bytes; 80 00.. => i64::MIN.
700        let bytes8 = [0x88u8, 0x80, 0, 0, 0, 0, 0, 0, 0];
701        let mut reader8 = TtcReader::new(&bytes8);
702        assert_eq!(reader8.read_sb8().expect("sb8 must not panic"), i64::MIN);
703    }
704
705    // Round-trip ordinary signed values to confirm the unsigned-accumulation
706    // rewrite did not change behavior for the common range.
707    #[test]
708    fn sb4_decodes_representative_values() {
709        // Hand-encoded sign-magnitude: len|0x80 for negatives.
710        let cases: [(&[u8], i32); 4] = [
711            (&[0x00], 0),
712            (&[0x01, 0x2a], 42),
713            (&[0x81, 0x2a], -42),
714            (&[0x02, 0x01, 0x00], 256),
715        ];
716        for (bytes, expected) in cases {
717            let mut reader = TtcReader::new(bytes);
718            assert_eq!(
719                reader.read_sb4().expect("sb4 decode"),
720                expected,
721                "{bytes:?}"
722            );
723        }
724    }
725
726    #[test]
727    fn ub4_round_trips_representative_values() {
728        for value in [0, 1, 255, 256, 65_535, 65_536, u32::MAX] {
729            let mut writer = TtcWriter::new();
730            writer.write_ub4(value);
731            let bytes = writer.into_bytes();
732            let mut reader = TtcReader::new(&bytes);
733            assert_eq!(reader.read_ub4().expect("ub4 should decode"), value);
734            assert_eq!(reader.remaining(), 0);
735        }
736    }
737
738    // `read_bytes_borrowed` must borrow the contiguous short-value bytes
739    // directly out of the buffer (the zero-copy hot path), signal `Null` for
740    // 0/0xff length, and fall back to an owned `Chunked` Vec for the
741    // 0xfe long-value form (which is not contiguous on the wire). The borrowed
742    // slice must equal what `read_bytes` would return, and consume exactly the
743    // same number of bytes.
744    #[test]
745    fn read_bytes_borrowed_borrows_short_values_and_owns_chunked() {
746        // Short value: length byte 3 + "abc".
747        let short = [0x03u8, b'a', b'b', b'c'];
748        let mut reader = TtcReader::new(&short);
749        match reader.read_bytes_borrowed().expect("short decode") {
750            BorrowedBytes::Slice(slice) => assert_eq!(slice, b"abc"),
751            other => panic!("expected borrowed slice, got {other:?}"),
752        }
753        assert_eq!(reader.remaining(), 0);
754
755        // NULL value: 0xff.
756        let null = [TNS_NULL_LENGTH_INDICATOR];
757        let mut reader = TtcReader::new(&null);
758        assert!(matches!(
759            reader.read_bytes_borrowed().expect("null decode"),
760            BorrowedBytes::Null
761        ));
762
763        // Zero-length value: 0x00 (also NULL in TTC).
764        let zero = [0x00u8];
765        let mut reader = TtcReader::new(&zero);
766        assert!(matches!(
767            reader.read_bytes_borrowed().expect("zero decode"),
768            BorrowedBytes::Null
769        ));
770
771        // Long/chunked value: 0xfe then ub4 chunk lengths terminated by 0.
772        let mut writer = TtcWriter::new();
773        writer
774            .write_bytes_with_length(&vec![0x5au8; 600]) // forces the 0xfe chunked form
775            .expect("chunked encode");
776        let long = writer.into_bytes();
777        let mut reader = TtcReader::new(&long);
778        match reader.read_bytes_borrowed().expect("chunked decode") {
779            BorrowedBytes::Chunked(bytes) => assert_eq!(bytes, vec![0x5au8; 600]),
780            other => panic!("expected owned chunked bytes, got {other:?}"),
781        }
782        assert_eq!(reader.remaining(), 0);
783    }
784
785    #[test]
786    fn bytes_with_length_accepts_nested_ttc_bytes() {
787        let mut writer = TtcWriter::new();
788        writer
789            .write_bytes_with_two_lengths(Some(b"abc"))
790            .expect("bytes should encode");
791        let bytes = writer.into_bytes();
792        let mut reader = TtcReader::new(&bytes);
793        assert_eq!(
794            reader
795                .read_bytes_with_length()
796                .expect("bytes should decode"),
797            Some(b"abc".to_vec())
798        );
799        assert_eq!(reader.remaining(), 0);
800    }
801
802    #[test]
803    fn bytes_with_length_accepts_direct_payload_bytes() {
804        let bytes = [1, 3, b'a', b'b', b'c'];
805        let mut reader = TtcReader::new(&bytes);
806        assert_eq!(
807            reader
808                .read_bytes_with_length()
809                .expect("bytes should decode"),
810            Some(b"abc".to_vec())
811        );
812        assert_eq!(reader.remaining(), 0);
813    }
814
815    #[test]
816    fn data_packet_uses_four_byte_length_when_negotiated() {
817        let packet = encode_packet(
818            6,
819            0,
820            Some(0),
821            &[0x03, 0x93, 0x01],
822            PacketLengthWidth::Large32,
823        )
824        .expect("packet should encode");
825        assert_eq!(&packet[..10], &[0, 0, 0, 13, 6, 0, 0, 0, 0, 0]);
826    }
827}