Skip to main content

plaza_wire/
payload.rs

1//! Carrying packed bytes without paying for them twice.
2//!
3//! A `Vec<u8>` field reaches a codec through `serialize_seq`, so every byte is
4//! re-encoded as its own integer: MessagePack spends two on anything above 127
5//! and [`crate::bit_codec`] spends a ten-bit varint. On the payload in
6//! `wire/tests/packing.rs` that costs 15502 bytes to carry 10396, giving back
7//! half of the packing saving.
8//!
9//! [`Payload`] is the fix and it is small enough that a dependency for it would
10//! cost more than the code. Wrap the packed bytes and the same payload travels
11//! in 10411, a fifteen-byte header over the raw layout.
12//!
13//! ```
14//! # use plaza_wire::Payload;
15//! # use serde::{Deserialize, Serialize};
16//! #[derive(Serialize, Deserialize)]
17//! enum Op {
18//!   Frame { tick: u64, entities: Payload },
19//! }
20//!
21//! let op = Op::Frame { tick: 7, entities: Payload::from(vec![200u8; 1000]) };
22//! ```
23//!
24//! Under a binary codec that has a byte-string type, that payload costs its own
25//! length plus a short header. As a `Vec<u8>` it would cost half again.
26
27use std::fmt;
28
29use serde::de::{self, Visitor};
30use serde::{Deserialize, Deserializer, Serialize, Serializer};
31
32/// Bytes that stay bytes on the wire.
33#[derive(Clone, Default, PartialEq, Eq, Hash)]
34pub struct Payload(pub Vec<u8>);
35
36impl Payload {
37  pub fn new() -> Self {
38    Self::default()
39  }
40
41  pub fn len(&self) -> usize {
42    self.0.len()
43  }
44
45  pub fn is_empty(&self) -> bool {
46    self.0.is_empty()
47  }
48
49  pub fn as_slice(&self) -> &[u8] {
50    &self.0
51  }
52
53  pub fn into_inner(self) -> Vec<u8> {
54    self.0
55  }
56}
57
58impl From<Vec<u8>> for Payload {
59  fn from(bytes: Vec<u8>) -> Self {
60    Self(bytes)
61  }
62}
63
64impl From<Payload> for Vec<u8> {
65  fn from(payload: Payload) -> Self {
66    payload.0
67  }
68}
69
70impl AsRef<[u8]> for Payload {
71  fn as_ref(&self) -> &[u8] {
72    &self.0
73  }
74}
75
76impl std::ops::Deref for Payload {
77  type Target = [u8];
78
79  fn deref(&self) -> &[u8] {
80    &self.0
81  }
82}
83
84/// Prints the length rather than the bytes, because raw packed bytes are
85/// unreadable in a log line and the length is what a reader wants.
86impl fmt::Debug for Payload {
87  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88    write!(f, "Payload({} bytes)", self.0.len())
89  }
90}
91
92impl Serialize for Payload {
93  fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
94    serializer.serialize_bytes(&self.0)
95  }
96}
97
98impl<'de> Deserialize<'de> for Payload {
99  fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
100    struct Raw;
101
102    impl<'de> Visitor<'de> for Raw {
103      type Value = Vec<u8>;
104
105      fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106        f.write_str("bytes")
107      }
108
109      fn visit_bytes<E: de::Error>(self, v: &[u8]) -> Result<Vec<u8>, E> {
110        Ok(v.to_vec())
111      }
112
113      fn visit_byte_buf<E: de::Error>(self, v: Vec<u8>) -> Result<Vec<u8>, E> {
114        Ok(v)
115      }
116
117      /// JSON has no byte string, so a text codec round-trips through an array
118      /// and this is the arm that catches it coming back.
119      fn visit_seq<A: de::SeqAccess<'de>>(self, mut seq: A) -> Result<Vec<u8>, A::Error> {
120        let mut out = Vec::with_capacity(seq.size_hint().unwrap_or(0));
121        while let Some(byte) = seq.next_element::<u8>()? {
122          out.push(byte);
123        }
124        Ok(out)
125      }
126    }
127
128    deserializer.deserialize_byte_buf(Raw).map(Payload)
129  }
130}
131
132#[cfg(test)]
133mod tests {
134  use super::*;
135
136  #[cfg(feature = "msgpack")]
137  #[test]
138  fn bytes_cost_a_header_rather_than_a_second_encoding() {
139    use crate::{MsgPackCodec, WireCodec};
140
141    // Values above 127 are the ones a sequence encoding pays double for.
142    let raw = vec![200u8; 4096];
143    let as_payload = MsgPackCodec.encode(&Payload::from(raw.clone())).unwrap();
144    let as_vec = MsgPackCodec.encode(&raw).unwrap();
145
146    assert!(as_payload.len() <= raw.len() + 8, "{} for {}", as_payload.len(), raw.len());
147    assert!(as_vec.len() > raw.len() * 3 / 2, "a Vec<u8> should visibly cost more");
148  }
149
150  #[cfg(feature = "msgpack")]
151  #[test]
152  fn it_round_trips() {
153    use crate::{MsgPackCodec, WireCodec};
154
155    let payload = Payload::from((0..=255u8).collect::<Vec<_>>());
156    let back: Payload = MsgPackCodec.decode(&MsgPackCodec.encode(&payload).unwrap()).unwrap();
157    assert_eq!(back, payload);
158  }
159
160  #[cfg(feature = "json")]
161  #[test]
162  fn a_text_codec_still_round_trips_through_an_array() {
163    use crate::{JsonCodec, WireCodec};
164
165    let payload = Payload::from(vec![1u8, 127, 128, 255]);
166    let back: Payload = JsonCodec.decode(&JsonCodec.encode(&payload).unwrap()).unwrap();
167    assert_eq!(back, payload);
168  }
169
170  #[test]
171  fn debug_prints_the_length_not_the_bytes() {
172    assert_eq!(format!("{:?}", Payload::from(vec![0u8; 900])), "Payload(900 bytes)");
173  }
174}