/rust/registry/src/index.crates.io-1949cf8c6b5b557f/revision-0.30.0/src/optimised/envelope.rs
Line | Count | Source |
1 | | //! Tagged-value envelope encoders and decoders. |
2 | | //! |
3 | | //! These helpers are the runtime side of `optimised` codegen. The |
4 | | //! macro emits calls into them; user code reaches them only indirectly. |
5 | | |
6 | | use std::io::{Read, Write}; |
7 | | |
8 | | use crate::Error; |
9 | | use crate::optimised::tag::{SizeClass, Tag, read_tag, write_tag}; |
10 | | use crate::slice_reader::{BorrowedReader, advance_read}; |
11 | | |
12 | | /// Encode an inline value: just the tag byte, no payload. |
13 | | #[doc(hidden)] |
14 | | #[inline] |
15 | 0 | pub fn encode_inline<W: Write>(w: &mut W, variant_id: u8) -> Result<(), Error> { |
16 | 0 | write_tag(w, Tag::new(variant_id, SizeClass::Inline)) |
17 | 0 | } |
18 | | |
19 | | /// Encode a fixed-size value: tag byte plus the writer-supplied static-size payload. |
20 | | /// |
21 | | /// The macro emits a `debug_assert_eq!(__bytes_written, expected_size)` after the |
22 | | /// payload write so encoding mismatches surface in debug builds. |
23 | | #[doc(hidden)] |
24 | | #[inline] |
25 | 0 | pub fn encode_fixed<W: Write, F>(w: &mut W, variant_id: u8, body: F) -> Result<(), Error> |
26 | 0 | where |
27 | 0 | F: FnOnce(&mut W) -> Result<(), Error>, |
28 | | { |
29 | 0 | write_tag(w, Tag::new(variant_id, SizeClass::Fixed))?; |
30 | 0 | body(w) |
31 | 0 | } |
32 | | |
33 | | /// Encode a varlen value: tag byte, then `u32_le byte_length`, then payload. |
34 | | /// |
35 | | /// Implementation note: the body is written into a scratch `Vec<u8>` first so the |
36 | | /// length is known before the prefix is emitted. This trades one allocation per |
37 | | /// varlen value for a simple, single-pass macro expansion (no measure-then-write |
38 | | /// dance on the writer side, no in-place patching of the length prefix). |
39 | | #[doc(hidden)] |
40 | 0 | pub fn encode_varlen<W: Write, F>(w: &mut W, variant_id: u8, body: F) -> Result<(), Error> |
41 | 0 | where |
42 | 0 | F: FnOnce(&mut Vec<u8>) -> Result<(), Error>, |
43 | | { |
44 | 0 | write_tag(w, Tag::new(variant_id, SizeClass::Varlen))?; |
45 | 0 | let mut scratch: Vec<u8> = Vec::new(); |
46 | 0 | body(&mut scratch)?; |
47 | 0 | let len: u32 = scratch |
48 | 0 | .len() |
49 | 0 | .try_into() |
50 | 0 | .map_err(|_| Error::Serialize("optimised varlen payload exceeds u32::MAX bytes".into()))?; |
51 | 0 | w.write_all(&len.to_le_bytes()).map_err(Error::Io)?; |
52 | 0 | w.write_all(&scratch).map_err(Error::Io) |
53 | 0 | } |
54 | | |
55 | | /// Read just the tag of an optimised value, validating the size class. |
56 | | #[doc(hidden)] |
57 | | #[inline] |
58 | 0 | pub fn read_optimised_tag<R: Read>(r: &mut R) -> Result<(Tag, SizeClass), Error> { |
59 | 0 | let tag = read_tag(r)?; |
60 | 0 | let sc = tag.size_class()?; |
61 | 0 | Ok((tag, sc)) |
62 | 0 | } |
63 | | |
64 | | /// Read the `u32_le` byte length of a varlen value. The caller is expected to have |
65 | | /// already consumed the tag byte. |
66 | | #[doc(hidden)] |
67 | | #[inline] |
68 | 0 | pub fn read_varlen_len<R: Read>(r: &mut R) -> Result<u32, Error> { |
69 | 0 | let mut buf = [0u8; 4]; |
70 | 0 | r.read_exact(&mut buf).map_err(Error::Io)?; |
71 | 0 | Ok(u32::from_le_bytes(buf)) |
72 | 0 | } |
73 | | |
74 | | /// Read a varlen value's payload as a borrowed slice. |
75 | | /// |
76 | | /// Tag must have already been consumed. Returns the payload bytes and advances the |
77 | | /// reader past them. The returned slice's lifetime is tied to the reader's input. |
78 | | #[doc(hidden)] |
79 | | #[inline] |
80 | 0 | pub fn read_varlen_slice<R: BorrowedReader>(r: &mut R) -> Result<&[u8], Error> { |
81 | 0 | let len = read_varlen_len(r)? as usize; |
82 | 0 | crate::slice_reader::read_borrowed_bytes(r, len) |
83 | 0 | } |
84 | | |
85 | | /// Skip past a varlen value's payload (tag already consumed). Streaming-reader friendly. |
86 | | #[doc(hidden)] |
87 | | #[inline] |
88 | 0 | pub fn skip_varlen<R: Read>(r: &mut R) -> Result<(), Error> { |
89 | 0 | let len = read_varlen_len(r)? as usize; |
90 | 0 | advance_read(r, len) |
91 | 0 | } |
92 | | |
93 | | /// Skip past a varlen value's payload using a [`BorrowedReader`]; cheaper than `skip_varlen`. |
94 | | #[doc(hidden)] |
95 | | #[inline] |
96 | 0 | pub fn skip_varlen_borrowed<R: BorrowedReader>(r: &mut R) -> Result<(), Error> { |
97 | 0 | let len = read_varlen_len(r)? as usize; |
98 | 0 | r.advance(len) |
99 | 0 | } |
100 | | |
101 | | #[cfg(test)] |
102 | | mod tests { |
103 | | use super::*; |
104 | | use crate::slice_reader::SliceReader; |
105 | | |
106 | | #[test] |
107 | | fn inline_round_trip() { |
108 | | let mut buf = Vec::new(); |
109 | | encode_inline(&mut buf, 3).unwrap(); |
110 | | assert_eq!(buf.len(), 1); |
111 | | let mut r: &[u8] = &buf; |
112 | | let (tag, sc) = read_optimised_tag(&mut r).unwrap(); |
113 | | assert_eq!(tag.variant_id(), 3); |
114 | | assert_eq!(sc, SizeClass::Inline); |
115 | | assert_eq!(r.len(), 0); |
116 | | } |
117 | | |
118 | | #[test] |
119 | | fn fixed_round_trip() { |
120 | | let mut buf = Vec::new(); |
121 | | encode_fixed(&mut buf, 7, |w| { |
122 | | w.write_all(&42i64.to_le_bytes()).map_err(Error::Io)?; |
123 | | Ok(()) |
124 | | }) |
125 | | .unwrap(); |
126 | | assert_eq!(buf.len(), 1 + 8); |
127 | | let mut r: &[u8] = &buf; |
128 | | let (tag, sc) = read_optimised_tag(&mut r).unwrap(); |
129 | | assert_eq!(tag.variant_id(), 7); |
130 | | assert_eq!(sc, SizeClass::Fixed); |
131 | | let mut payload = [0u8; 8]; |
132 | | r.read_exact(&mut payload).unwrap(); |
133 | | assert_eq!(i64::from_le_bytes(payload), 42); |
134 | | } |
135 | | |
136 | | #[test] |
137 | | fn varlen_round_trip() { |
138 | | let mut buf = Vec::new(); |
139 | | encode_varlen(&mut buf, 12, |scratch| { |
140 | | scratch.extend_from_slice(b"hello world"); |
141 | | Ok(()) |
142 | | }) |
143 | | .unwrap(); |
144 | | assert_eq!(buf.len(), 1 + 4 + 11); |
145 | | let mut r = SliceReader::new(&buf); |
146 | | let (tag, sc) = read_optimised_tag(&mut r).unwrap(); |
147 | | assert_eq!(tag.variant_id(), 12); |
148 | | assert_eq!(sc, SizeClass::Varlen); |
149 | | let payload = read_varlen_slice(&mut r).unwrap(); |
150 | | assert_eq!(payload, b"hello world"); |
151 | | } |
152 | | |
153 | | #[test] |
154 | | fn skip_varlen_advances_full_length() { |
155 | | let mut buf = Vec::new(); |
156 | | encode_varlen(&mut buf, 1, |s| { |
157 | | s.extend_from_slice(&[0xAA; 100]); |
158 | | Ok(()) |
159 | | }) |
160 | | .unwrap(); |
161 | | let mut r: &[u8] = &buf; |
162 | | let (_, sc) = read_optimised_tag(&mut r).unwrap(); |
163 | | assert_eq!(sc, SizeClass::Varlen); |
164 | | skip_varlen(&mut r).unwrap(); |
165 | | assert_eq!(r.len(), 0); |
166 | | } |
167 | | |
168 | | #[test] |
169 | | fn skip_varlen_borrowed_matches_skip_varlen() { |
170 | | let mut buf = Vec::new(); |
171 | | encode_varlen(&mut buf, 1, |s| { |
172 | | s.extend_from_slice(&[0xAA; 100]); |
173 | | Ok(()) |
174 | | }) |
175 | | .unwrap(); |
176 | | let mut r = SliceReader::new(&buf); |
177 | | let (_, _) = read_optimised_tag(&mut r).unwrap(); |
178 | | skip_varlen_borrowed(&mut r).unwrap(); |
179 | | assert!(r.remaining().is_empty()); |
180 | | } |
181 | | |
182 | | #[test] |
183 | | fn read_optimised_tag_errors_on_reserved_size_class() { |
184 | | // Hand-craft a tag with size_class = 0b11. |
185 | | let bad = 0b0110_0000; |
186 | | let mut r: &[u8] = &[bad]; |
187 | | let err = read_optimised_tag(&mut r).unwrap_err(); |
188 | | assert!(matches!(err, Error::InvalidOptimisedTag(b) if b == bad)); |
189 | | } |
190 | | |
191 | | #[test] |
192 | | fn read_varlen_slice_overrun_errors() { |
193 | | // Tag + length = 100 but only 4 bytes of payload follow. |
194 | | let mut buf = Vec::new(); |
195 | | buf.push(Tag::new(0, SizeClass::Varlen).0); |
196 | | buf.extend_from_slice(&100u32.to_le_bytes()); |
197 | | buf.extend_from_slice(&[0u8; 4]); |
198 | | let mut r = SliceReader::new(&buf); |
199 | | let _ = read_optimised_tag(&mut r).unwrap(); |
200 | | assert!(read_varlen_slice(&mut r).is_err()); |
201 | | } |
202 | | } |