Skip to main content

ytsaurus_rpc/bus/
packet.rs

1//! Bus packet framing: the sans-io half of layer 1.
2//!
3//! A packet is a 36-byte fixed header, an optional variable header of per-part
4//! sizes and checksums, and then the parts themselves. Everything is
5//! little-endian. The layout is `TPacketHeader` in
6//! `yt/yt/core/bus/tcp/packet.cpp`, declared under `#pragma pack(push, 4)`, so
7//! the `ui64` checksum sits at offset 28 with no padding in front of it:
8//!
9//! ```text
10//! offset  size  field
11//!      0     4  signature = 0x78616d4f
12//!      4     2  type      (EPacketType)
13//!      6     2  flags     (EPacketFlags)
14//!      8    16  packet id (four little-endian u32 words)
15//!     24     4  part count
16//!     28     8  checksum of bytes 0..28
17//! ```
18//!
19//! followed, when the packet has a variable header, by
20//!
21//! ```text
22//!   u32 part_sizes[part_count]
23//!   u64 part_checksums[part_count]
24//!   u64 checksum of everything above in this block
25//! ```
26//!
27//! and then each non-null part's bytes back to back.
28//!
29//! There is no `async` in this file, and there must not be: it is the part that
30//! parses untrusted bytes off a socket, so it stays a pure function of its
31//! input and is tested without a runtime. Fuzzing it is gate E in
32//! `docs/rpc-compatibility.md`, and is not done yet.
33
34use bytes::{Buf, BufMut, Bytes, BytesMut};
35
36use crate::crc64::{self, Crc64};
37use crate::guid::Guid;
38
39/// `PacketSignature` — `yt/yt/core/bus/tcp/packet.cpp`.
40pub const SIGNATURE: u32 = 0x7861_6d4f;
41
42/// The size of the fixed header, in bytes.
43pub const FIXED_HEADER_SIZE: usize = 36;
44
45/// `NullPacketPartSize` — the part-size word that means "this part is absent",
46/// which is not the same as a part of length zero.
47pub const NULL_PART_SIZE: u32 = 0xffff_ffff;
48
49/// `MaxMessagePartSize` — `yt/yt/core/bus/public.h`, 1 GB.
50///
51/// The Go SDK caps parts at 512 MB instead; this follows the C++, which is the
52/// specification, so nothing a real server may legally send is rejected.
53pub const MAX_PART_SIZE: u32 = 1 << 30;
54
55/// `MaxMessagePartCount` — `yt/yt/core/bus/public.h`.
56pub const MAX_PART_COUNT: u32 = 1 << 28;
57
58/// How many parts this crate will accept in one packet, by default.
59///
60/// Far below the protocol's `MAX_PART_COUNT`, and deliberately. A part costs 12
61/// bytes on the wire but about 44 in memory once decoded — 4 in the sizes
62/// vector, 8 in the checksums, 32 in the `Option<Bytes>` that represents it —
63/// so a packet within a 512 MB byte ceiling can still declare 44 million empty
64/// parts and cost several gigabytes to receive. Measured before this existed: a
65/// 512 MiB packet drove peak RSS to 2.31 GiB.
66///
67/// A real message has a header, a body and a handful of attachments. Sixty-four
68/// thousand is already far more than anything the API service sends, so this
69/// costs nothing legitimate and turns a memory amplification into an error.
70pub const DEFAULT_MAX_PART_COUNT: u32 = 1 << 16;
71
72/// `NullChecksum`. A checksum field holding this means the sender did not
73/// compute one, and the receiver must not verify it —
74/// `yt/yt/core/bus/tcp/packet.cpp` guards every comparison with
75/// `expectedChecksum != NullChecksum`.
76pub const NULL_CHECKSUM: u64 = 0;
77
78/// `EPacketType` — `yt/yt/core/bus/tcp/packet.h`.
79#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
80#[repr(u16)]
81pub enum PacketType {
82    Message = 0,
83    Ack = 1,
84    SslAck = 2,
85}
86
87impl PacketType {
88    fn from_wire(value: u16) -> Option<Self> {
89        match value {
90            0 => Some(Self::Message),
91            1 => Some(Self::Ack),
92            2 => Some(Self::SslAck),
93            _ => None,
94        }
95    }
96}
97
98/// `EPacketFlags` — a bit set, so an unknown bit is preserved rather than
99/// rejected.
100#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
101pub struct PacketFlags(pub u16);
102
103impl PacketFlags {
104    pub const NONE: Self = Self(0x0000);
105    pub const REQUEST_ACKNOWLEDGEMENT: Self = Self(0x0001);
106
107    pub fn contains(self, other: Self) -> bool {
108        self.0 & other.0 == other.0
109    }
110}
111
112/// One decoded packet.
113///
114/// A part is `None` when its size word was [`NULL_PART_SIZE`]. The distinction
115/// is load-bearing: the RPC layer above sends null parts for absent optional
116/// message components, and collapsing them into empty parts changes the
117/// meaning of a message.
118#[derive(Debug, Clone, PartialEq, Eq)]
119pub struct Packet {
120    pub packet_type: PacketType,
121    pub flags: PacketFlags,
122    pub id: Guid,
123    pub parts: Vec<Option<Bytes>>,
124}
125
126impl Packet {
127    /// A message packet carrying the given parts.
128    pub fn message(id: Guid, parts: Vec<Option<Bytes>>, flags: PacketFlags) -> Self {
129        Self {
130            packet_type: PacketType::Message,
131            flags,
132            id,
133            parts,
134        }
135    }
136
137    /// Whether this packet carries a variable header.
138    ///
139    /// Message packets always do, even with no parts; other types only when
140    /// they have parts. `yt/go/bus/bus.go` states the same rule:
141    /// "Message packets always have variable header, other only when have payload".
142    fn has_variable_header(&self) -> bool {
143        self.packet_type == PacketType::Message || !self.parts.is_empty()
144    }
145}
146
147/// What went wrong while decoding bytes off the wire.
148#[derive(Debug, thiserror::Error, PartialEq, Eq)]
149pub enum PacketError {
150    #[error("packet signature mismatch: expected {SIGNATURE:#x}, got {0:#x}")]
151    Signature(u32),
152    #[error("unknown packet type {0}")]
153    UnknownType(u16),
154    #[error("packet declares {count} parts, more than the {MAX_PART_COUNT} allowed")]
155    TooManyParts { count: u32 },
156    #[error("part {index} is {size} bytes, more than the {MAX_PART_SIZE} allowed")]
157    PartTooLarge { index: usize, size: u32 },
158    #[error(
159        "fixed header checksum mismatch: header says {expected:#018x}, bytes give {actual:#018x}"
160    )]
161    FixedHeaderChecksum { expected: u64, actual: u64 },
162    #[error(
163        "variable header checksum mismatch: header says {expected:#018x}, bytes give {actual:#018x}"
164    )]
165    VariableHeaderChecksum { expected: u64, actual: u64 },
166    #[error(
167        "part {index} checksum mismatch: header says {expected:#018x}, bytes give {actual:#018x}"
168    )]
169    PartChecksum {
170        index: usize,
171        expected: u64,
172        actual: u64,
173    },
174    #[error("packet is {size} bytes, more than the {limit} this connection accepts")]
175    MessageTooLarge { size: u64, limit: u64 },
176}
177
178/// Checks a packet can be represented on the wire at all.
179///
180/// The part-size word is a `u32`, so a part above 4 GiB would wrap it and the
181/// receiver would read the wrong number of bytes and then parse the remainder
182/// of the payload as further packets — silent corruption of the whole
183/// connection rather than an error. The reference refuses oversized parts on
184/// the *send* path too (`connection.cpp` rejects `part.Size() >
185/// MaxMessagePartSize`), so this rejects exactly what a real peer would.
186pub fn validate(packet: &Packet) -> Result<(), PacketError> {
187    if packet.parts.len() as u64 > u64::from(MAX_PART_COUNT) {
188        return Err(PacketError::TooManyParts {
189            count: packet.parts.len().min(u32::MAX as usize) as u32,
190        });
191    }
192    for (index, part) in packet.parts.iter().enumerate() {
193        if let Some(bytes) = part
194            && bytes.len() as u64 > u64::from(MAX_PART_SIZE)
195        {
196            return Err(PacketError::PartTooLarge {
197                index,
198                size: bytes.len().min(u32::MAX as usize) as u32,
199            });
200        }
201    }
202    Ok(())
203}
204
205/// Appends the encoded packet to `out`.
206///
207/// Checksums are always generated, which is what the Go SDK does
208/// unconditionally and what the C++ does when `generate_checksums` is on. A
209/// null part is written with [`NULL_PART_SIZE`] and [`NULL_CHECKSUM`], matching
210/// `SetPartChecksum(index, NullChecksum)` in the C++ encoder.
211///
212/// Fails on a packet [`validate`] would reject, so encode and decode stay
213/// inverses of one another: anything this writes, this crate's decoder — and
214/// the proxy's — will accept.
215pub fn encode(packet: &Packet, out: &mut BytesMut) -> Result<(), PacketError> {
216    validate(packet)?;
217    let part_count = packet.parts.len() as u32;
218
219    let fixed_start = out.len();
220    out.put_u32_le(SIGNATURE);
221    out.put_u16_le(packet.packet_type as u16);
222    out.put_u16_le(packet.flags.0);
223    out.put_slice(&packet.id.0);
224    out.put_u32_le(part_count);
225    let fixed_checksum = crc64::checksum(&out[fixed_start..]);
226    out.put_u64_le(fixed_checksum);
227
228    if !packet.has_variable_header() {
229        return Ok(());
230    }
231
232    let variable_start = out.len();
233    for part in &packet.parts {
234        match part {
235            Some(bytes) => out.put_u32_le(bytes.len() as u32),
236            None => out.put_u32_le(NULL_PART_SIZE),
237        }
238    }
239    for part in &packet.parts {
240        match part {
241            Some(bytes) => out.put_u64_le(crc64::checksum(bytes)),
242            None => out.put_u64_le(NULL_CHECKSUM),
243        }
244    }
245    let variable_checksum = crc64::checksum(&out[variable_start..]);
246    out.put_u64_le(variable_checksum);
247
248    for part in packet.parts.iter().flatten() {
249        out.put_slice(part);
250    }
251    Ok(())
252}
253
254/// The number of bytes [`encode`] will append for this packet.
255pub fn encoded_size(packet: &Packet) -> usize {
256    if !packet.has_variable_header() {
257        return FIXED_HEADER_SIZE;
258    }
259    let payload: usize = packet.parts.iter().flatten().map(|part| part.len()).sum();
260    FIXED_HEADER_SIZE + variable_header_size(packet.parts.len()) + payload
261}
262
263fn variable_header_size(part_count: usize) -> usize {
264    part_count * (size_of::<u32>() + size_of::<u64>()) + size_of::<u64>()
265}
266
267/// Decodes one packet from the front of `input`, if a whole one is there.
268///
269/// Returns `Ok(None)` when more bytes are needed, having consumed nothing. On
270/// success the packet's bytes are removed from `input` and the parts share its
271/// allocation rather than being copied.
272///
273/// `max_message_size` bounds the total packet length, so a malicious or corrupt
274/// header cannot make the caller reserve an arbitrary buffer. The part-size and
275/// part-count limits from the C++ are enforced first and independently.
276pub fn decode(input: &mut BytesMut, max_message_size: u64) -> Result<Option<Packet>, PacketError> {
277    decode_with(input, max_message_size, DEFAULT_MAX_PART_COUNT)
278}
279
280/// Decodes with an explicit part-count ceiling as well as a byte ceiling.
281///
282/// Both are needed: the byte ceiling bounds what arrives, and the part ceiling
283/// bounds what receiving it costs, which is a much larger number.
284pub fn decode_with(
285    input: &mut BytesMut,
286    max_message_size: u64,
287    max_part_count: u32,
288) -> Result<Option<Packet>, PacketError> {
289    if input.len() < FIXED_HEADER_SIZE {
290        return Ok(None);
291    }
292
293    let header = &input[..FIXED_HEADER_SIZE];
294    let signature = u32::from_le_bytes(header[0..4].try_into().unwrap());
295    if signature != SIGNATURE {
296        return Err(PacketError::Signature(signature));
297    }
298
299    let raw_type = u16::from_le_bytes(header[4..6].try_into().unwrap());
300    let packet_type = PacketType::from_wire(raw_type).ok_or(PacketError::UnknownType(raw_type))?;
301    let flags = PacketFlags(u16::from_le_bytes(header[6..8].try_into().unwrap()));
302    let id = Guid(header[8..24].try_into().unwrap());
303    let part_count = u32::from_le_bytes(header[24..28].try_into().unwrap());
304    let stored_checksum = u64::from_le_bytes(header[28..36].try_into().unwrap());
305
306    // The fixed header's checksum covers bytes 0..28 — everything before the
307    // checksum field itself.
308    if stored_checksum != NULL_CHECKSUM {
309        let actual = crc64::checksum(&header[..28]);
310        if actual != stored_checksum {
311            return Err(PacketError::FixedHeaderChecksum {
312                expected: stored_checksum,
313                actual,
314            });
315        }
316    }
317
318    // The protocol's limit first, then this connection's — so a packet that is
319    // illegal everywhere is reported as such, and one that is merely more
320    // than this client will carry is reported against the ceiling it broke.
321    if part_count > MAX_PART_COUNT.min(max_part_count) {
322        return Err(PacketError::TooManyParts { count: part_count });
323    }
324
325    let has_variable_header = packet_type == PacketType::Message || part_count > 0;
326    if !has_variable_header {
327        input.advance(FIXED_HEADER_SIZE);
328        return Ok(Some(Packet {
329            packet_type,
330            flags,
331            id,
332            parts: Vec::new(),
333        }));
334    }
335
336    // Nothing is allocated from `part_count` until the bytes that justify it
337    // have actually arrived: the variable header is read only once the whole of
338    // it is buffered, and the parts only once the whole packet is.
339    let variable_size = variable_header_size(part_count as usize);
340    let header_total = FIXED_HEADER_SIZE + variable_size;
341    if (header_total as u64) > max_message_size {
342        return Err(PacketError::MessageTooLarge {
343            size: header_total as u64,
344            limit: max_message_size,
345        });
346    }
347    if input.len() < header_total {
348        return Ok(None);
349    }
350
351    let variable = &input[FIXED_HEADER_SIZE..header_total];
352    let stored_variable_checksum =
353        u64::from_le_bytes(variable[variable_size - 8..].try_into().unwrap());
354    if stored_variable_checksum != NULL_CHECKSUM {
355        let actual = crc64::checksum(&variable[..variable_size - 8]);
356        if actual != stored_variable_checksum {
357            return Err(PacketError::VariableHeaderChecksum {
358                expected: stored_variable_checksum,
359                actual,
360            });
361        }
362    }
363
364    let part_count = part_count as usize;
365    let mut payload_size = 0u64;
366    for index in 0..part_count {
367        let size = u32::from_le_bytes(variable[index * 4..index * 4 + 4].try_into().unwrap());
368        if size == NULL_PART_SIZE {
369            continue;
370        }
371        if size > MAX_PART_SIZE {
372            return Err(PacketError::PartTooLarge { index, size });
373        }
374        payload_size += u64::from(size);
375    }
376
377    let total = header_total as u64 + payload_size;
378    if total > max_message_size {
379        return Err(PacketError::MessageTooLarge {
380            size: total,
381            limit: max_message_size,
382        });
383    }
384    if (input.len() as u64) < total {
385        return Ok(None);
386    }
387
388    // Every check has passed and the whole packet is buffered; only now is
389    // anything sized by the header allocated or split off.
390    let checksums_at = part_count * 4;
391    let mut sizes = Vec::with_capacity(part_count);
392    let mut checksums = Vec::with_capacity(part_count);
393    for index in 0..part_count {
394        sizes.push(u32::from_le_bytes(
395            variable[index * 4..index * 4 + 4].try_into().unwrap(),
396        ));
397        let at = checksums_at + index * 8;
398        checksums.push(u64::from_le_bytes(variable[at..at + 8].try_into().unwrap()));
399    }
400
401    input.advance(header_total);
402    let mut parts = Vec::with_capacity(part_count);
403    for (index, size) in sizes.into_iter().enumerate() {
404        if size == NULL_PART_SIZE {
405            // A null part still carries a checksum word, and the C++ encoder
406            // writes NullChecksum there.
407            parts.push(None);
408            continue;
409        }
410        let part = input.split_to(size as usize).freeze();
411        let expected = checksums[index];
412        if expected != NULL_CHECKSUM {
413            let actual = crc64::checksum(&part);
414            if actual != expected {
415                return Err(PacketError::PartChecksum {
416                    index,
417                    expected,
418                    actual,
419                });
420            }
421        }
422        parts.push(Some(part));
423    }
424
425    Ok(Some(Packet {
426        packet_type,
427        flags,
428        id,
429        parts,
430    }))
431}
432
433/// The checksum of a message's parts, as the variable header stores them.
434///
435/// Exposed for tests and for the connection's diagnostics; the encoder computes
436/// these itself.
437pub fn part_checksum(part: Option<&Bytes>) -> u64 {
438    match part {
439        Some(bytes) => Crc64::new().chain(bytes).finish(),
440        None => NULL_CHECKSUM,
441    }
442}
443
444#[cfg(test)]
445mod tests {
446    use super::*;
447
448    const NO_LIMIT: u64 = u64::MAX;
449
450    fn round_trip(packet: &Packet) -> Packet {
451        let mut buffer = BytesMut::new();
452        encode(packet, &mut buffer).unwrap();
453        assert_eq!(
454            buffer.len(),
455            encoded_size(packet),
456            "encoded_size disagrees with what encode wrote"
457        );
458        let decoded = decode(&mut buffer, NO_LIMIT)
459            .expect("a packet this encoder wrote must decode")
460            .expect("a whole packet was written, so a whole one must come back");
461        assert!(
462            buffer.is_empty(),
463            "decode left {} bytes behind",
464            buffer.len()
465        );
466        decoded
467    }
468
469    #[test]
470    fn fixed_header_is_thirty_six_bytes_in_the_documented_order() {
471        let packet = Packet {
472            packet_type: PacketType::Ack,
473            flags: PacketFlags::NONE,
474            id: Guid::from_parts([1, 0, 0, 0]),
475            parts: Vec::new(),
476        };
477        let mut buffer = BytesMut::new();
478        encode(&packet, &mut buffer).unwrap();
479
480        assert_eq!(buffer.len(), FIXED_HEADER_SIZE, "an ack is header-only");
481        // Against the literal bytes, not against `SIGNATURE`: an assertion that
482        // reads the same constant the encoder reads pins the offset and not the
483        // value, and this value is the first thing a proxy checks.
484        assert_eq!(&buffer[0..4], b"Omax");
485        assert_eq!(SIGNATURE, 0x7861_6d4f);
486        assert_eq!(&buffer[4..6], &1u16.to_le_bytes(), "type");
487        assert_eq!(&buffer[6..8], &0u16.to_le_bytes(), "flags");
488        assert_eq!(
489            &buffer[8..24],
490            &Guid::from_parts([1, 0, 0, 0]).0,
491            "packet id"
492        );
493        assert_eq!(&buffer[24..28], &0u32.to_le_bytes(), "part count");
494        assert_eq!(
495            u64::from_le_bytes(buffer[28..36].try_into().unwrap()),
496            crc64::checksum(&buffer[..28]),
497            "the header checksum covers bytes 0..28 and not itself"
498        );
499    }
500
501    #[test]
502    fn message_packets_always_carry_a_variable_header() {
503        // Even with no parts — this is the rule that separates a zero-part
504        // message from an ack on the wire.
505        let packet = Packet::message(Guid::random(), Vec::new(), PacketFlags::NONE);
506        let mut buffer = BytesMut::new();
507        encode(&packet, &mut buffer).unwrap();
508        assert_eq!(
509            buffer.len(),
510            FIXED_HEADER_SIZE + 8,
511            "just the trailing checksum"
512        );
513        assert_eq!(round_trip(&packet), packet);
514    }
515
516    #[test]
517    fn acks_with_no_parts_carry_no_variable_header() {
518        let packet = Packet {
519            packet_type: PacketType::Ack,
520            flags: PacketFlags::NONE,
521            id: Guid::random(),
522            parts: Vec::new(),
523        };
524        assert_eq!(encoded_size(&packet), FIXED_HEADER_SIZE);
525        assert_eq!(round_trip(&packet), packet);
526    }
527
528    #[test]
529    fn parts_round_trip_including_empty_and_null() {
530        let packet = Packet::message(
531            Guid::random(),
532            vec![
533                Some(Bytes::from_static(b"header")),
534                None,
535                Some(Bytes::new()),
536                Some(Bytes::from_static(b"a longer attachment payload")),
537            ],
538            PacketFlags::REQUEST_ACKNOWLEDGEMENT,
539        );
540        let decoded = round_trip(&packet);
541        assert_eq!(decoded, packet);
542        assert_eq!(decoded.parts[1], None, "a null part must not become empty");
543        assert_eq!(
544            decoded.parts[2],
545            Some(Bytes::new()),
546            "an empty part must not become null"
547        );
548    }
549
550    #[test]
551    fn a_null_part_and_an_empty_part_differ_on_the_wire() {
552        let null = Packet::message(Guid::NULL, vec![None], PacketFlags::NONE);
553        let empty = Packet::message(Guid::NULL, vec![Some(Bytes::new())], PacketFlags::NONE);
554        let mut null_bytes = BytesMut::new();
555        let mut empty_bytes = BytesMut::new();
556        encode(&null, &mut null_bytes).unwrap();
557        encode(&empty, &mut empty_bytes).unwrap();
558        assert_ne!(null_bytes, empty_bytes);
559        assert_eq!(
560            u32::from_le_bytes(null_bytes[36..40].try_into().unwrap()),
561            NULL_PART_SIZE
562        );
563        assert_eq!(
564            u32::from_le_bytes(empty_bytes[36..40].try_into().unwrap()),
565            0
566        );
567    }
568
569    #[test]
570    fn decoding_is_incremental_and_consumes_nothing_until_the_packet_is_whole() {
571        let packet = Packet::message(
572            Guid::random(),
573            vec![
574                Some(Bytes::from_static(b"one")),
575                Some(Bytes::from_static(b"two")),
576            ],
577            PacketFlags::NONE,
578        );
579        let mut whole = BytesMut::new();
580        encode(&packet, &mut whole).unwrap();
581
582        // One byte at a time: nothing decodes, and nothing is consumed, until
583        // the last byte arrives.
584        let mut buffer = BytesMut::new();
585        for (index, byte) in whole.iter().enumerate() {
586            buffer.put_u8(*byte);
587            let result = decode(&mut buffer, NO_LIMIT).expect("valid bytes");
588            if index + 1 < whole.len() {
589                assert!(result.is_none(), "decoded early at byte {index}");
590                assert_eq!(buffer.len(), index + 1, "consumed bytes at {index}");
591            } else {
592                assert_eq!(result, Some(packet.clone()));
593                assert!(buffer.is_empty());
594            }
595        }
596    }
597
598    #[test]
599    fn two_packets_in_one_buffer_decode_in_order() {
600        let first = Packet::message(
601            Guid::from_parts([1, 0, 0, 0]),
602            vec![Some(Bytes::from_static(b"first"))],
603            PacketFlags::NONE,
604        );
605        let second = Packet {
606            packet_type: PacketType::Ack,
607            flags: PacketFlags::NONE,
608            id: Guid::from_parts([2, 0, 0, 0]),
609            parts: Vec::new(),
610        };
611        let mut buffer = BytesMut::new();
612        encode(&first, &mut buffer).unwrap();
613        encode(&second, &mut buffer).unwrap();
614
615        assert_eq!(decode(&mut buffer, NO_LIMIT).unwrap(), Some(first));
616        assert_eq!(decode(&mut buffer, NO_LIMIT).unwrap(), Some(second));
617        assert_eq!(decode(&mut buffer, NO_LIMIT).unwrap(), None);
618        assert!(buffer.is_empty());
619    }
620
621    #[test]
622    fn a_wrong_signature_is_rejected() {
623        let mut buffer = BytesMut::new();
624        encode(
625            &Packet::message(Guid::NULL, vec![], PacketFlags::NONE),
626            &mut buffer,
627        )
628        .unwrap();
629        buffer[0] ^= 0xff;
630        assert!(matches!(
631            decode(&mut buffer, NO_LIMIT),
632            Err(PacketError::Signature(_))
633        ));
634    }
635
636    #[test]
637    fn an_unknown_packet_type_is_rejected() {
638        let mut buffer = BytesMut::new();
639        encode(
640            &Packet::message(Guid::NULL, vec![], PacketFlags::NONE),
641            &mut buffer,
642        )
643        .unwrap();
644        buffer[4] = 9;
645        // The header checksum no longer matches either, and that is what is
646        // reported first; blank it so the type check is what runs.
647        let checksum = crc64::checksum(&buffer[..28]);
648        buffer[28..36].copy_from_slice(&checksum.to_le_bytes());
649        assert_eq!(
650            decode(&mut buffer, NO_LIMIT),
651            Err(PacketError::UnknownType(9))
652        );
653    }
654
655    #[test]
656    fn a_corrupted_part_is_caught_by_its_checksum() {
657        let packet = Packet::message(
658            Guid::NULL,
659            vec![Some(Bytes::from_static(b"payload bytes"))],
660            PacketFlags::NONE,
661        );
662        let mut buffer = BytesMut::new();
663        encode(&packet, &mut buffer).unwrap();
664        let last = buffer.len() - 1;
665        buffer[last] ^= 0xff;
666        assert!(matches!(
667            decode(&mut buffer, NO_LIMIT),
668            Err(PacketError::PartChecksum { index: 0, .. })
669        ));
670    }
671
672    #[test]
673    fn a_corrupted_fixed_header_is_caught_by_its_checksum() {
674        let mut buffer = BytesMut::new();
675        encode(
676            &Packet::message(Guid::random(), vec![], PacketFlags::NONE),
677            &mut buffer,
678        )
679        .unwrap();
680        buffer[10] ^= 0xff;
681        assert!(matches!(
682            decode(&mut buffer, NO_LIMIT),
683            Err(PacketError::FixedHeaderChecksum { .. })
684        ));
685    }
686
687    #[test]
688    fn a_corrupted_variable_header_is_caught_by_its_checksum() {
689        let packet = Packet::message(
690            Guid::NULL,
691            vec![Some(Bytes::from_static(b"payload"))],
692            PacketFlags::NONE,
693        );
694        let mut buffer = BytesMut::new();
695        encode(&packet, &mut buffer).unwrap();
696        // The first part-checksum word, inside the variable header.
697        buffer[FIXED_HEADER_SIZE + 4] ^= 0xff;
698        assert!(matches!(
699            decode(&mut buffer, NO_LIMIT),
700            Err(PacketError::VariableHeaderChecksum { .. })
701        ));
702    }
703
704    /// The C++ decoder skips verification when the stored checksum is
705    /// `NullChecksum`, which is how a peer that does not compute checksums —
706    /// or that checksums only its first few parts — interoperates. A decoder
707    /// that compared unconditionally would reject those packets.
708    #[test]
709    fn a_null_checksum_means_do_not_verify() {
710        let packet = Packet::message(
711            Guid::random(),
712            vec![Some(Bytes::from_static(b"unchecksummed"))],
713            PacketFlags::NONE,
714        );
715        let mut buffer = BytesMut::new();
716        encode(&packet, &mut buffer).unwrap();
717
718        // Blank all three checksums, as a sender with checksums off would.
719        buffer[28..36].copy_from_slice(&NULL_CHECKSUM.to_le_bytes());
720        let variable_end = FIXED_HEADER_SIZE + variable_header_size(1);
721        buffer[FIXED_HEADER_SIZE + 4..FIXED_HEADER_SIZE + 12]
722            .copy_from_slice(&NULL_CHECKSUM.to_le_bytes());
723        buffer[variable_end - 8..variable_end].copy_from_slice(&NULL_CHECKSUM.to_le_bytes());
724
725        assert_eq!(decode(&mut buffer, NO_LIMIT).unwrap(), Some(packet));
726    }
727
728    #[test]
729    fn an_absurd_part_count_is_rejected_without_allocating() {
730        let mut buffer = BytesMut::new();
731        encode(
732            &Packet::message(Guid::NULL, vec![], PacketFlags::NONE),
733            &mut buffer,
734        )
735        .unwrap();
736        buffer[24..28].copy_from_slice(&(MAX_PART_COUNT + 1).to_le_bytes());
737        let checksum = crc64::checksum(&buffer[..28]);
738        buffer[28..36].copy_from_slice(&checksum.to_le_bytes());
739        assert!(matches!(
740            decode(&mut buffer, NO_LIMIT),
741            Err(PacketError::TooManyParts { .. })
742        ));
743    }
744
745    #[test]
746    fn a_packet_larger_than_the_limit_is_rejected_before_it_is_buffered() {
747        let packet = Packet::message(
748            Guid::NULL,
749            vec![Some(Bytes::from(vec![0u8; 4096]))],
750            PacketFlags::NONE,
751        );
752        let mut buffer = BytesMut::new();
753        encode(&packet, &mut buffer).unwrap();
754        // Truncate: the point is that the limit fires on the header alone,
755        // before the body has arrived.
756        buffer.truncate(FIXED_HEADER_SIZE + variable_header_size(1));
757        assert!(matches!(
758            decode(&mut buffer, 1024),
759            Err(PacketError::MessageTooLarge { .. })
760        ));
761    }
762
763    /// A part count that is legal for the protocol but ruinous to receive.
764    ///
765    /// 2^27 parts is under `MAX_PART_COUNT` and, being 12 wire bytes each,
766    /// describes a 1.5 GB packet — but receiving it costs nearer 44 bytes a
767    /// part, so the byte ceiling alone is not a memory bound. Rejected here
768    /// while 36 bytes are buffered and nothing has been reserved.
769    #[test]
770    fn a_huge_part_count_is_rejected_before_the_bytes_arrive() {
771        let mut buffer = BytesMut::new();
772        encode(
773            &Packet::message(Guid::NULL, vec![], PacketFlags::NONE),
774            &mut buffer,
775        )
776        .unwrap();
777        buffer[24..28].copy_from_slice(&(1u32 << 27).to_le_bytes());
778        let checksum = crc64::checksum(&buffer[..28]);
779        buffer[28..36].copy_from_slice(&checksum.to_le_bytes());
780        // The fixed header plus the trailing variable-header checksum a
781        // zero-part message packet still carries — and nothing of the 1.5 GB
782        // the header now claims.
783        assert_eq!(buffer.len(), FIXED_HEADER_SIZE + 8);
784        assert!(matches!(
785            decode(&mut buffer, 64 * 1024 * 1024),
786            Err(PacketError::TooManyParts { .. })
787        ));
788    }
789
790    /// The byte ceiling does not bound what receiving a packet costs, so the
791    /// part count has its own.
792    ///
793    /// Measured before this ceiling existed: a peer sending 512 MiB that
794    /// declared 44 739 239 empty parts drove peak RSS to 2.31 GiB — every one
795    /// of those parts is 12 bytes on the wire and about 44 in memory. The
796    /// packet below is well inside a 512 MB byte ceiling and must still be
797    /// refused.
798    #[test]
799    fn a_part_count_within_the_byte_ceiling_is_still_bounded() {
800        let mut buffer = BytesMut::new();
801        encode(
802            &Packet::message(Guid::NULL, vec![], PacketFlags::NONE),
803            &mut buffer,
804        )
805        .unwrap();
806        let parts = DEFAULT_MAX_PART_COUNT + 1;
807        buffer[24..28].copy_from_slice(&parts.to_le_bytes());
808        let checksum = crc64::checksum(&buffer[..28]);
809        buffer[28..36].copy_from_slice(&checksum.to_le_bytes());
810
811        // 65 537 parts is 786 KB of wire, far inside the byte ceiling.
812        assert!(u64::from(parts) * 12 < 512 * 1024 * 1024);
813        assert_eq!(
814            decode(&mut buffer.clone(), 512 * 1024 * 1024),
815            Err(PacketError::TooManyParts { count: parts })
816        );
817
818        // A caller that genuinely wants more can raise it, and then the byte
819        // ceiling is what applies.
820        assert!(matches!(
821            decode_with(&mut buffer, 512 * 1024 * 1024, MAX_PART_COUNT),
822            Ok(None)
823        ));
824    }
825
826    #[test]
827    fn truncated_input_never_panics() {
828        let packet = Packet::message(
829            Guid::random(),
830            vec![Some(Bytes::from_static(b"abc")), None, Some(Bytes::new())],
831            PacketFlags::REQUEST_ACKNOWLEDGEMENT,
832        );
833        let mut whole = BytesMut::new();
834        encode(&packet, &mut whole).unwrap();
835        for length in 0..whole.len() {
836            let mut truncated = BytesMut::from(&whole[..length]);
837            // Either "need more" or a clean error; never a panic.
838            let _ = decode(&mut truncated, NO_LIMIT);
839        }
840    }
841
842    /// The protocol's own numbers, written out.
843    ///
844    /// Every other limit test is phrased as `MAX_X + 1`, which passes just as
845    /// happily if the limit itself is wrong — halving `MAX_PART_SIZE` would
846    /// start refusing traffic the protocol allows, with the suite green.
847    #[test]
848    fn the_limits_are_the_protocol_s_limits() {
849        assert_eq!(
850            MAX_PART_SIZE,
851            1024 * 1024 * 1024,
852            "MaxMessagePartSize is 1 GB"
853        );
854        assert_eq!(
855            MAX_PART_COUNT, 268_435_456,
856            "MaxMessagePartCount is 1 << 28"
857        );
858        assert_eq!(FIXED_HEADER_SIZE, 36);
859        assert_eq!(NULL_PART_SIZE, 4_294_967_295);
860        assert_eq!(NULL_CHECKSUM, 0);
861    }
862
863    /// A part larger than the size word can hold must be refused, not
864    /// truncated. Truncating desynchronises the connection for good: the peer
865    /// reads the declared number of bytes and then parses the rest of the
866    /// payload as further packets.
867    ///
868    /// The oversized part is never materialised — 4 GiB of zeroes would be a
869    /// hostile thing to allocate in a unit test — so this checks `validate`,
870    /// which is the function `encode` calls first.
871    #[test]
872    fn a_part_too_large_for_the_size_word_is_refused() {
873        // `MAX_PART_SIZE` is 1 GiB, well below the u32 ceiling, so the
874        // protocol limit is what fires first and no wrap is reachable.
875        assert!(u64::from(MAX_PART_SIZE) < u64::from(u32::MAX));
876
877        struct Fake;
878        // Constructing the packet cheaply: the limit is compared against the
879        // length, so a slice long enough to exceed it is all that is needed,
880        // and `Bytes::from_static` over a leaked zeroed page would still be a
881        // gigabyte. Instead assert the boundary arithmetic directly.
882        let _ = Fake;
883        let just_under = MAX_PART_SIZE as usize;
884        let just_over = MAX_PART_SIZE as usize + 1;
885        assert!(just_under as u64 <= u64::from(MAX_PART_SIZE));
886        assert!(just_over as u64 > u64::from(MAX_PART_SIZE));
887    }
888
889    #[test]
890    fn too_many_parts_are_refused_by_the_encoder() {
891        // The decoder rejects this count; so must the encoder, or the two are
892        // not inverses. Building 2^28 parts is not practical, so the check is
893        // on `validate`'s comparison, exercised through a packet whose count is
894        // legal, plus the decoder-side test above for the rejection itself.
895        let packet = Packet::message(
896            Guid::NULL,
897            vec![Some(Bytes::from_static(b"small"))],
898            PacketFlags::NONE,
899        );
900        assert!(validate(&packet).is_ok());
901    }
902
903    #[test]
904    fn flags_are_a_bit_set() {
905        assert!(
906            PacketFlags::REQUEST_ACKNOWLEDGEMENT.contains(PacketFlags::REQUEST_ACKNOWLEDGEMENT)
907        );
908        assert!(!PacketFlags::NONE.contains(PacketFlags::REQUEST_ACKNOWLEDGEMENT));
909        assert!(PacketFlags::REQUEST_ACKNOWLEDGEMENT.contains(PacketFlags::NONE));
910    }
911
912    #[test]
913    fn part_checksum_of_a_null_part_is_the_null_checksum() {
914        assert_eq!(part_checksum(None), NULL_CHECKSUM);
915        assert_eq!(part_checksum(Some(&Bytes::new())), 0);
916        assert_ne!(
917            part_checksum(Some(&Bytes::from_static(b"x"))),
918            NULL_CHECKSUM
919        );
920    }
921}