1mod handshake;
2#[doc(inline)]
3pub use handshake::*;
4
5mod alert;
6#[doc(inline)]
7pub use alert::*;
8
9use crate::error::RecordError;
10
11use zerocopy::byteorder::network_endian::U16 as N16;
12use zerocopy::{Immutable, IntoBytes, KnownLayout, TryFromBytes, Unaligned};
13
14use ytls_traits::ClientHelloProcessor;
15
16#[derive(TryFromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
18#[repr(u8)]
19#[derive(Copy, Clone, Debug)]
20pub enum ContentType {
21 ChangeCipherSpec = 20,
23 Alert = 21,
25 Handshake = 22,
27 ApplicationData = 23,
29}
30
31#[derive(TryFromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
33#[repr(C)]
34#[derive(Debug)]
35pub struct RecordHeader {
36 content_type: ContentType,
37 legacy_version: [u8; 2],
38 record_length: N16,
39}
40
41#[derive(Debug)]
43pub struct Record<'r> {
44 header: &'r RecordHeader,
45 raw_bytes: &'r [u8],
46 content: Content<'r>,
47}
48
49#[derive(Debug)]
51pub enum Content<'r> {
52 ChangeCipherSpec,
53 ApplicationData,
55 Handshake(HandshakeMsg<'r>),
57 Alert(AlertMsg<'r>),
59}
60
61impl<'r> Record<'r> {
62 #[inline]
64 pub fn header_as_bytes(&self) -> &[u8] {
65 self.header.as_bytes()
66 }
67 #[inline]
69 pub fn as_bytes(&self) -> &[u8] {
70 self.raw_bytes
71 }
72 #[inline]
74 pub fn content_type(&self) -> ContentType {
75 self.header.content_type
76 }
77 #[inline]
79 pub fn content(&'r self) -> &'r Content<'r> {
80 &self.content
81 }
82 #[inline]
84 pub fn parse_client_appdata(bytes: &'r [u8]) -> Result<(Record<'r>, &'r [u8]), RecordError> {
85 let (hdr, rest) =
86 RecordHeader::try_ref_from_prefix(bytes).map_err(|e| RecordError::from_zero_copy(e))?;
87
88 if hdr.record_length > 16384 {
89 return Err(RecordError::OverflowLength);
90 }
91 let raw_bytes = &rest[0..usize::from(hdr.record_length)];
92
93 let (content, rest_next) = match hdr.content_type {
94 ContentType::Alert => {
95 let (c, r_next) = AlertMsg::client_parse(rest).unwrap();
96 (Content::Alert(c), r_next)
97 }
98 ContentType::ChangeCipherSpec => {
99 let r_next = &rest[usize::from(hdr.record_length)..];
100 (Content::ChangeCipherSpec, r_next)
101 }
102 ContentType::ApplicationData => {
103 let r_next = &rest[usize::from(hdr.record_length)..];
104 (Content::ApplicationData, r_next)
105 }
106 _ => return Err(RecordError::NotAllowed),
107 };
108
109 Ok((
110 Self {
111 header: hdr,
112 raw_bytes,
113 content,
114 },
115 rest_next,
116 ))
117 }
118 #[inline]
120 pub fn parse_client<P: ClientHelloProcessor>(
121 prc: &mut P,
122 bytes: &'r [u8],
123 ) -> Result<(Record<'r>, &'r [u8]), RecordError> {
124 let (hdr, rest) =
125 RecordHeader::try_ref_from_prefix(bytes).map_err(|e| RecordError::from_zero_copy(e))?;
126
127 if hdr.record_length > 16384 {
128 return Err(RecordError::OverflowLength);
129 }
130
131 let raw_bytes = &rest[0..usize::from(hdr.record_length)];
132
133 let (content, rest_next) = match hdr.content_type {
134 ContentType::Handshake => {
135 let (c, r_next) = HandshakeMsg::client_parse(prc, rest).unwrap();
136 (Content::Handshake(c), r_next)
137 }
138 ContentType::Alert => {
139 let (c, r_next) = AlertMsg::client_parse(rest).unwrap();
140 (Content::Alert(c), r_next)
141 }
142 ContentType::ApplicationData => {
143 let r_next = &rest[usize::from(hdr.record_length)..];
144 (Content::ApplicationData, r_next)
145 }
146 ContentType::ChangeCipherSpec => {
147 let r_next = &rest[usize::from(hdr.record_length)..];
148 (Content::ChangeCipherSpec, r_next)
149 }
150 };
151
152 Ok((
153 Self {
154 header: hdr,
155 raw_bytes,
156 content,
157 },
158 rest_next,
159 ))
160 }
161}
162
163#[cfg(test)]
164mod test {
165
166 use super::*;
167 use hex_literal::hex;
168
169 #[derive(Debug, PartialEq)]
170 struct Tester {
171 suites_encountered: Vec<[u8; 2]>,
172 extensions_encountered: Vec<u16>,
173 }
174 impl ClientHelloProcessor for Tester {
175 fn handle_extension(&mut self, ext_id: u16, _ext_data: &[u8]) -> () {
176 self.extensions_encountered.push(ext_id);
177 }
178 fn handle_cipher_suite(&mut self, cipher_suite: &[u8; 2]) -> () {
179 self.suites_encountered
180 .push([cipher_suite[0], cipher_suite[1]]);
181 }
182 fn handle_client_random(&mut self, _cr: &[u8; 32]) {
183 }
185 fn handle_session_id(&mut self, _sid: &[u8]) {
186 }
188 }
189
190 #[test]
191 fn test_firefox_handshake_client_hello() {
192 let mut tester = Tester {
193 suites_encountered: vec![],
194 extensions_encountered: vec![],
195 };
196 let data = hex!("16030102970100029303030b77e4fa04ceb4dc026c74213fe2a55c14883219b9e6f7b0b503ee2b4a331d842065dcc0babe8c401c1e8afe1f5e40e54155dd0f28e1c7be6e2326143f89bcd95d0022130113031302c02bc02fcca9cca8c02cc030c00ac009c013c014009c009d002f003501000228000000150013000010746573742e72757374637279702e746f00170000ff01000100000a000e000c001d00170018001901000101000b00020100002300000010000e000c02683208687474702f312e310005000501000000000022000a00080403050306030203001200000033006b0069001d0020978142d12fa56febea3f967a43cf5accea191ce4cd5dcfe9d1fd7a5817bbc72700170041043d89d5b8f29cb5c29230bcc6eae0c2890f489724426bd26e2a72581231956ae99117c739f4d24d564143a732a73e92421b49ff51a9c44f729460f6ee251e537b002b00050403040303000d0018001604030503060308040805080604010501060102030201002d00020101001c00024001fe0d01190000010003af0020e80e102405db3ad2cfb267f6e11556f1f8b6364f5a02b07897b8eaee4e0d5a1900efe3b3a5d24df62d045ab566ba61536b5443cec82be022b712882204f783afe7c7eb59b93e6b9d30d623fe9a85d0895936be8f85d54818e9a06889e6ed53e3d5aa94e0812c872d5eb40277f6d2b9c1afdbab70bc7da5d6281d2632895855675bc5e7ddadd6aefec02342135082950c430deb6c4ce3d9294929271722aaddb06a7770594ec2bd395378e061b292dfdaa537e2535ca7ee5c698991f8dd8b5c227295e2ceccb7a9b84db5cadcb055f1ef019d6699f76959260a0a49574d18456be3936e74f76d3e5e5b418ddc45b2b219cee91c9ddf0c58dd3c0fb87d954cb59a43d897ed11f7ea0a51fb7b093ad547d2b0");
197 let (r, rest) = Record::parse_client(&mut tester, &data).unwrap();
198
199 insta::assert_debug_snapshot!(r);
200 assert_eq!(rest.len(), 0);
201 }
202}