Skip to main content

dig_peer_protocol/
bytes.rs

1//! [`Bytes`] — the DIG-owned payload container for [`DigMessage`](crate::DigMessage).
2//!
3//! ## Why this is not `chia_protocol::Bytes`
4//!
5//! The DIG peer wire is a native protocol, not a chia protocol that happens to carry extra
6//! opcodes. A DIG frame's payload type is part of DIG's public API: every consumer that builds
7//! or reads a DIG message names it. Sourcing that type from `chia-protocol` meant a `chia-protocol`
8//! version bump was a breaking change to the DIG wire API, and it is what let chia types leak
9//! into consumers that have no chia traffic at all.
10//!
11//! ## Byte-identity is the whole constraint
12//!
13//! This is a live network. The [`Streamable`] encoding below is deliberately identical to the one
14//! it replaces — a `u32` big-endian length prefix followed by the raw bytes — so a DIG peer on the
15//! old type and a DIG peer on this one exchange the same frames. `tests/golden_wire_vectors.rs`
16//! pins that as absolute hex; it is not inferred from a round-trip.
17
18use std::{fmt, io::Cursor, ops::Deref};
19
20use chia_sha2::Sha256;
21use chia_traits::{Error, Result, Streamable};
22
23/// A length-prefixed byte payload.
24///
25/// Cheap to build from anything that owns or borrows bytes, and derefs to `[u8]`, so it reads as
26/// a slice everywhere it is consumed.
27#[derive(Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
28pub struct Bytes(Vec<u8>);
29
30impl Bytes {
31    /// Wrap an owned byte vector, taking ownership without copying.
32    #[must_use]
33    pub fn new(bytes: Vec<u8>) -> Self {
34        Self(bytes)
35    }
36
37    /// Number of payload bytes.
38    #[must_use]
39    pub fn len(&self) -> usize {
40        self.0.len()
41    }
42
43    /// Whether the payload is empty. A zero-length payload is a valid DIG frame.
44    #[must_use]
45    pub fn is_empty(&self) -> bool {
46        self.0.is_empty()
47    }
48
49    /// Consume into the underlying vector, without copying.
50    #[must_use]
51    pub fn into_inner(self) -> Vec<u8> {
52        self.0
53    }
54}
55
56/// Hex, because a payload is read against a wire dump far more often than as a decimal list.
57impl fmt::Display for Bytes {
58    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59        for byte in &self.0 {
60            write!(f, "{byte:02x}")?;
61        }
62        Ok(())
63    }
64}
65
66/// Same hex rendering as [`Display`](fmt::Display) — a `Vec<u8>`'s derived `Debug` is unreadable
67/// at payload sizes, and a payload is almost always inspected inside a larger `Debug` dump.
68impl fmt::Debug for Bytes {
69    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70        write!(f, "Bytes({self})")
71    }
72}
73
74impl Streamable for Bytes {
75    fn update_digest(&self, digest: &mut Sha256) {
76        #[allow(clippy::cast_possible_truncation)]
77        (self.0.len() as u32).update_digest(digest);
78        digest.update(&self.0);
79    }
80
81    fn stream(&self, out: &mut Vec<u8>) -> Result<()> {
82        // The length prefix is a u32, so a payload that cannot be described by one is not
83        // expressible on the wire at all — refused here rather than silently truncated.
84        if self.0.len() > u32::MAX as usize {
85            return Err(Error::SequenceTooLarge);
86        }
87        #[allow(clippy::cast_possible_truncation)]
88        (self.0.len() as u32).stream(out)?;
89        out.extend_from_slice(&self.0);
90        Ok(())
91    }
92
93    fn parse<const TRUSTED: bool>(input: &mut Cursor<&[u8]>) -> Result<Self> {
94        let len = u32::parse::<TRUSTED>(input)? as usize;
95        let start = usize::try_from(input.position()).map_err(|_| Error::EndOfBuffer)?;
96        let end = start.checked_add(len).ok_or(Error::EndOfBuffer)?;
97        let buf = *input.get_ref();
98        if buf.len() < end {
99            return Err(Error::EndOfBuffer);
100        }
101        input.set_position(end as u64);
102        Ok(Self(buf[start..end].to_vec()))
103    }
104}
105
106impl From<Vec<u8>> for Bytes {
107    fn from(value: Vec<u8>) -> Self {
108        Self(value)
109    }
110}
111
112impl From<&[u8]> for Bytes {
113    fn from(value: &[u8]) -> Self {
114        Self(value.to_vec())
115    }
116}
117
118impl From<Bytes> for Vec<u8> {
119    fn from(value: Bytes) -> Self {
120        value.0
121    }
122}
123
124impl AsRef<[u8]> for Bytes {
125    fn as_ref(&self) -> &[u8] {
126        &self.0
127    }
128}
129
130impl Deref for Bytes {
131    type Target = [u8];
132
133    fn deref(&self) -> &Self::Target {
134        &self.0
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141
142    /// The encoding is pinned as an absolute value, not compared against another encoder: a
143    /// four-byte big-endian length followed by the raw payload. An encoder using a narrower
144    /// prefix, or little-endian, produces different bytes here.
145    #[test]
146    fn streams_a_u32_big_endian_length_prefix_then_the_raw_payload() {
147        let bytes = Bytes::new(vec![0xab, 0xcd, 0xef]);
148        assert_eq!(
149            bytes.to_bytes().expect("encode"),
150            vec![0x00, 0x00, 0x00, 0x03, 0xab, 0xcd, 0xef]
151        );
152    }
153
154    /// A length that no narrower prefix could express, so a `u8` or `u16` prefix masquerading as
155    /// a `u32` cannot pass. 300 needs two bytes; the prefix must still occupy four.
156    #[test]
157    fn a_length_beyond_one_byte_still_occupies_four_prefix_bytes() {
158        let encoded = Bytes::new(vec![7u8; 300]).to_bytes().expect("encode");
159        assert_eq!(&encoded[..4], &[0x00, 0x00, 0x01, 0x2c]);
160        assert_eq!(encoded.len(), 4 + 300);
161    }
162
163    #[test]
164    fn empty_payload_streams_as_a_bare_zero_length() {
165        assert_eq!(
166            Bytes::default().to_bytes().expect("encode"),
167            vec![0x00, 0x00, 0x00, 0x00]
168        );
169    }
170
171    #[test]
172    fn round_trips_through_parse() {
173        let original = Bytes::new((0..300).map(|i| (i % 251) as u8).collect());
174        let decoded = Bytes::from_bytes(&original.to_bytes().expect("encode")).expect("decode");
175        assert_eq!(decoded, original);
176    }
177
178    /// A length prefix that promises more bytes than the buffer holds must be an error, not a
179    /// panic and not a short read — the prefix is peer-controlled.
180    #[test]
181    fn a_length_prefix_longer_than_the_buffer_is_rejected() {
182        let truncated = [0x00, 0x00, 0x00, 0x08, 0xab, 0xcd];
183        assert!(Bytes::from_bytes(&truncated).is_err());
184    }
185
186    /// `u32::MAX` as a length must not overflow the cursor arithmetic on any pointer width.
187    #[test]
188    fn a_maximal_length_prefix_errors_rather_than_overflowing() {
189        let hostile = [0xff, 0xff, 0xff, 0xff, 0x00];
190        assert!(Bytes::from_bytes(&hostile).is_err());
191    }
192
193    #[test]
194    fn renders_as_hex_in_both_display_and_debug() {
195        let bytes = Bytes::new(vec![0x00, 0x0f, 0xff]);
196        assert_eq!(bytes.to_string(), "000fff");
197        assert_eq!(format!("{bytes:?}"), "Bytes(000fff)");
198    }
199
200    #[test]
201    fn converts_to_and_from_the_shapes_callers_actually_hold() {
202        let from_vec: Bytes = vec![1u8, 2, 3].into();
203        let from_slice: Bytes = [1u8, 2, 3].as_slice().into();
204        assert_eq!(from_vec, from_slice);
205        assert_eq!(from_vec.as_ref(), &[1, 2, 3]);
206        assert_eq!(&*from_slice, &[1, 2, 3]);
207        assert_eq!(Vec::<u8>::from(from_vec.clone()), vec![1, 2, 3]);
208        assert_eq!(from_vec.into_inner(), vec![1, 2, 3]);
209    }
210
211    #[test]
212    fn reports_its_own_length_and_emptiness() {
213        assert!(Bytes::default().is_empty());
214        assert_eq!(Bytes::default().len(), 0);
215        assert!(!Bytes::new(vec![1]).is_empty());
216        assert_eq!(Bytes::new(vec![1, 2]).len(), 2);
217    }
218}