@@ -4,6 +4,7 @@ use crate::message::ReaderOptions;
44use crate :: message:: ReaderSegments ;
55use crate :: private:: units:: BYTES_PER_WORD ;
66use crate :: { Error , ErrorKind , Result } ;
7+ use core:: ops:: Deref ;
78
89use super :: SEGMENTS_COUNT_LIMIT ;
910
@@ -168,6 +169,172 @@ impl<'b> ReaderSegments for NoAllocSliceSegments<'b> {
168169 }
169170}
170171
172+ enum NoAllocBufferSegmentType {
173+ SingleSegment ( usize , usize ) ,
174+ MultipleSegments ,
175+ }
176+
177+ /// Segments read from a buffer, useful for when you have the message in a buffer and don't want the
178+ /// extra copy of `read_message`.
179+ ///
180+ /// `NoAllocBufferSegments` is similar to [`crate::serialize::BufferSegments`] but optimized for
181+ /// low memory embedded environment. It does not do heap allocations.
182+ ///
183+ /// # Performance considerations
184+ ///
185+ /// Due to lack of heap allocations, `NoAllocBufferSegments` does not cache segments offset and
186+ /// length and has to parse message header every time `NoAllocBufferSegments::get_segment` is called.
187+ /// The parsing has O(N) complexity where N is total number of segments in the message.
188+ /// `NoAllocBufferSegments` has optimization for single segment messages: if message has only one
189+ /// segment, it will be parsed only once during creation and no parsing will be required on `get_segment` calls
190+ pub struct NoAllocBufferSegments < T > {
191+ buffer : T ,
192+ segment_type : NoAllocBufferSegmentType ,
193+ }
194+
195+ impl < T : Deref < Target = [ u8 ] > > NoAllocBufferSegments < T > {
196+ /// Reads a serialized message (including a segment table) from a buffer and takes ownership, without copying.
197+ /// The buffer is allowed to extend beyond the end of the message.
198+ ///
199+ /// ALIGNMENT: If the "unaligned" feature is enabled, then there are no alignment requirements on `buffer`.
200+ /// Otherwise, `buffer` must be 8-byte aligned (attempts to read the message will trigger errors).
201+ pub fn try_new ( buffer : T , options : ReaderOptions ) -> Result < Self > {
202+ let mut remaining = & * buffer;
203+
204+ verify_alignment ( remaining. as_ptr ( ) ) ?;
205+
206+ let segments_count = u32_to_segments_count ( read_u32_le ( & mut remaining) ?) ?;
207+
208+ if segments_count >= SEGMENTS_COUNT_LIMIT {
209+ return Err ( Error :: from_kind ( ErrorKind :: InvalidNumberOfSegments (
210+ segments_count,
211+ ) ) ) ;
212+ }
213+
214+ let mut total_segments_length_bytes = 0_usize ;
215+
216+ for _ in 0 ..segments_count {
217+ let segment_length_in_bytes =
218+ u32_to_segment_length_bytes ( read_u32_le ( & mut remaining) ?) ?;
219+
220+ total_segments_length_bytes = total_segments_length_bytes
221+ . checked_add ( segment_length_in_bytes)
222+ . ok_or_else ( || Error :: from_kind ( ErrorKind :: MessageSizeOverflow ) ) ?;
223+ }
224+
225+ // Don't accept a message which the receiver couldn't possibly traverse without hitting the
226+ // traversal limit. Without this check, a malicious client could transmit a very large segment
227+ // size to make the receiver allocate excessive space and possibly crash.
228+ if let Some ( limit) = options. traversal_limit_in_words {
229+ let total_segments_length_words = total_segments_length_bytes / 8 ;
230+ if total_segments_length_words > limit {
231+ return Err ( Error :: from_kind ( ErrorKind :: MessageTooLarge (
232+ total_segments_length_words,
233+ ) ) ) ;
234+ }
235+ }
236+
237+ // If number of segments is even, header length will not be aligned by 8, we need to consume
238+ // padding from the remainder of the message
239+ if segments_count % 2 == 0 {
240+ let _padding = read_u32_le ( & mut remaining) ?;
241+ }
242+
243+ let expected_data_offset = calculate_data_offset ( segments_count)
244+ . ok_or_else ( || Error :: from_kind ( ErrorKind :: MessageSizeOverflow ) ) ?;
245+
246+ let consumed_bytes = buffer. len ( ) - remaining. len ( ) ;
247+
248+ assert_eq ! (
249+ expected_data_offset, consumed_bytes,
250+ "Expected header size and actual header size must match, otherwise we have a bug in this code"
251+ ) ;
252+
253+ // If data section of the message is smaller than calculated total segments length, the message
254+ // is malformed. It looks like it's ok to have extra bytes in the end, according to
255+ // of `SliceSegments` implementation.
256+ if remaining. len ( ) < total_segments_length_bytes {
257+ return Err ( Error :: from_kind ( ErrorKind :: MessageEndsPrematurely (
258+ total_segments_length_bytes / BYTES_PER_WORD ,
259+ remaining. len ( ) / BYTES_PER_WORD ,
260+ ) ) ) ;
261+ }
262+
263+ let message_length = expected_data_offset + total_segments_length_bytes;
264+
265+ if segments_count == 1 {
266+ Ok ( Self {
267+ buffer,
268+ segment_type : NoAllocBufferSegmentType :: SingleSegment (
269+ expected_data_offset,
270+ message_length,
271+ ) ,
272+ } )
273+ } else {
274+ Ok ( Self {
275+ buffer,
276+ segment_type : NoAllocBufferSegmentType :: MultipleSegments ,
277+ } )
278+ }
279+ }
280+ }
281+
282+ impl < T : Deref < Target = [ u8 ] > > ReaderSegments for NoAllocBufferSegments < T > {
283+ fn get_segment ( & self , idx : u32 ) -> Option < & [ u8 ] > {
284+ // panic safety: we are doing a lot of `unwrap` here. We assume that underlying message slice
285+ // holds valid capnp message - we already verified slice in NoAllocBufferSegments::try_new,
286+ // so these unwraps are not expected to panic unless we have bug in the code.
287+
288+ let idx: usize = idx. try_into ( ) . unwrap ( ) ;
289+
290+ match self . segment_type {
291+ NoAllocBufferSegmentType :: SingleSegment ( start, end) => {
292+ if idx == 0 {
293+ Some ( & self . buffer [ start..end] )
294+ } else {
295+ None
296+ }
297+ }
298+ NoAllocBufferSegmentType :: MultipleSegments => {
299+ let mut buf = & * self . buffer ;
300+
301+ let segments_count = u32_to_segments_count ( read_u32_le ( & mut buf) . unwrap ( ) ) . unwrap ( ) ;
302+
303+ if idx >= segments_count {
304+ return None ;
305+ }
306+
307+ let mut segment_offset = calculate_data_offset ( segments_count) . unwrap ( ) ;
308+
309+ for _ in 0 ..idx {
310+ segment_offset = segment_offset
311+ . checked_add (
312+ u32_to_segment_length_bytes ( read_u32_le ( & mut buf) . unwrap ( ) ) . unwrap ( ) ,
313+ )
314+ . unwrap ( ) ;
315+ }
316+
317+ let segment_length =
318+ u32_to_segment_length_bytes ( read_u32_le ( & mut buf) . unwrap ( ) ) . unwrap ( ) ;
319+
320+ Some ( & self . buffer [ segment_offset..( segment_offset + segment_length) ] )
321+ }
322+ }
323+ }
324+
325+ fn len ( & self ) -> usize {
326+ // panic safety: we are doing a lot of `unwrap` here. We assume that underlying message slice
327+ // holds valid capnp message - we already verified slice in NoAllocBufferSegments::try_new
328+
329+ match self . segment_type {
330+ NoAllocBufferSegmentType :: SingleSegment { .. } => 1 ,
331+ NoAllocBufferSegmentType :: MultipleSegments => {
332+ u32_to_segments_count ( read_u32_le ( & mut & * self . buffer ) . unwrap ( ) ) . unwrap ( )
333+ }
334+ }
335+ }
336+ }
337+
171338/// Verifies whether pointer meets alignment requirements
172339///
173340/// If crate is compiled with "unaligned" feature, then this function does nothing since
0 commit comments