1use alloc::vec::Vec;
2use core::ops::{Deref, DerefMut, Range};
3use core::{fmt, slice};
4
5use crate::Protocol;
6use crate::enums::{ContentType, ProtocolVersion};
7use crate::error::{Error, InvalidMessage, PeerMisbehaved};
8use crate::msgs::{Codec, HEADER_SIZE, MAX_FRAGMENT_LEN, Reader, hex, read_record_header};
9
10#[expect(clippy::exhaustive_structs)]
12#[derive(Clone, Debug)]
13pub struct Record<P> {
14 pub typ: ContentType,
16 pub version: EncodableVersion,
18 pub payload: P,
20}
21
22impl<P> Record<P> {
23 pub fn new(typ: ContentType, version: EncodableVersion, payload: P) -> Self {
25 Self {
26 typ,
27 version,
28 payload,
29 }
30 }
31}
32
33impl<'a> Record<Payload<'a>> {
34 pub(crate) fn read(r: &mut Reader<'a>) -> Result<Self, RecordError> {
39 let (typ, version, len) = read_record_header(r)?;
40
41 let content = r
42 .take(len as usize)
43 .ok_or(RecordError::TooShortForLength)?;
44
45 Ok(Self {
46 typ,
47 version: EncodableVersion::Legacy(version),
48 payload: Payload::Borrowed(content),
49 })
50 }
51
52 pub fn borrow_outbound(&'a self) -> Record<OutboundPlain<'a>> {
54 Record {
55 typ: self.typ,
56 version: self.version,
57 payload: self.payload.bytes().into(),
58 }
59 }
60
61 pub fn into_owned(self) -> Self {
63 Self {
64 typ: self.typ,
65 version: self.version,
66 payload: self.payload.into_owned(),
67 }
68 }
69}
70
71impl Record<&'_ [u8]> {
72 pub(crate) fn is_valid_ccs(&self) -> bool {
78 self.typ == ContentType::ChangeCipherSpec && self.payload == [0x01]
79 }
80}
81
82impl<'a> Record<InboundOpaque<'a>> {
83 pub fn into_tls13_unpadded_record(mut self) -> Result<Record<&'a [u8]>, Error> {
89 let payload = &mut self.payload;
90
91 if self.typ != ContentType::ApplicationData {
92 return Err(PeerMisbehaved::IllegalTls13ContentType.into());
93 }
94
95 if payload.len() > MAX_FRAGMENT_LEN.get() + 1 {
96 return Err(Error::PeerSentOversizedRecord);
97 }
98
99 self.typ = unpad_tls13_payload(payload);
100 if self.typ == ContentType(0) {
101 return Err(PeerMisbehaved::IllegalTlsInnerPlaintext.into());
102 }
103
104 if self.typ == ContentType::ChangeCipherSpec {
109 return Err(PeerMisbehaved::IllegalMiddleboxChangeCipherSpec.into());
110 }
111
112 if payload.len() > MAX_FRAGMENT_LEN.get() {
113 return Err(Error::PeerSentOversizedRecord);
114 }
115
116 self.version = EncodableVersion::Legacy(ProtocolVersion::TLSv1_3);
117 Ok(self.into_plain_record())
118 }
119
120 pub fn into_plain_record_range(self, range: Range<usize>) -> Record<&'a [u8]> {
129 Record {
130 typ: self.typ,
131 version: self.version,
132 payload: &self.payload.into_inner()[range],
133 }
134 }
135
136 pub fn into_plain_record(self) -> Record<&'a [u8]> {
142 Record {
143 typ: self.typ,
144 version: self.version,
145 payload: self.payload.into_inner(),
146 }
147 }
148}
149
150impl Record<OutboundPlain<'_>> {
151 pub(crate) fn to_unencrypted_bytes(&self) -> Vec<u8> {
154 let len = self.payload.len();
155 debug_assert!(len <= usize::from(u16::MAX));
156 let mut buf = Vec::with_capacity(HEADER_SIZE + len);
157 self.encode_unencrypted(&mut buf);
158 buf
159 }
160
161 pub(crate) fn encode_unencrypted(&self, buf: &mut Vec<u8>) {
162 let len = self.payload.len();
163 debug_assert!(len <= usize::from(u16::MAX));
164 buf.extend_from_slice(&encode_record_header(self.typ, self.version, len as u16));
165 self.payload.copy_to_vec(buf);
166 }
167}
168
169pub fn encode_record_header(
173 typ: ContentType,
174 version: EncodableVersion,
175 len: u16,
176) -> [u8; HEADER_SIZE] {
177 let [version_hi, version_lo] = version.encode().to_array();
178 let [len_hi, len_lo] = len.to_be_bytes();
179 [typ.into(), version_hi, version_lo, len_hi, len_lo]
180}
181
182#[non_exhaustive]
187#[derive(Debug, Clone)]
188pub enum OutboundPlain<'a> {
189 Single(&'a [u8]),
193 Multiple {
195 chunks: &'a [&'a [u8]],
197 start: usize,
202 end: usize,
206 },
207}
208
209impl<'a> OutboundPlain<'a> {
210 pub fn new(chunks: &'a [&'a [u8]]) -> Self {
213 if chunks.len() == 1 {
214 Self::Single(chunks[0])
215 } else {
216 Self::Multiple {
217 chunks,
218 start: 0,
219 end: chunks
220 .iter()
221 .map(|chunk| chunk.len())
222 .sum(),
223 }
224 }
225 }
226
227 pub fn new_empty() -> Self {
229 Self::Single(&[])
230 }
231
232 pub fn to_vec(&self) -> Vec<u8> {
234 let mut vec = Vec::with_capacity(self.len());
235 self.copy_to_vec(&mut vec);
236 vec
237 }
238
239 pub fn copy_to_vec(&self, vec: &mut Vec<u8>) {
241 for chunk in self.chunks() {
242 vec.extend_from_slice(chunk);
243 }
244 }
245
246 pub fn chunks(&self) -> impl Iterator<Item = &'a [u8]> + '_ {
250 match self {
251 Self::Single(chunk) => Chunks::Single((!chunk.is_empty()).then_some(*chunk)),
252 Self::Multiple { chunks, start, end } => Chunks::Multiple {
253 chunks: chunks.iter(),
254 skip: *start,
255 remaining: end - start,
256 },
257 }
258 }
259
260 pub fn single_chunk(&self) -> Option<&'a [u8]> {
266 if let Self::Single(chunk) = self {
267 return Some(chunk);
268 }
269 if self.is_empty() {
270 return Some(&[]);
271 }
272 let mut chunks = self.chunks();
273 let chunk = chunks.next()?;
274 (chunk.len() == self.len() && chunks.next().is_none()).then_some(chunk)
275 }
276
277 pub(crate) fn split_at(&self, mid: usize) -> (Self, Self) {
280 let (chunks, start, end) = match *self {
281 Self::Multiple { chunks, start, end } => (chunks, start, end),
282 Self::Single(chunk) => {
283 let mid = Ord::min(mid, chunk.len());
284 return (Self::Single(&chunk[..mid]), Self::Single(&chunk[mid..]));
285 }
286 };
287
288 let mid = Ord::min(start + mid, end);
289 let mut skip = 0;
290 let mut consumed = 0;
291 for chunk in chunks {
292 if consumed + chunk.len() > mid {
293 break;
294 }
295 consumed += chunk.len();
296 skip += 1;
297 }
298
299 (
300 Self::Multiple {
301 chunks,
302 start,
303 end: mid,
304 },
305 Self::Multiple {
306 chunks: &chunks[skip..],
307 start: mid - consumed,
308 end: end - consumed,
309 },
310 )
311 }
312
313 pub(crate) fn is_empty(&self) -> bool {
315 self.len() == 0
316 }
317
318 #[expect(clippy::len_without_is_empty)]
320 pub fn len(&self) -> usize {
321 match self {
322 Self::Single(chunk) => chunk.len(),
323 Self::Multiple { start, end, .. } => end - start,
324 }
325 }
326}
327
328enum Chunks<'a> {
330 Single(Option<&'a [u8]>),
331 Multiple {
332 chunks: slice::Iter<'a, &'a [u8]>,
334 skip: usize,
336 remaining: usize,
338 },
339}
340
341impl<'a> Iterator for Chunks<'a> {
342 type Item = &'a [u8];
343
344 fn next(&mut self) -> Option<Self::Item> {
345 let (chunks, skip, remaining) = match self {
346 Self::Single(chunk) => return chunk.take(),
347 Self::Multiple {
348 chunks,
349 skip,
350 remaining,
351 } => (chunks, skip, remaining),
352 };
353
354 loop {
355 if *remaining == 0 {
356 return None;
357 }
358
359 let chunk = chunks.next()?;
360 let Some((_, chunk)) = chunk.split_at_checked(*skip) else {
361 *skip -= chunk.len();
362 continue;
363 };
364
365 *skip = 0;
366 if chunk.is_empty() {
367 continue;
368 }
369
370 let take = Ord::min(chunk.len(), *remaining);
371 *remaining -= take;
372 return Some(&chunk[..take]);
373 }
374 }
375}
376
377impl<'a> From<&'a [u8]> for OutboundPlain<'a> {
378 fn from(payload: &'a [u8]) -> Self {
379 Self::Single(payload)
380 }
381}
382
383impl<'a, const N: usize> From<&'a [u8; N]> for OutboundPlain<'a> {
384 fn from(payload: &'a [u8; N]) -> Self {
385 Self::Single(payload)
386 }
387}
388
389impl<'a> From<&'a Vec<u8>> for OutboundPlain<'a> {
390 fn from(payload: &'a Vec<u8>) -> Self {
391 Self::Single(payload)
392 }
393}
394
395#[non_exhaustive]
402#[derive(Clone, Eq, PartialEq)]
403pub enum Payload<'a> {
404 Borrowed(&'a [u8]),
406 Owned(Vec<u8>),
408}
409
410impl<'a> Payload<'a> {
411 pub fn bytes(&'a self) -> &'a [u8] {
413 match self {
414 Self::Borrowed(bytes) => bytes,
415 Self::Owned(bytes) => bytes,
416 }
417 }
418
419 pub(crate) fn into_owned(self) -> Payload<'static> {
420 Payload::Owned(self.into_vec())
421 }
422
423 pub(crate) fn into_vec(self) -> Vec<u8> {
424 match self {
425 Self::Borrowed(bytes) => bytes.to_vec(),
426 Self::Owned(bytes) => bytes,
427 }
428 }
429
430 pub(crate) fn read(r: &mut Reader<'a>) -> Self {
431 Self::Borrowed(r.rest())
432 }
433}
434
435impl Payload<'static> {
436 pub fn new(bytes: impl Into<Vec<u8>>) -> Self {
438 Self::Owned(bytes.into())
439 }
440}
441
442impl<'a> Codec<'a> for Payload<'a> {
443 fn encode(&self, bytes: &mut Vec<u8>) {
444 bytes.extend_from_slice(self.bytes());
445 }
446
447 fn read(r: &mut Reader<'a>) -> Result<Self, InvalidMessage> {
448 Ok(Self::read(r))
449 }
450}
451
452impl fmt::Debug for Payload<'_> {
453 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
454 hex(f, self.bytes())
455 }
456}
457
458#[expect(clippy::exhaustive_structs)]
460pub struct InboundOpaque<'a>(pub &'a mut [u8]);
461
462impl<'a> InboundOpaque<'a> {
463 pub fn truncate(&mut self, len: usize) {
465 if len >= self.len() {
466 return;
467 }
468
469 self.0 = core::mem::take(&mut self.0)
470 .split_at_mut(len)
471 .0;
472 }
473
474 pub(crate) fn into_inner(self) -> &'a mut [u8] {
475 self.0
476 }
477
478 pub(crate) fn pop(&mut self) -> Option<u8> {
479 if self.is_empty() {
480 return None;
481 }
482
483 let len = self.len();
484 let last = self[len - 1];
485 self.truncate(len - 1);
486 Some(last)
487 }
488}
489
490impl Deref for InboundOpaque<'_> {
491 type Target = [u8];
492
493 fn deref(&self) -> &Self::Target {
494 self.0
495 }
496}
497
498impl DerefMut for InboundOpaque<'_> {
499 fn deref_mut(&mut self) -> &mut Self::Target {
500 self.0
501 }
502}
503
504#[derive(Clone, Copy, Debug, PartialEq, Eq)]
506#[non_exhaustive]
507pub enum EncodableVersion {
508 Legacy(ProtocolVersion),
515 InitialClientHello(Protocol),
522}
523
524impl EncodableVersion {
525 pub fn encode(&self) -> ProtocolVersion {
527 match self {
528 Self::Legacy(_) => ProtocolVersion::TLSv1_2,
529 Self::InitialClientHello(Protocol::Tcp | Protocol::Quic(_)) => ProtocolVersion::TLSv1_0,
530 }
531 }
532
533 pub fn version(&self) -> ProtocolVersion {
535 match self {
536 Self::Legacy(v) => *v,
537 Self::InitialClientHello(Protocol::Tcp | Protocol::Quic(_)) => ProtocolVersion::TLSv1_2,
538 }
539 }
540}
541
542fn unpad_tls13_payload(p: &mut InboundOpaque<'_>) -> ContentType {
550 loop {
551 match p.pop() {
552 Some(0) => {}
553 Some(content_type) => return ContentType::from(content_type),
554 None => return ContentType(0),
555 }
556 }
557}
558
559#[expect(missing_docs)]
561#[non_exhaustive]
562#[derive(Debug)]
563pub enum RecordError {
564 TooShortForHeader,
565 TooShortForLength,
566 InvalidEmptyPayload,
567 MessageTooLarge,
568 InvalidContentType,
569 UnknownProtocolVersion,
570}
571
572#[cfg(test)]
573mod tests {
574 use std::{println, vec};
575
576 use super::*;
577 use crate::quic;
578
579 #[test]
580 fn single_chunk_with_multiple_slices() {
581 let chunks: &[&[u8]] = &[&[], &[1, 2, 3], &[], &[4, 5, 6, 7], &[]];
582 let expected = [1, 2, 3, 4, 5, 6, 7];
583 for start in 0..=7 {
584 for end in start..=7 {
585 let (_, tail) = OutboundPlain::new(chunks).split_at(start);
586 let (plain, _) = tail.split_at(end - start);
587 let contiguous = start == end || end <= 3 || start >= 3;
588 let single_chunk = plain.single_chunk();
589 assert_eq!(single_chunk.is_some(), contiguous);
590 let Some(slice) = single_chunk else {
591 continue;
592 };
593 assert_eq!(slice, &expected[start..end]);
594 if !slice.is_empty() {
595 let source = if end <= 3 { chunks[1] } else { chunks[3] };
596 let offset = if end <= 3 { start } else { start - 3 };
597 assert_eq!(slice.as_ptr(), source[offset..].as_ptr());
598 }
599 }
600 }
601 }
602
603 #[test]
604 fn single_chunk_with_out_of_bounds_range() {
605 for start in [3, 5, 6] {
606 let plain = OutboundPlain::Multiple {
607 chunks: &[&[1, 2, 3], &[], &[4, 5], &[]],
608 start,
609 end: 7,
610 };
611 assert!(plain.single_chunk().is_none());
612 }
613 }
614
615 #[test]
616 fn single_chunk_borrows_payload() {
617 let data = [1, 2, 3];
618 let chunks: &[&[u8]] = &[&[], &data, &[]];
619 let slice = {
620 let plain = OutboundPlain::Multiple {
621 chunks,
622 start: 1,
623 end: 3,
624 };
625 plain.single_chunk().unwrap()
626 };
627 assert_eq!(slice, &data[1..]);
628 assert_eq!(slice.as_ptr(), data[1..].as_ptr());
629 }
630
631 #[test]
632 fn single_chunk_does_not_join_adjacent_slices() {
633 let owner = [1, 2, 3, 4];
634 let adjacent = [&owner[..2], &owner[2..]];
635 assert!(
636 OutboundPlain::new(&adjacent)
637 .single_chunk()
638 .is_none()
639 );
640 }
641
642 #[test]
643 fn single_chunk_empty_and_single_payloads() {
644 for plain in [
645 OutboundPlain::new_empty(),
646 OutboundPlain::new(&[]),
647 OutboundPlain::new(&[&[], &[]]),
648 ] {
649 assert_eq!(plain.single_chunk(), Some(&[][..]));
650 }
651 let data = [1, 2, 3];
652 for plain in [
653 OutboundPlain::from(data.as_slice()),
654 OutboundPlain::new(&[&data]),
655 ] {
656 let slice = plain.single_chunk().unwrap();
657 assert_eq!(slice, data);
658 assert_eq!(slice.as_ptr(), data.as_ptr());
659 }
660 }
661
662 #[test]
663 fn chunks_iteration() {
664 assert_eq!(
666 OutboundPlain::Single(&[1, 2, 3])
667 .chunks()
668 .collect::<Vec<_>>(),
669 [&[1u8, 2, 3][..]],
670 );
671 assert_eq!(
672 OutboundPlain::new_empty()
673 .chunks()
674 .count(),
675 0
676 );
677
678 let owner: Vec<&[u8]> = vec![&[], &[1, 2, 3], &[], &[4, 5], &[], &[6, 7], &[]];
681 let (_, tail) = OutboundPlain::new(&owner).split_at(1);
682 let (window, _) = tail.split_at(5);
683 assert_eq!(
684 window.chunks().collect::<Vec<_>>(),
685 [&[2u8, 3][..], &[4, 5][..], &[6][..]],
686 );
687 }
688
689 #[test]
690 fn split_at_with_single_slice() {
691 let owner: &[u8] = &[0, 1, 2, 3, 4, 5, 6, 7];
692 let borrowed_payload = OutboundPlain::Single(owner);
693
694 let (before, after) = borrowed_payload.split_at(6);
695 println!("before:{before:?}\nafter:{after:?}");
696 assert_eq!(before.to_vec(), &[0, 1, 2, 3, 4, 5]);
697 assert_eq!(after.to_vec(), &[6, 7]);
698 }
699
700 #[test]
701 fn split_at_with_multiple_slices() {
702 let owner: Vec<&[u8]> = vec![&[0, 1, 2, 3], &[4, 5], &[6, 7, 8], &[9, 10, 11, 12]];
703 let borrowed_payload = OutboundPlain::new(&owner);
704
705 let (before, after) = borrowed_payload.split_at(3);
706 println!("before:{before:?}\nafter:{after:?}");
707 assert_eq!(before.to_vec(), &[0, 1, 2]);
708 assert_eq!(after.to_vec(), &[3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
709
710 let (before, after) = borrowed_payload.split_at(8);
711 println!("before:{before:?}\nafter:{after:?}");
712 assert_eq!(before.to_vec(), &[0, 1, 2, 3, 4, 5, 6, 7]);
713 assert_eq!(after.to_vec(), &[8, 9, 10, 11, 12]);
714
715 let (before, after) = borrowed_payload.split_at(11);
716 println!("before:{before:?}\nafter:{after:?}");
717 assert_eq!(before.to_vec(), &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
718 assert_eq!(after.to_vec(), &[11, 12]);
719 }
720
721 #[test]
722 fn split_at_drops_consumed_chunks() {
723 let owner = vec![&[0, 1, 2][..], &[], &[3, 4], &[5], &[]];
724 let payload = OutboundPlain::new(&owner);
725
726 let (before, after) = payload.split_at(4);
727 assert_eq!(before.to_vec(), &[0, 1, 2, 3]);
728 assert_eq!(after.to_vec(), &[4, 5]);
729 let OutboundPlain::Multiple { chunks, start, end } = after else {
730 panic!("expected multiple chunks");
731 };
732 assert_eq!(chunks, &owner[2..]);
733 assert_eq!((start, end), (1, 3));
734
735 let (before, after) = after.split_at(1);
736 assert_eq!(before.to_vec(), &[4]);
737 assert_eq!(after.to_vec(), &[5]);
738 let OutboundPlain::Multiple { chunks, start, end } = after else {
739 panic!("expected multiple chunks");
740 };
741 assert_eq!(chunks, &owner[3..]);
742 assert_eq!((start, end), (0, 1));
743
744 let (before, after) = after.split_at(1);
745 assert_eq!(before.to_vec(), &[5]);
746 assert!(after.is_empty());
747 let OutboundPlain::Multiple { chunks, start, end } = after else {
748 panic!("expected multiple chunks");
749 };
750 assert!(chunks.is_empty());
751 assert_eq!((start, end), (0, 0));
752 }
753
754 #[test]
755 fn split_out_of_bounds() {
756 let owner: Vec<&[u8]> = vec![&[0, 1, 2, 3], &[4, 5], &[6, 7, 8], &[9, 10, 11, 12]];
757
758 let single_payload = OutboundPlain::Single(owner[0]);
759 let (before, after) = single_payload.split_at(17);
760 println!("before:{before:?}\nafter:{after:?}");
761 assert_eq!(before.to_vec(), &[0, 1, 2, 3]);
762 assert!(after.is_empty());
763
764 let multiple_payload = OutboundPlain::new(&owner);
765 let (before, after) = multiple_payload.split_at(17);
766 println!("before:{before:?}\nafter:{after:?}");
767 assert_eq!(before.to_vec(), &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
768 assert!(after.is_empty());
769
770 let empty_payload = OutboundPlain::new_empty();
771 let (before, after) = empty_payload.split_at(17);
772 println!("before:{before:?}\nafter:{after:?}");
773 assert!(before.is_empty());
774 assert!(after.is_empty());
775 }
776
777 #[test]
778 fn empty_slices_mixed() {
779 let owner: Vec<&[u8]> = vec![&[], &[], &[0], &[], &[1, 2], &[], &[3], &[4], &[], &[]];
780 let mut borrowed_payload = OutboundPlain::new(&owner);
781 let mut fragment_count = 0;
782 let mut fragment;
783 let expected_fragments: &[&[u8]] = &[&[0, 1], &[2, 3], &[4]];
784
785 while !borrowed_payload.is_empty() {
786 (fragment, borrowed_payload) = borrowed_payload.split_at(2);
787 println!("{fragment:?}");
788 assert_eq!(&expected_fragments[fragment_count], &fragment.to_vec());
789 fragment_count += 1;
790 }
791 assert_eq!(fragment_count, expected_fragments.len());
792 }
793
794 #[test]
795 fn exhaustive_splitting() {
796 let owner: Vec<u8> = (0..127).collect();
797 let slices = (0..7)
798 .map(|i| &owner[((1 << i) - 1)..((1 << (i + 1)) - 1)])
799 .collect::<Vec<_>>();
800 let payload = OutboundPlain::new(&slices);
801
802 assert_eq!(payload.to_vec(), owner);
803 println!("{payload:#?}");
804
805 for start in 0..128 {
806 for end in start..128 {
807 for mid in 0..(end - start) {
808 let witness = owner[start..end].split_at(mid);
809 let split_payload = payload
810 .split_at(end)
811 .0
812 .split_at(start)
813 .1
814 .split_at(mid);
815 assert_eq!(
816 witness.0,
817 split_payload.0.to_vec(),
818 "start: {start}, mid:{mid}, end:{end}"
819 );
820 assert_eq!(
821 witness.1,
822 split_payload.1.to_vec(),
823 "start: {start}, mid:{mid}, end:{end}"
824 );
825 }
826 }
827 }
828 }
829
830 #[test]
831 fn tls13_unpadded_record_rejects_protected_change_cipher_spec() {
832 let mut payload = [0x01u8, ContentType::ChangeCipherSpec.into(), 0x00];
833 let record = Record {
834 typ: ContentType::ApplicationData,
835 version: EncodableVersion::Legacy(ProtocolVersion::TLSv1_2),
836 payload: InboundOpaque(&mut payload),
837 };
838 assert_eq!(
839 record
840 .into_tls13_unpadded_record()
841 .unwrap_err(),
842 Error::PeerMisbehaved(PeerMisbehaved::IllegalMiddleboxChangeCipherSpec)
843 );
844 }
845
846 #[test]
847 fn record_encoding_version() {
848 for (version, expect) in [
849 (
850 EncodableVersion::InitialClientHello(Protocol::Tcp),
851 ProtocolVersion::TLSv1_0,
852 ),
853 (
854 EncodableVersion::InitialClientHello(Protocol::Quic(quic::Version::V2)),
855 ProtocolVersion::TLSv1_0,
856 ),
857 (
858 EncodableVersion::Legacy(ProtocolVersion::TLSv1_3),
859 ProtocolVersion::TLSv1_2,
860 ),
861 (
862 EncodableVersion::Legacy(ProtocolVersion::TLSv1_2),
863 ProtocolVersion::TLSv1_2,
864 ),
865 ] {
866 let record = Record {
867 typ: ContentType::Handshake,
868 version,
869 payload: OutboundPlain::Single(&[0, 1, 2, 3, 4]),
870 };
871 let encoded = record.to_unencrypted_bytes();
872 let decoded = Record::<Payload<'_>>::read(&mut Reader::new(&encoded)).unwrap();
873 assert_eq!(decoded.version.version(), expect);
874 }
875 }
876}