@@ -4,6 +4,7 @@ use crate::message::ReaderOptions;
4
4
use crate :: message:: ReaderSegments ;
5
5
use crate :: private:: units:: BYTES_PER_WORD ;
6
6
use crate :: { Error , ErrorKind , Result } ;
7
+ use core:: ops:: Deref ;
7
8
8
9
use super :: SEGMENTS_COUNT_LIMIT ;
9
10
@@ -168,6 +169,172 @@ impl<'b> ReaderSegments for NoAllocSliceSegments<'b> {
168
169
}
169
170
}
170
171
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
+
171
338
/// Verifies whether pointer meets alignment requirements
172
339
///
173
340
/// If crate is compiled with "unaligned" feature, then this function does nothing since
0 commit comments