1use bitflags::bitflags;
12use crc::Hasher32 as _;
13use static_assertions::const_assert_eq;
14use zerocopy::byteorder::{LE, U32, U64};
15use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned};
16
17use crate::{DeliveryBlobError, DeliveryBlobHeader, DeliveryBlobType, Type1Blob};
18
19const DELIVERY_BLOB_MAGIC: [u8; 4] = [0xfc, 0x1a, 0xb1, 0x0b];
21
22const_assert_eq!(std::mem::size_of::<SerializedHeader>(), 12);
24const_assert_eq!(
25 std::mem::size_of::<SerializedType1Blob>(),
26 std::mem::size_of::<SerializedHeader>() + 16
27);
28
29bitflags! {
30 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
32 struct SerializedType1Flags : u32 {
33 const IS_COMPRESSED = 0x00000001;
34 const VALID_FLAGS_MASK = Self::IS_COMPRESSED.bits();
35 }
36}
37
38impl From<&Type1Blob> for SerializedType1Flags {
39 fn from(value: &Type1Blob) -> Self {
40 if value.is_compressed {
41 SerializedType1Flags::IS_COMPRESSED
42 } else {
43 SerializedType1Flags::empty()
44 }
45 }
46}
47
48#[derive(IntoBytes, KnownLayout, FromBytes, Immutable, Unaligned, Clone, Copy, Debug)]
50#[repr(C)]
51pub(crate) struct SerializedHeader {
52 magic: [u8; 4],
53 delivery_type: U32<LE>,
54 header_length: U32<LE>,
55}
56
57impl SerializedHeader {
58 pub fn decode(&self) -> Result<DeliveryBlobHeader, DeliveryBlobError> {
59 if self.magic != DELIVERY_BLOB_MAGIC {
60 return Err(DeliveryBlobError::BadMagic);
61 }
62 Ok(DeliveryBlobHeader {
63 delivery_type: self.delivery_type.get().try_into()?,
64 header_length: self.header_length.get(),
65 })
66 }
67}
68
69impl From<&DeliveryBlobHeader> for SerializedHeader {
70 fn from(value: &DeliveryBlobHeader) -> Self {
71 Self {
72 magic: DELIVERY_BLOB_MAGIC,
73 delivery_type: Into::<u32>::into(value.delivery_type).into(),
74 header_length: value.header_length.into(),
75 }
76 }
77}
78
79#[derive(IntoBytes, KnownLayout, FromBytes, Immutable, Unaligned, Clone, Copy, Debug)]
86#[repr(C)]
87pub(crate) struct SerializedType1Blob {
88 header: SerializedHeader,
90 payload_length: U64<LE>,
92 checksum: U32<LE>,
93 flags: U32<LE>,
94}
95
96impl From<Type1Blob> for SerializedType1Blob {
97 fn from(value: Type1Blob) -> Self {
98 let serialized = Self {
99 header: (&value.header).into(),
100 payload_length: (value.payload_length as u64).into(),
101 checksum: Default::default(), flags: SerializedType1Flags::from(&value).bits().into(),
103 };
104
105 Self { checksum: serialized.checksum().into(), ..serialized }
106 }
107}
108
109impl SerializedType1Blob {
110 pub fn checksum(&self) -> u32 {
111 let header = Self { checksum: 0.into(), ..*self };
113 let mut digest = crc::crc32::Digest::new(crc::crc32::IEEE);
114 digest.write(header.as_bytes());
115 digest.sum32()
116 }
117
118 pub fn decode(&self) -> Result<Type1Blob, DeliveryBlobError> {
120 if self.checksum.get() != self.checksum() {
122 return Err(DeliveryBlobError::IntegrityError);
123 }
124 let header: DeliveryBlobHeader = self.header.decode()?;
126 if header.delivery_type != DeliveryBlobType::Type1 {
127 return Err(DeliveryBlobError::InvalidType);
128 }
129 if header.header_length != Type1Blob::HEADER.header_length {
130 return Err(DeliveryBlobError::IntegrityError);
131 }
132 let payload_length = self.payload_length.get() as usize;
134 let flags = SerializedType1Flags::from_bits(self.flags.get())
135 .ok_or(DeliveryBlobError::IntegrityError)?;
136
137 Ok(Type1Blob {
138 header,
139 payload_length,
140 is_compressed: flags.contains(SerializedType1Flags::IS_COMPRESSED),
141 })
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148 use crate::CompressionMode;
149
150 const TEST_DATA: &[u8] = &[1, 2, 3, 4];
151
152 #[test]
153 fn type_1_round_trip_uncompressed() {
154 let delivery_blob = Type1Blob::generate(TEST_DATA, CompressionMode::Never);
155 assert_eq!(
156 delivery_blob.len(),
157 std::mem::size_of::<SerializedType1Blob>() + TEST_DATA.len()
158 );
159 let (metadata, payload) = Type1Blob::parse(&delivery_blob).unwrap().unwrap();
161 assert_eq!(metadata.header, Type1Blob::HEADER);
162 assert_eq!(metadata.payload_length, TEST_DATA.len());
163 assert_eq!(metadata.is_compressed, false);
164 assert_eq!(payload, TEST_DATA);
166 }
167
168 #[test]
169 fn type_1_round_trip_empty() {
170 let delivery_blob = Type1Blob::generate(&[], CompressionMode::Never);
171 assert_eq!(delivery_blob.len(), std::mem::size_of::<SerializedType1Blob>());
172 let (metadata, payload) = Type1Blob::parse(&delivery_blob).unwrap().unwrap();
174 assert_eq!(metadata.header, Type1Blob::HEADER);
175 assert_eq!(metadata.payload_length, 0);
176 assert_eq!(metadata.is_compressed, false);
177 assert!(payload.is_empty());
178 }
179
180 #[test]
181 fn type_1_not_enough_data() {
182 let delivery_blob = Type1Blob::generate(TEST_DATA, CompressionMode::Never);
183 let not_enough_data = &delivery_blob[..std::mem::size_of::<SerializedType1Blob>() - 1];
184 assert!(Type1Blob::parse(not_enough_data).unwrap().is_none());
185 }
186
187 #[test]
188 fn type_1_bad_magic() {
189 let valid: SerializedType1Blob =
191 Type1Blob { header: Type1Blob::HEADER, payload_length: 0, is_compressed: false }
192 .try_into()
193 .unwrap();
194 assert!(Type1Blob::parse(valid.as_bytes()).is_ok());
195 let mut has_corrupt_magic = SerializedType1Blob {
197 header: SerializedHeader { magic: [0, 0, 0, 0], ..valid.header },
198 ..valid
199 };
200 has_corrupt_magic.checksum = has_corrupt_magic.checksum().into();
201 assert_eq!(
202 Type1Blob::parse(has_corrupt_magic.as_bytes()).unwrap_err(),
203 DeliveryBlobError::BadMagic
204 );
205 }
206
207 #[test]
208 fn type_1_invalid_type() {
209 let has_invalid_type: SerializedType1Blob = Type1Blob {
211 header: DeliveryBlobHeader {
212 delivery_type: DeliveryBlobType::Reserved,
213 header_length: 0,
214 },
215 payload_length: 0,
216 is_compressed: false,
217 }
218 .try_into()
219 .unwrap();
220 assert_eq!(
221 Type1Blob::parse(has_invalid_type.as_bytes()).unwrap_err(),
222 DeliveryBlobError::InvalidType
223 );
224 }
225
226 #[test]
227 fn type_1_invalid_header_length() {
228 let has_invalid_header_length: SerializedType1Blob = Type1Blob {
230 header: DeliveryBlobHeader {
231 delivery_type: DeliveryBlobType::Type1,
232 header_length: Type1Blob::HEADER.header_length + 1,
233 },
234 payload_length: 0,
235 is_compressed: false,
236 }
237 .try_into()
238 .unwrap();
239 assert_eq!(
240 Type1Blob::parse(has_invalid_header_length.as_bytes()).unwrap_err(),
241 DeliveryBlobError::IntegrityError
242 );
243 }
244
245 #[test]
246 fn type_1_verify_checksum() {
247 let serialized: SerializedType1Blob = Type1Blob {
249 header: Type1Blob::HEADER,
250 payload_length: TEST_DATA.len(),
251 is_compressed: false,
252 }
253 .try_into()
254 .unwrap();
255 assert!(serialized.decode().is_ok());
256 assert_eq!(serialized.checksum.get(), serialized.checksum());
257 let corrupted_checksum: u32 = !(serialized.checksum.get());
259 let corrupted_blob =
260 SerializedType1Blob { checksum: corrupted_checksum.into(), ..serialized };
261 assert_eq!(
262 Type1Blob::parse(corrupted_blob.as_bytes()).unwrap_err(),
263 DeliveryBlobError::IntegrityError
264 );
265 }
266}