capnp/serialize/
no_alloc_buffer_segments.rs

1use crate::message::ReaderOptions;
2use crate::message::ReaderSegments;
3use crate::private::units::BYTES_PER_WORD;
4use crate::{Error, ErrorKind, Result};
5
6use super::SEGMENTS_COUNT_LIMIT;
7
8const U32_LEN_IN_BYTES: usize = core::mem::size_of::<u32>();
9
10/// Information about a message read from its segment table.
11pub struct NoAllocSegmentTableInfo {
12    /// The number of segments in the message.
13    pub segments_count: usize,
14
15    /// The number of bytes in the segment table.
16    pub segment_table_length_bytes: usize,
17
18    /// The total number of bytes in all segments of the message.
19    pub total_segments_length_bytes: usize,
20}
21
22fn read_segment_table(slice: &[u8], options: ReaderOptions) -> Result<NoAllocSegmentTableInfo> {
23    let mut remaining = slice;
24
25    verify_alignment(remaining.as_ptr())?;
26
27    let segments_count = u32_to_segments_count(read_u32_le(&mut remaining)?)?;
28
29    if segments_count >= SEGMENTS_COUNT_LIMIT {
30        return Err(Error::from_kind(ErrorKind::InvalidNumberOfSegments(
31            segments_count,
32        )));
33    }
34
35    let mut total_segments_length_bytes = 0_usize;
36
37    for _ in 0..segments_count {
38        let segment_length_in_bytes = u32_to_segment_length_bytes(read_u32_le(&mut remaining)?)?;
39
40        total_segments_length_bytes = total_segments_length_bytes
41            .checked_add(segment_length_in_bytes)
42            .ok_or_else(|| Error::from_kind(ErrorKind::MessageSizeOverflow))?;
43    }
44
45    // Don't accept a message which the receiver couldn't possibly traverse without hitting the
46    // traversal limit. Without this check, a malicious client could transmit a very large segment
47    // size to make the receiver allocate excessive space and possibly crash.
48    if let Some(limit) = options.traversal_limit_in_words {
49        let total_segments_length_words = total_segments_length_bytes / 8;
50        if total_segments_length_words > limit {
51            return Err(Error::from_kind(ErrorKind::MessageTooLarge(
52                total_segments_length_words,
53            )));
54        }
55    }
56
57    // If number of segments is even, header length will not be aligned by 8, we need to consume
58    // padding from the remainder of the message
59    if segments_count % 2 == 0 {
60        let _padding = read_u32_le(&mut remaining)?;
61    }
62
63    let expected_data_offset = calculate_data_offset(segments_count)
64        .ok_or_else(|| Error::from_kind(ErrorKind::MessageSizeOverflow))?;
65
66    let consumed_bytes = slice.len() - remaining.len();
67
68    assert_eq!(
69        expected_data_offset, consumed_bytes,
70        "Expected header size and actual header size must match, otherwise we have a bug in this code"
71    );
72
73    // If data section of the message is smaller than calculated total segments length, the message
74    // is malformed. It looks like it's ok to have extra bytes in the end, according to
75    // of `SliceSegments` implementation.
76    if remaining.len() < total_segments_length_bytes {
77        return Err(Error::from_kind(ErrorKind::MessageEndsPrematurely(
78            total_segments_length_bytes / BYTES_PER_WORD,
79            remaining.len() / BYTES_PER_WORD,
80        )));
81    }
82
83    Ok(NoAllocSegmentTableInfo {
84        segments_count,
85        segment_table_length_bytes: expected_data_offset,
86        total_segments_length_bytes,
87    })
88}
89
90/// Alias for the common case where the buffer in `NoAllocBufferSegments` is a u8 slice.
91pub type NoAllocSliceSegments<'b> = NoAllocBufferSegments<&'b [u8]>;
92
93enum NoAllocBufferSegmentType {
94    /// The buffer contains a single segment, with length in bytes given by
95    /// the value of the `usize` parameter. The segment starts at byte index
96    /// 8 of the buffer.
97    SingleSegment(usize),
98
99    /// The buffer contains multiple segments. In this case, the segment table
100    /// needs to be re-parsed on each call to `get_segment()`.
101    MultipleSegments,
102}
103
104/// `NoAllocBufferSegments` is similar to [`crate::serialize::BufferSegments`] but optimized for
105/// low memory embedded environment. It does not do heap allocations.
106///
107/// # Performance considerations
108///
109/// Due to lack of heap allocations, `NoAllocBufferSegments` does not cache segments offset and
110/// length and has to parse message header every time `NoAllocBufferSegments::get_segment` is called.
111/// The parsing has O(N) complexity where N is total number of segments in the message.
112/// `NoAllocBufferSegments` has optimization for single segment messages: if message has only one
113/// segment, it will be parsed only once during creation and no parsing will be required on `get_segment` calls
114pub struct NoAllocBufferSegments<T> {
115    buffer: T,
116    segment_type: NoAllocBufferSegmentType,
117}
118
119impl<T> NoAllocBufferSegments<T> {
120    /// Constructs a NoAllocBufferSegments from a buffer and a [`NoAllocSegmentTableInfo`].
121    /// This method is used internally by [`NoAllocBufferSegments::from_slice()`]
122    /// and [`NoAllocBufferSegments::from_buffer()`]. It has been made public to allow for
123    /// situations where the segment table is read by nonstandard means.
124    pub fn from_segment_table_info(buffer: T, info: NoAllocSegmentTableInfo) -> Self {
125        if info.segments_count == 1 {
126            Self {
127                buffer,
128                segment_type: NoAllocBufferSegmentType::SingleSegment(
129                    info.total_segments_length_bytes,
130                ),
131            }
132        } else {
133            Self {
134                buffer,
135                segment_type: NoAllocBufferSegmentType::MultipleSegments,
136            }
137        }
138    }
139}
140
141impl<'b> NoAllocBufferSegments<&'b [u8]> {
142    /// Reads a serialized message (including a segment table) from a buffer and takes ownership, without copying.
143    /// The buffer is allowed to extend beyond the end of the message. On success, updates `slice` to point
144    /// to the remaining bytes beyond the end of the message.
145    ///
146    /// ALIGNMENT: If the "unaligned" feature is enabled, then there are no alignment requirements on `slice`.
147    /// Otherwise, `slice` must be 8-byte aligned (attempts to read the message will trigger errors).
148    pub fn from_slice(slice: &mut &'b [u8], options: ReaderOptions) -> Result<Self> {
149        let segment_table_info = read_segment_table(slice, options)?;
150
151        let message_length = segment_table_info.segment_table_length_bytes
152            + segment_table_info.total_segments_length_bytes;
153
154        let message = &slice[..message_length];
155        *slice = &slice[message_length..];
156
157        Ok(Self::from_segment_table_info(message, segment_table_info))
158    }
159}
160
161impl<T: AsRef<[u8]>> NoAllocBufferSegments<T> {
162    /// Reads a serialized message (including a segment table) from a buffer and takes ownership, without copying.
163    /// The buffer is allowed to extend beyond the end of the message.
164    ///
165    /// ALIGNMENT: If the "unaligned" feature is enabled, then there are no alignment requirements on `buffer`.
166    /// Otherwise, `buffer` must be 8-byte aligned (attempts to read the message will trigger errors).
167    pub fn from_buffer(buffer: T, options: ReaderOptions) -> Result<Self> {
168        let segment_table_info = read_segment_table(buffer.as_ref(), options)?;
169        Ok(Self::from_segment_table_info(buffer, segment_table_info))
170    }
171}
172
173impl<T: AsRef<[u8]>> ReaderSegments for NoAllocBufferSegments<T> {
174    fn get_segment(&self, idx: u32) -> Option<&[u8]> {
175        // panic safety: we are doing a lot of `unwrap` here. We assume that underlying message slice
176        // holds valid capnp message - we already verified slice in read_segment_table(),
177        // so these unwraps are not expected to panic unless we have bug in the code.
178
179        let idx: usize = idx.try_into().unwrap();
180
181        match self.segment_type {
182            NoAllocBufferSegmentType::SingleSegment(length_bytes) => {
183                if idx == 0 {
184                    Some(&self.buffer.as_ref()[8..8 + length_bytes])
185                } else {
186                    None
187                }
188            }
189            NoAllocBufferSegmentType::MultipleSegments => {
190                let mut buf = self.buffer.as_ref();
191
192                let segments_count = u32_to_segments_count(read_u32_le(&mut buf).unwrap()).unwrap();
193
194                if idx >= segments_count {
195                    return None;
196                }
197
198                let mut segment_offset = calculate_data_offset(segments_count).unwrap();
199
200                for _ in 0..idx {
201                    segment_offset = segment_offset
202                        .checked_add(
203                            u32_to_segment_length_bytes(read_u32_le(&mut buf).unwrap()).unwrap(),
204                        )
205                        .unwrap();
206                }
207
208                let segment_length =
209                    u32_to_segment_length_bytes(read_u32_le(&mut buf).unwrap()).unwrap();
210
211                Some(&self.buffer.as_ref()[segment_offset..(segment_offset + segment_length)])
212            }
213        }
214    }
215
216    fn len(&self) -> usize {
217        // panic safety: we are doing a lot of `unwrap` here. We assume that underlying message slice
218        // holds valid capnp message - we already verified slice in read_segment_table(),
219
220        match self.segment_type {
221            NoAllocBufferSegmentType::SingleSegment { .. } => 1,
222            NoAllocBufferSegmentType::MultipleSegments => {
223                u32_to_segments_count(read_u32_le(&mut self.buffer.as_ref()).unwrap()).unwrap()
224            }
225        }
226    }
227}
228
229/// Verifies whether pointer meets alignment requirements
230///
231/// If crate is compiled with "unaligned" feature, then this function does nothing since
232/// there are no alignment requirements in this mode.
233///
234/// If crate was not compiled with "unaligned" feature, it will verify that pointer is aligned
235/// by WORD boundary.
236fn verify_alignment(ptr: *const u8) -> Result<()> {
237    if cfg!(feature = "unaligned") {
238        return Ok(());
239    }
240
241    if ptr.align_offset(BYTES_PER_WORD) == 0 {
242        Ok(())
243    } else {
244        Err(Error::from_kind(
245            ErrorKind::MessageNotAlignedBy8BytesBoundary,
246        ))
247    }
248}
249
250/// Reads u32 little endian value from the front of the slice and truncates processed bytes
251/// Returns Error if there are not enough bytes to read u32
252fn read_u32_le(slice: &mut &[u8]) -> Result<u32> {
253    if slice.len() < U32_LEN_IN_BYTES {
254        return Err(Error::from_kind(ErrorKind::MessageEndsPrematurely(
255            U32_LEN_IN_BYTES,
256            slice.len(),
257        )));
258    }
259
260    // Panic safety: we just confirmed that `slice` has at least `U32_LEN_IN_BYTES` so nothing
261    // here should panic
262    let u32_buf: [u8; U32_LEN_IN_BYTES] = slice[..U32_LEN_IN_BYTES].try_into().unwrap();
263    *slice = &slice[U32_LEN_IN_BYTES..];
264
265    Ok(u32::from_le_bytes(u32_buf))
266}
267
268/// Converts 32 bit value which represents encoded segments count in header to usize segment count
269fn u32_to_segments_count(val: u32) -> Result<usize> {
270    // This conversion can fail on 8 or 16 bit machines.
271    let result: Option<usize> = val.try_into().ok();
272
273    // According to encoding schema, segments count is encoded as (count - 1), where 0 means one
274    // segment, 1 - two segments and so on, so we need to add +1 to value read from the stream.
275    // We need to do +1 to value read from the stream.
276    let result = result.and_then(|v: usize| v.checked_add(1));
277
278    result.ok_or_else(|| Error::from_kind(ErrorKind::FourByteLengthTooBigForUSize))
279}
280
281/// Converts 32 bit value which represents encoded segment length to usize segment length in bytes
282fn u32_to_segment_length_bytes(val: u32) -> Result<usize> {
283    // This conversion can fail on 8 or 16 bit machines.
284    let length_in_words: Option<usize> = val.try_into().ok();
285
286    let length_in_bytes = length_in_words.and_then(|l| l.checked_mul(BYTES_PER_WORD));
287
288    length_in_bytes.ok_or_else(|| Error::from_kind(ErrorKind::FourByteSegmentLengthTooBigForUSize))
289}
290
291/// Calculates expected offset of the message data (beginning of first segment)
292/// in the capnp message.
293/// Message data comes right after message header and potential padding
294///
295/// Returns None if it's impossible to calculate offset without arithmetic overflow of usize or
296/// if segments count is invalid
297fn calculate_data_offset(segments_count: usize) -> Option<usize> {
298    // Message data goes right after message header.
299    // Message header has following format:
300    //
301    // Segment count (u32)
302    // Segments length (u32 per each segment)
303    // Padding to align header size by 8 bytes (it will be either 0 bytes or 4 bytes)
304
305    // It should be impossible to have properly encoded message with 0 segments
306    if segments_count == 0 {
307        return None;
308    }
309
310    let mut data_offset = 0_usize;
311
312    {
313        // 4 bytes encoded segments count
314        let segments_count_len = U32_LEN_IN_BYTES;
315        data_offset = data_offset.checked_add(segments_count_len)?;
316    }
317
318    {
319        // 4 bytes per each segment
320        let segments_lengt_len = segments_count.checked_mul(U32_LEN_IN_BYTES)?;
321        data_offset = data_offset.checked_add(segments_lengt_len)?;
322    }
323
324    // Message data must be aligned by 8 bytes. If there was even number of segments, then
325    // header size will not be aligned by 8, in this case we have to add 4 byte padding to make
326    // data offset aligned by 8.
327    let padding_len = match data_offset % BYTES_PER_WORD {
328        0 => 0,
329        4 => 4,
330        _ => unreachable!(
331            "Mis-alignment by anything other than 4 should be impossible, this is a bug"
332        ),
333    };
334
335    data_offset = data_offset.checked_add(padding_len)?;
336
337    // It's a sanity check to ensure that message offset has correct alignment
338    assert_eq!(
339        data_offset % BYTES_PER_WORD,
340        0,
341        "data_offset after adding panic must be aligned by 8. \
342            If it's not, it's a bug"
343    );
344
345    Some(data_offset)
346}
347
348#[cfg(test)]
349mod tests {
350    #[cfg(feature = "alloc")]
351    use quickcheck::{quickcheck, TestResult};
352
353    use super::calculate_data_offset;
354    #[cfg(feature = "alloc")]
355    use crate::{
356        message::{ReaderOptions, ReaderSegments},
357        serialize, word, Word,
358    };
359
360    #[cfg(feature = "alloc")]
361    use crate::OutputSegments;
362
363    use super::{
364        read_u32_le, u32_to_segment_length_bytes, u32_to_segments_count, verify_alignment,
365    };
366    #[cfg(feature = "alloc")]
367    use super::{NoAllocBufferSegmentType, NoAllocBufferSegments, NoAllocSliceSegments};
368
369    #[repr(align(8))]
370    struct Aligned([u8; 8]);
371
372    #[cfg(feature = "unaligned")]
373    #[test]
374    fn test_verify_alignment_unaligned_mode() {
375        // To run this test do
376        // `% cargo test --features unaligned`
377
378        // make sure there is no padding
379        assert_eq!(core::mem::size_of::<Aligned>(), 8);
380
381        let aligned = Aligned([0; 8]);
382
383        // no alignment requirements in "unaligned" mode
384        for idx in 0..8 {
385            verify_alignment(unsafe { aligned.0.as_ptr().add(idx) }).unwrap();
386        }
387    }
388
389    #[cfg(not(feature = "unaligned"))]
390    #[test]
391    fn test_verify_alignment() {
392        // make sure there is no padding
393        assert_eq!(core::mem::size_of::<Aligned>(), 8);
394
395        let aligned = Aligned([0; 8]);
396
397        verify_alignment(aligned.0.as_ptr()).unwrap();
398        for idx in 1..8 {
399            verify_alignment(unsafe { aligned.0.as_ptr().add(idx) }).unwrap_err();
400        }
401    }
402
403    #[test]
404    fn test_read_u32_le() {
405        let buffer = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08];
406        let mut buffer_remaining = &buffer[..];
407
408        assert_eq!(read_u32_le(&mut buffer_remaining).unwrap(), 0x04030201);
409        assert_eq!(buffer_remaining, &buffer[4..]);
410    }
411
412    #[test]
413    fn test_read_u32_le_truncated() {
414        let buffer = [0x01, 0x02, 0x03];
415        let mut buffer_remaining = &buffer[..];
416
417        read_u32_le(&mut buffer_remaining).unwrap_err();
418        assert_eq!(buffer_remaining, &buffer[..]);
419    }
420
421    #[test]
422    fn test_u32_to_segments_count() {
423        assert_eq!(u32_to_segments_count(0).unwrap(), 1);
424        assert_eq!(u32_to_segments_count(10).unwrap(), 11);
425        // There is no way to reproduce "negative" case on 64 bit machine
426    }
427
428    #[test]
429    fn test_u32_to_segment_length_bytes() {
430        assert_eq!(u32_to_segment_length_bytes(0).unwrap(), 0);
431        assert_eq!(u32_to_segment_length_bytes(123).unwrap(), 123 * 8);
432    }
433
434    #[test]
435    fn test_calculate_data_offset_no_padding() {
436        assert_eq!(calculate_data_offset(0), None);
437
438        assert_eq!(calculate_data_offset(1), Some(8));
439
440        assert_eq!(calculate_data_offset(2), Some(16));
441        assert_eq!(calculate_data_offset(3), Some(16));
442
443        assert_eq!(calculate_data_offset(100), Some(408));
444        assert_eq!(calculate_data_offset(101), Some(408));
445    }
446
447    #[cfg(feature = "alloc")]
448    quickcheck! {
449        #[cfg_attr(miri, ignore)] // miri takes a long time with quickcheck
450        fn test_no_alloc_buffer_segments_single_segment_optimization(
451            segment_0 : alloc::vec::Vec<Word>) -> TestResult
452        {
453            let words = &segment_0[..];
454            let bytes = Word::words_to_bytes(words);
455            let output_segments = OutputSegments::SingleSegment([bytes]);
456            let mut msg = vec![];
457
458            serialize::write_message_segments(&mut msg, &output_segments).unwrap();
459
460            let no_alloc_segments =
461                NoAllocSliceSegments::from_slice(&mut msg.as_slice(), ReaderOptions::new()).unwrap();
462
463            assert!(matches!(
464                no_alloc_segments,
465                NoAllocBufferSegments { buffer: _,
466                                        segment_type : NoAllocBufferSegmentType::SingleSegment { .. },
467                }
468            ));
469
470            assert_eq!(no_alloc_segments.len(), 1);
471            assert_eq!(no_alloc_segments.get_segment(0), Some(bytes));
472            assert_eq!(no_alloc_segments.get_segment(1), None);
473            TestResult::from_bool(true)
474        }
475
476        #[cfg_attr(miri, ignore)] // miri takes a long time with quickcheck
477        fn test_no_alloc_buffer_segments_multiple_segments(segments_vec: alloc::vec::Vec<alloc::vec::Vec<Word>>) -> TestResult {
478            if segments_vec.is_empty() { return TestResult::discard() };
479
480            let segments: alloc::vec::Vec<_> = segments_vec.iter().map(|s|
481                                                           Word::words_to_bytes(s.as_slice())).collect();
482
483            let output_segments = OutputSegments::MultiSegment(segments.clone());
484
485            let mut msg = vec![];
486
487            serialize::write_message_segments(&mut msg, &output_segments).unwrap();
488
489            let no_alloc_segments =
490                NoAllocSliceSegments::from_slice(&mut msg.as_slice(), ReaderOptions::new()).unwrap();
491
492            assert_eq!(no_alloc_segments.len(), segments.len());
493            for (i, segment) in segments.iter().enumerate() {
494                assert_eq!(no_alloc_segments.get_segment(i as u32), Some(*segment));
495            }
496
497            assert_eq!(
498                no_alloc_segments.get_segment(no_alloc_segments.len() as u32),
499                None
500            );
501            TestResult::from_bool(true)
502        }
503    }
504
505    #[cfg(feature = "alloc")]
506    #[test]
507    fn test_no_alloc_buffer_segments_message_postfix() {
508        let output_segments = OutputSegments::SingleSegment([&[1, 2, 3, 4, 5, 6, 7, 8]]);
509        let mut buf = Word::allocate_zeroed_vec(2);
510        serialize::write_message_segments(Word::words_to_bytes_mut(&mut buf), &output_segments)
511            .unwrap();
512        buf.push(word(11, 12, 13, 14, 15, 16, 0, 0));
513
514        let remaining = &mut Word::words_to_bytes(&buf);
515        NoAllocSliceSegments::from_slice(remaining, ReaderOptions::new()).unwrap();
516
517        // Confirm that slice pointer was advanced to data past first message
518        assert_eq!(*remaining, &[11, 12, 13, 14, 15, 16, 0, 0]);
519    }
520
521    #[cfg(feature = "alloc")]
522    #[test]
523    fn test_no_alloc_buffer_segments_message_invalid() {
524        let mut buf = vec![];
525
526        buf.extend([0, 2, 0, 0]); // 513 segments
527        buf.extend([0; 513 * 8]);
528        assert!(NoAllocSliceSegments::from_slice(&mut &buf[..], ReaderOptions::new()).is_err());
529        buf.clear();
530
531        buf.extend([0, 0, 0, 0]); // 1 segments
532        assert!(NoAllocSliceSegments::from_slice(&mut &buf[..], ReaderOptions::new()).is_err());
533        buf.clear();
534
535        buf.extend([0, 0, 0, 0]); // 1 segments
536        buf.extend([0; 3]);
537        assert!(NoAllocSliceSegments::from_slice(&mut &buf[..], ReaderOptions::new()).is_err());
538        buf.clear();
539
540        buf.extend([255, 255, 255, 255]); // 0 segments
541        assert!(NoAllocSliceSegments::from_slice(&mut &buf[..], ReaderOptions::new()).is_err());
542        buf.clear();
543    }
544
545    #[cfg(feature = "alloc")]
546    quickcheck! {
547        #[cfg_attr(miri, ignore)] // miri takes a long time with quickcheck
548        fn test_no_alloc_buffer_segments_message_truncated(segments_vec: alloc::vec::Vec<alloc::vec::Vec<Word>>) -> TestResult {
549            if segments_vec.is_empty() { return TestResult::discard() }
550
551            let segments: alloc::vec::Vec<_> = segments_vec.iter()
552                .map(|s| Word::words_to_bytes(s.as_slice())).collect();
553
554            let output_segments = OutputSegments::MultiSegment(segments.clone());
555
556            let mut msg = vec![];
557
558            serialize::write_message_segments(&mut msg, &output_segments).unwrap();
559
560            // Lop off the final element.
561            msg.pop().unwrap();
562
563            let no_alloc_segments =
564                NoAllocSliceSegments::from_slice(&mut msg.as_slice(), ReaderOptions::new());
565
566            assert!(no_alloc_segments.is_err());
567            TestResult::from_bool(true)
568        }
569
570        #[cfg_attr(miri, ignore)] // miri takes a long time with quickcheck
571        fn test_no_alloc_buffer_segments_message_options_limit(
572            segments_vec: alloc::vec::Vec<alloc::vec::Vec<Word>>) -> TestResult
573        {
574            let mut word_count = 0;
575            let segments: alloc::vec::Vec<_> = segments_vec.iter()
576                .map(|s| {
577                    let ws = Word::words_to_bytes(s.as_slice());
578                    word_count += s.len();
579                    ws
580                }).collect();
581            if word_count == 0 { return TestResult::discard() };
582
583            let output_segments = OutputSegments::MultiSegment(segments.clone());
584
585            let mut msg = vec![];
586
587            serialize::write_message_segments(&mut msg, &output_segments).unwrap();
588
589            let mut options = ReaderOptions::new();
590            options.traversal_limit_in_words(Some(word_count));
591
592            let _no_alloc_segments =
593                NoAllocSliceSegments::from_slice(&mut msg.as_slice(), options).unwrap();
594
595            let mut options = ReaderOptions::new();
596            options.traversal_limit_in_words(Some(word_count - 1));
597
598            let no_alloc_segments = NoAllocSliceSegments::from_slice(&mut msg.as_slice(), options);
599
600            assert!(no_alloc_segments.is_err());
601            TestResult::from_bool(true)
602        }
603
604        #[cfg_attr(miri, ignore)] // miri takes a long time with quickcheck
605        fn test_no_alloc_buffer_segments_bad_alignment(segment_0: alloc::vec::Vec<Word>) -> TestResult {
606            if segment_0.is_empty() { return TestResult::discard(); }
607            let output_segments = OutputSegments::SingleSegment([Word::words_to_bytes(&segment_0)]);
608
609            let mut msg = vec![];
610
611            serialize::write_message_segments(&mut msg, &output_segments).unwrap();
612            // mis-align buffer by 1 byte
613            msg.insert(0_usize, 0_u8);
614
615            let no_alloc_segments = NoAllocSliceSegments::from_slice(&mut &msg[1..], ReaderOptions::new());
616
617            if cfg!(feature = "unaligned") {
618                // If we build with "unaligned" feature, alignment requirements should not be enforced
619                no_alloc_segments.unwrap();
620            } else {
621                assert!(no_alloc_segments.is_err());
622            }
623            TestResult::from_bool(true)
624        }
625    }
626}