1 | //! [TCG] (Trusted Computing Group) protocol for [TPM] (Trusted Platform |
2 | //! Module) 2.0. |
3 | //! |
4 | //! This protocol is defined in the [TCG EFI Protocol Specification _TPM |
5 | //! Family 2.0_][spec]. It is generally implemented only for TPM 2.0 |
6 | //! devices, but the spec indicates it can also be used for older TPM |
7 | //! devices. |
8 | //! |
9 | //! [spec]: https://trustedcomputinggroup.org/resource/tcg-efi-protocol-specification/ |
10 | //! [TCG]: https://trustedcomputinggroup.org/ |
11 | //! [TPM]: https://en.wikipedia.org/wiki/Trusted_Platform_Module |
12 | |
13 | use super::{v1, AlgorithmId, EventType, HashAlgorithm, PcrIndex}; |
14 | use crate::data_types::{PhysicalAddress, UnalignedSlice}; |
15 | use crate::proto::unsafe_protocol ; |
16 | use crate::util::{ptr_write_unaligned_and_add, usize_from_u32}; |
17 | use crate::{Error, Result, Status}; |
18 | use bitflags::bitflags; |
19 | use core::fmt::{self, Debug, Formatter}; |
20 | use core::marker::PhantomData; |
21 | use core::mem::MaybeUninit; |
22 | use core::{mem, ptr, slice}; |
23 | use ptr_meta::{Pointee, PtrExt}; |
24 | |
25 | /// Version information. |
26 | /// |
27 | /// Layout compatible with the C type `EFI_TG2_VERSION`. |
28 | #[repr (C)] |
29 | #[derive (Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd)] |
30 | pub struct Version { |
31 | /// Major version. |
32 | pub major: u8, |
33 | /// Minor version. |
34 | pub minor: u8, |
35 | } |
36 | |
37 | bitflags! { |
38 | /// Event log formats supported by the firmware. |
39 | /// |
40 | /// Corresponds to the C typedef `EFI_TCG2_EVENT_ALGORITHM_BITMAP`. |
41 | #[derive (Default)] |
42 | #[repr (transparent)] |
43 | pub struct EventLogFormat: u32 { |
44 | /// Firmware supports the SHA-1 log format. |
45 | const TCG_1_2 = 0x0000_0001; |
46 | |
47 | /// Firmware supports the crypto-agile log format. |
48 | const TCG_2 = 0x0000_0002; |
49 | } |
50 | } |
51 | |
52 | /// Information about the protocol and the TPM device. |
53 | /// |
54 | /// Layout compatible with the C type `EFI_TCG2_BOOT_SERVICE_CAPABILITY`. |
55 | #[repr (C)] |
56 | #[derive (Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)] |
57 | pub struct BootServiceCapability { |
58 | size: u8, |
59 | |
60 | /// Version of the EFI TCG2 protocol. |
61 | pub structure_version: Version, |
62 | |
63 | /// Version of the EFI TCG2 protocol. |
64 | pub protocol_version: Version, |
65 | |
66 | /// Bitmap of supported hash algorithms. |
67 | pub hash_algorithm_bitmap: HashAlgorithm, |
68 | |
69 | /// Event log formats supported by the firmware. |
70 | pub supported_event_logs: EventLogFormat, |
71 | |
72 | present_flag: u8, |
73 | |
74 | /// Maximum size (in bytes) of a command that can be sent to the TPM. |
75 | pub max_command_size: u16, |
76 | |
77 | /// Maximum size (in bytes) of a response that can be provided by the TPM. |
78 | pub max_response_size: u16, |
79 | |
80 | /// Manufacturer ID. |
81 | /// |
82 | /// See the [TCG Vendor ID registry]. |
83 | /// |
84 | /// [TCG Vendor ID registry]: https://trustedcomputinggroup.org/resource/vendor-id-registry/ |
85 | pub manufacturer_id: u32, |
86 | |
87 | /// Maximum number of supported PCR banks (hashing algorithms). |
88 | pub number_of_pcr_banks: u32, |
89 | |
90 | /// Bitmap of currently-active PCR banks (hashing algorithms). This |
91 | /// is a subset of the supported algorithms in [`hash_algorithm_bitmap`]. |
92 | /// |
93 | /// [`hash_algorithm_bitmap`]: Self::hash_algorithm_bitmap |
94 | pub active_pcr_banks: HashAlgorithm, |
95 | } |
96 | |
97 | impl Default for BootServiceCapability { |
98 | fn default() -> Self { |
99 | // OK to unwrap, the size is less than u8. |
100 | let struct_size: u8 = u8::try_from(mem::size_of::<BootServiceCapability>()).unwrap(); |
101 | |
102 | Self { |
103 | size: struct_size, |
104 | structure_version: Version::default(), |
105 | protocol_version: Version::default(), |
106 | hash_algorithm_bitmap: HashAlgorithm::default(), |
107 | supported_event_logs: EventLogFormat::default(), |
108 | present_flag: 0, |
109 | max_command_size: 0, |
110 | max_response_size: 0, |
111 | manufacturer_id: 0, |
112 | number_of_pcr_banks: 0, |
113 | active_pcr_banks: HashAlgorithm::default(), |
114 | } |
115 | } |
116 | } |
117 | |
118 | impl BootServiceCapability { |
119 | /// Whether the TPM device is present. |
120 | #[must_use ] |
121 | pub fn tpm_present(&self) -> bool { |
122 | self.present_flag != 0 |
123 | } |
124 | } |
125 | |
126 | bitflags! { |
127 | /// Flags for the [`Tcg::hash_log_extend_event`] function. |
128 | #[derive (Default)] |
129 | #[repr (transparent)] |
130 | pub struct HashLogExtendEventFlags: u64 { |
131 | /// Extend an event but don't log it. |
132 | const EFI_TCG2_EXTEND_ONLY = 0x0000_0000_0000_0001; |
133 | |
134 | /// Use when measuring a PE/COFF image. |
135 | const PE_COFF_IMAGE = 0x0000_0000_0000_0010; |
136 | } |
137 | } |
138 | |
139 | /// Header used in [`PcrEventInputs`]. |
140 | /// |
141 | /// Layout compatible with the C type `EFI_TCG2_EVENT_HEADER`. |
142 | #[derive (Clone, Copy, Debug, Eq, PartialEq)] |
143 | #[repr (C, packed)] |
144 | struct EventHeader { |
145 | header_size: u32, |
146 | header_version: u16, |
147 | pcr_index: PcrIndex, |
148 | event_type: EventType, |
149 | } |
150 | |
151 | /// Event type passed to [`Tcg::hash_log_extend_event`]. |
152 | /// |
153 | /// Layout compatible with the C type `EFI_TCG2_EVENT`. |
154 | /// |
155 | /// The TPM v1 spec uses a single generic event type for both creating a |
156 | /// new event and reading an event from the log. The v2 spec splits this |
157 | /// into two structs: `EFI_TCG2_EVENT` for creating events, and |
158 | /// `TCG_PCR_EVENT2` for reading events. To help clarify the usage, our |
159 | /// API renames these types to `PcrEventInputs` and `PcrEvent`, |
160 | /// respectively. |
161 | #[derive (Pointee)] |
162 | #[repr (C, packed)] |
163 | pub struct PcrEventInputs { |
164 | size: u32, |
165 | event_header: EventHeader, |
166 | event: [u8], |
167 | } |
168 | |
169 | impl PcrEventInputs { |
170 | /// Create a new `PcrEventInputs` using a byte buffer for storage. |
171 | /// |
172 | /// # Errors |
173 | /// |
174 | /// Returns [`Status::BUFFER_TOO_SMALL`] if the `buffer` is not large |
175 | /// enough. |
176 | /// |
177 | /// Returns [`Status::INVALID_PARAMETER`] if the `event_data` size is too |
178 | /// large. |
179 | pub fn new_in_buffer<'buf>( |
180 | buffer: &'buf mut [MaybeUninit<u8>], |
181 | pcr_index: PcrIndex, |
182 | event_type: EventType, |
183 | event_data: &[u8], |
184 | ) -> Result<&'buf Self> { |
185 | let required_size = |
186 | mem::size_of::<u32>() + mem::size_of::<EventHeader>() + event_data.len(); |
187 | |
188 | if buffer.len() < required_size { |
189 | return Err(Status::BUFFER_TOO_SMALL.into()); |
190 | } |
191 | let size_field = |
192 | u32::try_from(required_size).map_err(|_| Error::from(Status::INVALID_PARAMETER))?; |
193 | |
194 | let mut ptr: *mut u8 = buffer.as_mut_ptr().cast(); |
195 | |
196 | unsafe { |
197 | ptr_write_unaligned_and_add(&mut ptr, size_field); |
198 | ptr_write_unaligned_and_add( |
199 | &mut ptr, |
200 | EventHeader { |
201 | header_size: u32::try_from(mem::size_of::<EventHeader>()).unwrap(), |
202 | header_version: 1, |
203 | pcr_index, |
204 | event_type, |
205 | }, |
206 | ); |
207 | ptr::copy(event_data.as_ptr(), ptr, event_data.len()); |
208 | |
209 | let ptr: *const PcrEventInputs = |
210 | ptr_meta::from_raw_parts(buffer.as_ptr().cast(), event_data.len()); |
211 | Ok(&*ptr) |
212 | } |
213 | } |
214 | } |
215 | |
216 | #[repr (C, packed)] |
217 | #[derive (Clone, Copy, Debug, PartialEq, Eq)] |
218 | struct AlgorithmDigestSize { |
219 | algorithm_id: AlgorithmId, |
220 | digest_size: u16, |
221 | } |
222 | |
223 | #[derive (Clone, Debug)] |
224 | struct AlgorithmDigestSizes<'a>(UnalignedSlice<'a, AlgorithmDigestSize>); |
225 | |
226 | impl<'a> AlgorithmDigestSizes<'a> { |
227 | fn get_size(&self, alg: AlgorithmId) -> Option<u16> { |
228 | self.0.iter().find_map(|elem: AlgorithmDigestSize| { |
229 | if { elem.algorithm_id } == alg { |
230 | Some(elem.digest_size) |
231 | } else { |
232 | None |
233 | } |
234 | }) |
235 | } |
236 | } |
237 | |
238 | fn u32_le_from_bytes_at_offset(bytes: &[u8], offset: usize) -> Option<u32> { |
239 | let bytes: &[u8] = bytes.get(index:offset..offset + 4)?; |
240 | // OK to unwrap: we know `bytes` is now of length 4. |
241 | let val: u32 = u32::from_le_bytes(bytes.try_into().unwrap()); |
242 | Some(val) |
243 | } |
244 | |
245 | /// Header stored at the beginning of the event log. |
246 | /// |
247 | /// Layout compatible with the C type `TCG_EfiSpecIDEventStruct`. |
248 | #[derive (Clone, Debug)] |
249 | #[allow (unused)] // We don't current access most of the fields. |
250 | struct EventLogHeader<'a> { |
251 | platform_class: u32, |
252 | // major, minor, errata |
253 | spec_version: (u8, u8, u8), |
254 | uintn_size: u8, |
255 | algorithm_digest_sizes: AlgorithmDigestSizes<'a>, |
256 | vendor_info: &'a [u8], |
257 | // Size of the whole header event, in bytes. |
258 | size_in_bytes: usize, |
259 | } |
260 | |
261 | impl<'a> EventLogHeader<'a> { |
262 | fn new(event: &'a v1::PcrEvent) -> Option<Self> { |
263 | if event.pcr_index() != PcrIndex(0) { |
264 | return None; |
265 | } |
266 | if { event.event_type() } != EventType::NO_ACTION { |
267 | return None; |
268 | } |
269 | if event.digest() != [0; 20] { |
270 | return None; |
271 | } |
272 | |
273 | let event = &event.event_data(); |
274 | if event.get(..16)? != *b"Spec ID Event03 \0" { |
275 | return None; |
276 | } |
277 | let platform_class = u32_le_from_bytes_at_offset(event, 16)?; |
278 | let version_minor = *event.get(20)?; |
279 | let version_major = *event.get(21)?; |
280 | let version_errata = *event.get(22)?; |
281 | let uintn_size = *event.get(23)?; |
282 | let number_of_algorithms = usize_from_u32(u32_le_from_bytes_at_offset(event, 24)?); |
283 | let vendor_info_size_byte_offset = |
284 | 28 + (number_of_algorithms * mem::size_of::<AlgorithmDigestSize>()); |
285 | let vendor_info_size = usize::from(*event.get(vendor_info_size_byte_offset)?); |
286 | |
287 | // Safety: we know the slice is big enough because we just |
288 | // safely got the field after the slice (`vendor_info_size`). |
289 | let algorithm_digest_sizes = unsafe { |
290 | let ptr: *const AlgorithmDigestSize = event.as_ptr().add(28).cast(); |
291 | AlgorithmDigestSizes(UnalignedSlice::new(ptr, number_of_algorithms)) |
292 | }; |
293 | |
294 | let vendor_info_byte_offset = vendor_info_size_byte_offset + 1; |
295 | let vendor_info = |
296 | event.get(vendor_info_byte_offset..vendor_info_byte_offset + vendor_info_size)?; |
297 | |
298 | // 32 is the size of PcrEventV1 excluding the event data. |
299 | let size_in_bytes = 32 + vendor_info_byte_offset + vendor_info_size; |
300 | |
301 | Some(Self { |
302 | platform_class, |
303 | spec_version: (version_major, version_minor, version_errata), |
304 | uintn_size, |
305 | algorithm_digest_sizes, |
306 | vendor_info, |
307 | size_in_bytes, |
308 | }) |
309 | } |
310 | } |
311 | |
312 | /// TPM event log as returned by [`Tcg::get_event_log_v2`]. |
313 | /// |
314 | /// This type of event log can contain multiple hash types (e.g. SHA-1, SHA-256, |
315 | /// SHA-512, etc). |
316 | pub struct EventLog<'a> { |
317 | // Tie the lifetime to the protocol, and by extension, boot services. |
318 | _lifetime: PhantomData<&'a Tcg>, |
319 | |
320 | location: *const u8, |
321 | last_entry: *const u8, |
322 | |
323 | is_truncated: bool, |
324 | } |
325 | |
326 | impl<'a> EventLog<'a> { |
327 | /// Iterator of events in the log. |
328 | #[must_use ] |
329 | pub fn iter(&self) -> EventLogIter { |
330 | if let Some(header) = self.header() { |
331 | // Advance past the header |
332 | let location = unsafe { self.location.add(header.size_in_bytes) }; |
333 | |
334 | EventLogIter { |
335 | log: self, |
336 | location, |
337 | header: self.header(), |
338 | } |
339 | } else { |
340 | EventLogIter { |
341 | log: self, |
342 | location: ptr::null(), |
343 | header: None, |
344 | } |
345 | } |
346 | } |
347 | |
348 | /// Header at the beginning of the event log. |
349 | fn header(&self) -> Option<EventLogHeader> { |
350 | // The spec is unclear if the header is present when there are |
351 | // no entries, so lets assume that `self.location` will be null |
352 | // if there's no header, and otherwise valid. |
353 | if self.location.is_null() { |
354 | None |
355 | } else { |
356 | // Safety: we trust that the protocol has given us a valid range |
357 | // of memory to read from. |
358 | let event = unsafe { v1::PcrEvent::from_ptr(self.location) }; |
359 | EventLogHeader::new(event) |
360 | } |
361 | } |
362 | |
363 | /// Whether the event log is truncated due to not enough space in the log to |
364 | /// contain some events. |
365 | #[must_use ] |
366 | pub fn is_truncated(&self) -> bool { |
367 | self.is_truncated |
368 | } |
369 | } |
370 | |
371 | /// Digests in a PCR event. |
372 | #[derive (Clone)] |
373 | pub struct PcrEventDigests<'a> { |
374 | data: &'a [u8], |
375 | algorithm_digest_sizes: AlgorithmDigestSizes<'a>, |
376 | } |
377 | |
378 | impl<'a> Debug for PcrEventDigests<'a> { |
379 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { |
380 | f.debug_list().entries(self.clone()).finish() |
381 | } |
382 | } |
383 | |
384 | impl<'a> IntoIterator for PcrEventDigests<'a> { |
385 | type Item = (AlgorithmId, &'a [u8]); |
386 | type IntoIter = PcrEventDigestIter<'a>; |
387 | |
388 | fn into_iter(self) -> Self::IntoIter { |
389 | PcrEventDigestIter { |
390 | digests: self, |
391 | offset: 0, |
392 | } |
393 | } |
394 | } |
395 | |
396 | /// Iterator over a list of digests. |
397 | pub struct PcrEventDigestIter<'a> { |
398 | digests: PcrEventDigests<'a>, |
399 | offset: usize, |
400 | } |
401 | |
402 | impl<'a> Iterator for PcrEventDigestIter<'a> { |
403 | type Item = (AlgorithmId, &'a [u8]); |
404 | |
405 | fn next(&mut self) -> Option<Self::Item> { |
406 | let data: &[u8] = &self.digests.data[self.offset..]; |
407 | let alg: &[u8] = data.get(..2)?; |
408 | let alg: AlgorithmId = AlgorithmId(u16::from_le_bytes([alg[0], alg[1]])); |
409 | let digest_size: usize = usize::from(self.digests.algorithm_digest_sizes.get_size(alg)?); |
410 | let digest: &[u8] = data.get(index:2..2 + digest_size)?; |
411 | self.offset += 2 + digest_size; |
412 | Some((alg, digest)) |
413 | } |
414 | } |
415 | |
416 | /// PCR event from an [`EventLog`]. |
417 | /// |
418 | /// This roughly corresponds to the C type `TCG_PCR_EVENT2`, but is not layout |
419 | /// compatible. |
420 | /// |
421 | /// The TPM v1 spec uses a single generic event type for both creating a |
422 | /// new event and reading an event from the log. The v2 spec splits this |
423 | /// into two structs: `EFI_TCG2_EVENT` for creating events, and |
424 | /// `TCG_PCR_EVENT2` for reading events. To help clarify the usage, our |
425 | /// API renames these types to `PcrEventInputs` and `PcrEvent`, |
426 | /// respectively. |
427 | #[derive (Debug)] |
428 | pub struct PcrEvent<'a> { |
429 | pcr_index: PcrIndex, |
430 | event_type: EventType, |
431 | digests: &'a [u8], |
432 | event_data: &'a [u8], |
433 | |
434 | // Precalculate the pointer to the next event. |
435 | next: *const u8, |
436 | |
437 | // This data from the v2 log header is needed to parse the digest data. |
438 | algorithm_digest_sizes: AlgorithmDigestSizes<'a>, |
439 | } |
440 | |
441 | impl<'a> PcrEvent<'a> { |
442 | unsafe fn from_ptr(ptr: *const u8, header: EventLogHeader<'a>) -> Option<Self> { |
443 | let ptr_u32: *const u32 = ptr.cast(); |
444 | let pcr_index = PcrIndex(ptr_u32.read_unaligned()); |
445 | let event_type = EventType(ptr_u32.add(1).read_unaligned()); |
446 | let digests_count = ptr_u32.add(2).read_unaligned(); |
447 | let digests_ptr: *const u8 = ptr.add(12); |
448 | |
449 | // Get the byte size of the digests so that the digests iterator |
450 | // can be safe code. |
451 | let mut digests_byte_size = 0; |
452 | let mut elem_ptr = digests_ptr; |
453 | for _ in 0..digests_count { |
454 | let algorithm_id = AlgorithmId(elem_ptr.cast::<u16>().read_unaligned()); |
455 | let alg_and_digest_size = mem::size_of::<AlgorithmId>() |
456 | + usize::from(header.algorithm_digest_sizes.get_size(algorithm_id)?); |
457 | digests_byte_size += alg_and_digest_size; |
458 | elem_ptr = elem_ptr.add(alg_and_digest_size); |
459 | } |
460 | |
461 | let digests = slice::from_raw_parts(digests_ptr, digests_byte_size); |
462 | let event_size_ptr = digests_ptr.add(digests_byte_size); |
463 | let event_size = usize_from_u32(event_size_ptr.cast::<u32>().read_unaligned()); |
464 | let event_data_ptr = event_size_ptr.add(4); |
465 | let event_data = slice::from_raw_parts(event_data_ptr, event_size); |
466 | |
467 | Some(Self { |
468 | pcr_index, |
469 | event_type, |
470 | digests, |
471 | event_data, |
472 | next: event_data_ptr.add(event_size), |
473 | algorithm_digest_sizes: header.algorithm_digest_sizes, |
474 | }) |
475 | } |
476 | |
477 | /// PCR index for the event. |
478 | #[must_use ] |
479 | pub fn pcr_index(&self) -> PcrIndex { |
480 | self.pcr_index |
481 | } |
482 | |
483 | /// Type of event, indicating what type of data is stored in [`event_data`]. |
484 | /// |
485 | /// [`event_data`]: Self::event_data |
486 | #[must_use ] |
487 | pub fn event_type(&self) -> EventType { |
488 | self.event_type |
489 | } |
490 | |
491 | /// Raw event data. The meaning of this data can be determined from |
492 | /// the [`event_type`]. |
493 | /// |
494 | /// Note that this data is independent of what is hashed in [`digests`]. |
495 | /// |
496 | /// [`digests`]: Self::digests |
497 | /// [`event_type`]: Self::event_type |
498 | #[must_use ] |
499 | pub fn event_data(&self) -> &[u8] { |
500 | self.event_data |
501 | } |
502 | |
503 | /// Digests of the data hashed for this event. |
504 | #[must_use ] |
505 | pub fn digests(&self) -> PcrEventDigests { |
506 | PcrEventDigests { |
507 | data: self.digests, |
508 | algorithm_digest_sizes: self.algorithm_digest_sizes.clone(), |
509 | } |
510 | } |
511 | } |
512 | |
513 | /// Iterator for events in [`EventLog`]. |
514 | pub struct EventLogIter<'a> { |
515 | log: &'a EventLog<'a>, |
516 | header: Option<EventLogHeader<'a>>, |
517 | location: *const u8, |
518 | } |
519 | |
520 | impl<'a> Iterator for EventLogIter<'a> { |
521 | type Item = PcrEvent<'a>; |
522 | |
523 | fn next(&mut self) -> Option<Self::Item> { |
524 | // The spec says that `last_entry` will be null if there are no |
525 | // events. Presumably `location` will be null as well, but check |
526 | // both just to be safe. |
527 | if self.location.is_null() || self.log.last_entry.is_null() { |
528 | return None; |
529 | } |
530 | |
531 | // Safety: we trust that the protocol has given us a valid range |
532 | // of memory to read from. |
533 | let event = unsafe { PcrEvent::from_ptr(self.location, self.header.clone()?)? }; |
534 | |
535 | // If this is the last entry, set the location to null so that |
536 | // future calls to `next()` return `None`. |
537 | if self.location == self.log.last_entry { |
538 | self.location = ptr::null(); |
539 | } else { |
540 | self.location = event.next; |
541 | } |
542 | |
543 | Some(event) |
544 | } |
545 | } |
546 | |
547 | /// Protocol for interacting with TPM devices. |
548 | /// |
549 | /// This protocol can be used for interacting with older TPM 1.1/1.2 |
550 | /// devices, but most firmware only uses it for TPM 2.0. |
551 | /// |
552 | /// The corresponding C type is `EFI_TCG2_PROTOCOL`. |
553 | #[repr (C)] |
554 | #[unsafe_protocol ("607f766c-7455-42be-930b-e4d76db2720f" )] |
555 | pub struct Tcg { |
556 | get_capability: unsafe extern "efiapi" fn( |
557 | this: *mut Tcg, |
558 | protocol_capability: *mut BootServiceCapability, |
559 | ) -> Status, |
560 | |
561 | get_event_log: unsafe extern "efiapi" fn( |
562 | this: *mut Tcg, |
563 | event_log_format: EventLogFormat, |
564 | event_log_location: *mut PhysicalAddress, |
565 | event_log_last_entry: *mut PhysicalAddress, |
566 | event_log_truncated: *mut u8, |
567 | ) -> Status, |
568 | |
569 | hash_log_extend_event: unsafe extern "efiapi" fn( |
570 | this: *mut Tcg, |
571 | flags: HashLogExtendEventFlags, |
572 | data_to_hash: PhysicalAddress, |
573 | data_to_hash_len: u64, |
574 | // Use `()` here rather than `PcrEventInputs` so that it's a |
575 | // thin pointer. |
576 | event: *const (), |
577 | ) -> Status, |
578 | |
579 | submit_command: unsafe extern "efiapi" fn( |
580 | this: *mut Tcg, |
581 | input_parameter_block_size: u32, |
582 | input_parameter_block: *const u8, |
583 | output_parameter_block_size: u32, |
584 | output_parameter_block: *mut u8, |
585 | ) -> Status, |
586 | |
587 | get_active_pcr_banks: |
588 | unsafe extern "efiapi" fn(this: *mut Tcg, active_pcr_banks: *mut HashAlgorithm) -> Status, |
589 | |
590 | set_active_pcr_banks: |
591 | unsafe extern "efiapi" fn(this: *mut Tcg, active_pcr_banks: HashAlgorithm) -> Status, |
592 | |
593 | get_result_of_set_active_pcr_banks: unsafe extern "efiapi" fn( |
594 | this: *mut Tcg, |
595 | operation_present: *mut u32, |
596 | response: *mut u32, |
597 | ) -> Status, |
598 | } |
599 | |
600 | impl Tcg { |
601 | /// Get information about the protocol and TPM device. |
602 | pub fn get_capability(&mut self) -> Result<BootServiceCapability> { |
603 | let mut capability = BootServiceCapability::default(); |
604 | unsafe { (self.get_capability)(self, &mut capability).into_with_val(|| capability) } |
605 | } |
606 | |
607 | /// Get the V1 event log. This provides events in the same format as a V1 |
608 | /// TPM, so all events use SHA-1 hashes. |
609 | pub fn get_event_log_v1(&mut self) -> Result<v1::EventLog> { |
610 | let mut location = 0; |
611 | let mut last_entry = 0; |
612 | let mut truncated = 0; |
613 | |
614 | let status = unsafe { |
615 | (self.get_event_log)( |
616 | self, |
617 | EventLogFormat::TCG_1_2, |
618 | &mut location, |
619 | &mut last_entry, |
620 | &mut truncated, |
621 | ) |
622 | }; |
623 | |
624 | if status.is_success() { |
625 | let is_truncated = truncated != 0; |
626 | |
627 | let log = unsafe { |
628 | v1::EventLog::new(location as *const u8, last_entry as *const u8, is_truncated) |
629 | }; |
630 | |
631 | Ok(log) |
632 | } else { |
633 | Err(status.into()) |
634 | } |
635 | } |
636 | |
637 | /// Get the V2 event log. This format allows for a flexible list of hash types. |
638 | pub fn get_event_log_v2(&mut self) -> Result<EventLog> { |
639 | let mut location = 0; |
640 | let mut last_entry = 0; |
641 | let mut truncated = 0; |
642 | |
643 | let status = unsafe { |
644 | (self.get_event_log)( |
645 | self, |
646 | EventLogFormat::TCG_2, |
647 | &mut location, |
648 | &mut last_entry, |
649 | &mut truncated, |
650 | ) |
651 | }; |
652 | |
653 | if status.is_success() { |
654 | let is_truncated = truncated != 0; |
655 | |
656 | let log = EventLog { |
657 | _lifetime: PhantomData, |
658 | location: location as *const u8, |
659 | last_entry: last_entry as *const u8, |
660 | is_truncated, |
661 | }; |
662 | |
663 | Ok(log) |
664 | } else { |
665 | Err(status.into()) |
666 | } |
667 | } |
668 | |
669 | /// Extend a PCR and add an entry to the event log. |
670 | pub fn hash_log_extend_event( |
671 | &mut self, |
672 | flags: HashLogExtendEventFlags, |
673 | data_to_hash: &[u8], |
674 | event: &PcrEventInputs, |
675 | ) -> Result { |
676 | let event: *const PcrEventInputs = event; |
677 | let (event, _event_size) = PtrExt::to_raw_parts(event); |
678 | unsafe { |
679 | (self.hash_log_extend_event)( |
680 | self, |
681 | flags, |
682 | data_to_hash.as_ptr() as PhysicalAddress, |
683 | // OK to unwrap, usize fits in u64. |
684 | u64::try_from(data_to_hash.len()).unwrap(), |
685 | event, |
686 | ) |
687 | .into() |
688 | } |
689 | } |
690 | |
691 | /// Send a command directly to the TPM. |
692 | /// |
693 | /// Constructing the input block and parsing the output block are outside |
694 | /// the scope of this crate. See the [TPM 2.0 Specification][spec], in |
695 | /// particular Part 2 (Structures) and Part 3 (Commands). |
696 | /// |
697 | /// Note that TPM structures are big endian. |
698 | /// |
699 | /// [spec]: https://trustedcomputinggroup.org/resource/tpm-library-specification/ |
700 | pub fn submit_command( |
701 | &mut self, |
702 | input_parameter_block: &[u8], |
703 | output_parameter_block: &mut [u8], |
704 | ) -> Result { |
705 | let input_parameter_block_len = u32::try_from(input_parameter_block.len()) |
706 | .map_err(|_| Error::from(Status::BAD_BUFFER_SIZE))?; |
707 | let output_parameter_block_len = u32::try_from(output_parameter_block.len()) |
708 | .map_err(|_| Error::from(Status::BAD_BUFFER_SIZE))?; |
709 | |
710 | unsafe { |
711 | (self.submit_command)( |
712 | self, |
713 | input_parameter_block_len, |
714 | input_parameter_block.as_ptr(), |
715 | output_parameter_block_len, |
716 | output_parameter_block.as_mut_ptr(), |
717 | ) |
718 | .into() |
719 | } |
720 | } |
721 | |
722 | /// Get a bitmap of the active PCR banks. Each bank corresponds to a hash |
723 | /// algorithm. |
724 | pub fn get_active_pcr_banks(&mut self) -> Result<HashAlgorithm> { |
725 | let mut active_pcr_banks = HashAlgorithm::empty(); |
726 | |
727 | let status = unsafe { (self.get_active_pcr_banks)(self, &mut active_pcr_banks) }; |
728 | |
729 | status.into_with_val(|| active_pcr_banks) |
730 | } |
731 | |
732 | /// Set the active PCR banks. Each bank corresponds to a hash |
733 | /// algorithm. This change will not take effect until the system is |
734 | /// rebooted twice. |
735 | pub fn set_active_pcr_banks(&mut self, active_pcr_banks: HashAlgorithm) -> Result { |
736 | unsafe { (self.set_active_pcr_banks)(self, active_pcr_banks) }.into() |
737 | } |
738 | |
739 | /// Get the stored result of calling [`Tcg::set_active_pcr_banks`] in a |
740 | /// previous boot. |
741 | /// |
742 | /// If there was no attempt to set the active PCR banks in a previous boot, |
743 | /// this returns `None`. Otherwise, it returns a numeric response code: |
744 | /// * `0x00000000`: Success |
745 | /// * `0x00000001..=0x00000FFF`: TPM error code |
746 | /// * `0xfffffff0`: The operation was canceled by the user or timed out |
747 | /// * `0xfffffff1`: Firmware error |
748 | pub fn get_result_of_set_active_pcr_banks(&mut self) -> Result<Option<u32>> { |
749 | let mut operation_present = 0; |
750 | let mut response = 0; |
751 | |
752 | let status = unsafe { |
753 | (self.get_result_of_set_active_pcr_banks)(self, &mut operation_present, &mut response) |
754 | }; |
755 | |
756 | status.into_with_val(|| { |
757 | if operation_present == 0 { |
758 | None |
759 | } else { |
760 | Some(response) |
761 | } |
762 | }) |
763 | } |
764 | } |
765 | |
766 | #[cfg (test)] |
767 | mod tests { |
768 | use super::*; |
769 | use alloc::vec::Vec; |
770 | use core::slice; |
771 | |
772 | #[test ] |
773 | fn test_new_event() { |
774 | let mut buf = [MaybeUninit::uninit(); 22]; |
775 | let event_data = [0x12, 0x13, 0x14, 0x15]; |
776 | let event = |
777 | PcrEventInputs::new_in_buffer(&mut buf, PcrIndex(4), EventType::IPL, &event_data) |
778 | .unwrap(); |
779 | |
780 | assert_eq!({ event.size }, 22); |
781 | assert_eq!( |
782 | event.event_header, |
783 | EventHeader { |
784 | header_size: 14, |
785 | header_version: 1, |
786 | pcr_index: PcrIndex(4), |
787 | event_type: EventType::IPL, |
788 | } |
789 | ); |
790 | |
791 | // Cast to a byte slice to check the data is exactly as expected. |
792 | let event_ptr: *const PcrEventInputs = event; |
793 | let event_ptr: *const u8 = event_ptr.cast(); |
794 | let event_bytes = unsafe { slice::from_raw_parts(event_ptr, mem::size_of_val(event)) }; |
795 | |
796 | #[rustfmt::skip] |
797 | assert_eq!(event_bytes, [ |
798 | // Size |
799 | 0x16, 0x00, 0x00, 0x00, |
800 | |
801 | // Header |
802 | // Header size |
803 | 0x0e, 0x00, 0x00, 0x00, |
804 | // Header version |
805 | 0x01, 0x00, |
806 | // PCR index |
807 | 0x04, 0x00, 0x00, 0x00, |
808 | // Event type |
809 | 0x0d, 0x00, 0x00, 0x00, |
810 | // Event data |
811 | 0x12, 0x13, 0x14, 0x15, |
812 | ]); |
813 | } |
814 | |
815 | #[test ] |
816 | fn test_event_log_v2() { |
817 | // This data comes from dumping the TPM event log in a VM |
818 | // (truncated to just two entries after the header). |
819 | #[rustfmt::skip] |
820 | let bytes = [ |
821 | // Header event |
822 | // PCR index |
823 | 0x00, 0x00, 0x00, 0x00, |
824 | // Event type |
825 | 0x03, 0x00, 0x00, 0x00, |
826 | // SHA1 digest |
827 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
828 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
829 | // Event data size |
830 | 0x2d, 0x00, 0x00, 0x00, |
831 | // Spec ID event data |
832 | // Signature |
833 | 0x53, 0x70, 0x65, 0x63, |
834 | 0x20, 0x49, 0x44, 0x20, |
835 | 0x45, 0x76, 0x65, 0x6e, |
836 | 0x74, 0x30, 0x33, 0x00, |
837 | // Platform class |
838 | 0x00, 0x00, 0x00, 0x00, |
839 | // Spec version (minor, major, errata) (yes the order is weird) |
840 | 0x00, 0x02, 0x00, |
841 | // Uintn size |
842 | 0x02, |
843 | // Number of algorithms |
844 | 0x04, 0x00, 0x00, 0x00, |
845 | // Digest sizes |
846 | // SHA1, size |
847 | 0x04, 0x00, |
848 | 0x14, 0x00, |
849 | // SHA256, size |
850 | 0x0b, 0x00, |
851 | 0x20, 0x00, |
852 | // SHA384, size |
853 | 0x0c, 0x00, |
854 | 0x30, 0x00, |
855 | // SHA512, size |
856 | 0x0d, 0x00, |
857 | 0x40, 0x00, |
858 | // Vendor info size |
859 | 0x00, |
860 | |
861 | // Event 1 |
862 | // PCR index |
863 | 0x00, 0x00, 0x00, 0x00, |
864 | // Event type |
865 | 0x08, 0x00, 0x00, 0x00, |
866 | // Digest count |
867 | 0x04, 0x00, 0x00, 0x00, |
868 | // Digests |
869 | // SHA1 |
870 | 0x04, 0x00, |
871 | 0x14, 0x89, 0xf9, 0x23, 0xc4, 0xdc, 0xa7, 0x29, 0x17, 0x8b, |
872 | 0x3e, 0x32, 0x33, 0x45, 0x85, 0x50, 0xd8, 0xdd, 0xdf, 0x29, |
873 | // SHA256 |
874 | 0x0b, 0x00, |
875 | 0x96, 0xa2, 0x96, 0xd2, 0x24, 0xf2, 0x85, 0xc6, |
876 | 0x7b, 0xee, 0x93, 0xc3, 0x0f, 0x8a, 0x30, 0x91, |
877 | 0x57, 0xf0, 0xda, 0xa3, 0x5d, 0xc5, 0xb8, 0x7e, |
878 | 0x41, 0x0b, 0x78, 0x63, 0x0a, 0x09, 0xcf, 0xc7, |
879 | // SHA384 |
880 | 0x0c, 0x00, |
881 | 0x1d, 0xd6, 0xf7, 0xb4, 0x57, 0xad, 0x88, 0x0d, |
882 | 0x84, 0x0d, 0x41, 0xc9, 0x61, 0x28, 0x3b, 0xab, |
883 | 0x68, 0x8e, 0x94, 0xe4, 0xb5, 0x93, 0x59, 0xea, |
884 | 0x45, 0x68, 0x65, 0x81, 0xe9, 0x0f, 0xec, 0xce, |
885 | 0xa3, 0xc6, 0x24, 0xb1, 0x22, 0x61, 0x13, 0xf8, |
886 | 0x24, 0xf3, 0x15, 0xeb, 0x60, 0xae, 0x0a, 0x7c, |
887 | // SHA512 |
888 | 0x0d, 0x00, |
889 | 0x5e, 0xa7, 0x1d, 0xc6, 0xd0, 0xb4, 0xf5, 0x7b, |
890 | 0xf3, 0x9a, 0xad, 0xd0, 0x7c, 0x20, 0x8c, 0x35, |
891 | 0xf0, 0x6c, 0xd2, 0xba, 0xc5, 0xfd, 0xe2, 0x10, |
892 | 0x39, 0x7f, 0x70, 0xde, 0x11, 0xd4, 0x39, 0xc6, |
893 | 0x2e, 0xc1, 0xcd, 0xf3, 0x18, 0x37, 0x58, 0x86, |
894 | 0x5f, 0xd3, 0x87, 0xfc, 0xea, 0x0b, 0xad, 0xa2, |
895 | 0xf6, 0xc3, 0x7a, 0x4a, 0x17, 0x85, 0x1d, 0xd1, |
896 | 0xd7, 0x8f, 0xef, 0xe6, 0xf2, 0x04, 0xee, 0x54, |
897 | // Event size |
898 | 0x02, 0x00, 0x00, 0x00, |
899 | // Event data |
900 | 0x00, 0x00, |
901 | |
902 | // Event 2 |
903 | // PCR index |
904 | 0x00, 0x00, 0x00, 0x00, |
905 | // Event type |
906 | 0x08, 0x00, 0x00, 0x80, |
907 | // Digest count |
908 | 0x04, 0x00, 0x00, 0x00, |
909 | // SHA1 |
910 | 0x04, 0x00, |
911 | 0xc7, 0x06, 0xe7, 0xdd, 0x36, 0x39, 0x29, 0x84, 0xeb, 0x06, |
912 | 0xaa, 0xa0, 0x8f, 0xf3, 0x36, 0x84, 0x40, 0x77, 0xb3, 0xed, |
913 | // SHA256 |
914 | 0x0b, 0x00, |
915 | 0x3a, 0x30, 0x8e, 0x95, 0x87, 0x84, 0xbf, 0xd0, |
916 | 0xf6, 0xe3, 0xf1, 0xbd, 0x4d, 0x42, 0x14, 0xd3, |
917 | 0x0a, 0x4c, 0x55, 0x00, 0xa4, 0x5b, 0x06, 0xda, |
918 | 0x96, 0xfc, 0x90, 0x33, 0x8f, 0x69, 0xb3, 0x61, |
919 | // SHA384 |
920 | 0x0c, 0x00, |
921 | 0xc0, 0xd0, 0x75, 0x96, 0xc5, 0x9a, 0x90, 0x7b, |
922 | 0x79, 0x71, 0x6f, 0xc9, 0xf3, 0x6a, 0xad, 0x8f, |
923 | 0x0f, 0x26, 0xf2, 0x02, 0x67, 0x5b, 0x42, 0x5a, |
924 | 0x52, 0x3f, 0x72, 0xec, 0xb6, 0xf2, 0x53, 0x99, |
925 | 0x57, 0xf0, 0xd9, 0x2c, 0x0a, 0x7d, 0xce, 0xaa, |
926 | 0xf9, 0x9e, 0x60, 0x0e, 0x54, 0x18, 0xf1, 0xdc, |
927 | // SHA512 |
928 | 0x0d, 0x00, |
929 | 0x9a, 0xe9, 0x25, 0xdc, 0x9c, 0xd2, 0x9d, 0xf0, |
930 | 0xe5, 0x80, 0x54, 0x35, 0xa5, 0x99, 0x06, 0x1f, |
931 | 0xcf, 0x32, 0x98, 0xcc, 0x2a, 0x15, 0xe4, 0x87, |
932 | 0x99, 0xa2, 0x0c, 0x9c, 0xe5, 0x6c, 0x8f, 0xe5, |
933 | 0x84, 0x09, 0x75, 0xaf, 0xf0, 0xe1, 0xb6, 0x98, |
934 | 0x20, 0x07, 0x5e, 0xe4, 0x29, 0x79, 0x8b, 0x5d, |
935 | 0xbb, 0xe5, 0xd1, 0xa2, 0x74, 0x36, 0xab, 0x49, |
936 | 0xf1, 0x9b, 0x7a, 0x04, 0x11, 0xd2, 0x96, 0x2c, |
937 | // Event size |
938 | 0x10, 0x00, 0x00, 0x00, |
939 | // Event data |
940 | 0x00, 0x00, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, |
941 | 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, |
942 | ]; |
943 | |
944 | let log = EventLog { |
945 | _lifetime: PhantomData, |
946 | location: bytes.as_ptr(), |
947 | last_entry: unsafe { bytes.as_ptr().add(267) }, |
948 | is_truncated: false, |
949 | }; |
950 | |
951 | let header = log.header().unwrap(); |
952 | assert_eq!(header.platform_class, 0); |
953 | assert_eq!(header.spec_version, (2, 0, 0)); |
954 | assert_eq!(header.uintn_size, 2); |
955 | assert_eq!( |
956 | header.algorithm_digest_sizes.0.to_vec(), |
957 | [ |
958 | AlgorithmDigestSize { |
959 | algorithm_id: AlgorithmId::SHA1, |
960 | digest_size: 20, |
961 | }, |
962 | AlgorithmDigestSize { |
963 | algorithm_id: AlgorithmId::SHA256, |
964 | digest_size: 32, |
965 | }, |
966 | AlgorithmDigestSize { |
967 | algorithm_id: AlgorithmId::SHA384, |
968 | digest_size: 48, |
969 | }, |
970 | AlgorithmDigestSize { |
971 | algorithm_id: AlgorithmId::SHA512, |
972 | digest_size: 64, |
973 | }, |
974 | ] |
975 | ); |
976 | assert_eq!(header.vendor_info, []); |
977 | |
978 | let mut iter = log.iter(); |
979 | |
980 | // Entry 1 |
981 | let entry = iter.next().unwrap(); |
982 | assert_eq!(entry.pcr_index, PcrIndex(0)); |
983 | assert_eq!(entry.event_type, EventType::CRTM_VERSION); |
984 | #[rustfmt::skip] |
985 | assert_eq!( |
986 | entry.digests().into_iter().collect::<Vec<_>>(), |
987 | [ |
988 | (AlgorithmId::SHA1, [ |
989 | 0x14, 0x89, 0xf9, 0x23, 0xc4, 0xdc, 0xa7, 0x29, 0x17, 0x8b, |
990 | 0x3e, 0x32, 0x33, 0x45, 0x85, 0x50, 0xd8, 0xdd, 0xdf, 0x29, |
991 | ].as_slice()), |
992 | (AlgorithmId::SHA256, [ |
993 | 0x96, 0xa2, 0x96, 0xd2, 0x24, 0xf2, 0x85, 0xc6, |
994 | 0x7b, 0xee, 0x93, 0xc3, 0x0f, 0x8a, 0x30, 0x91, |
995 | 0x57, 0xf0, 0xda, 0xa3, 0x5d, 0xc5, 0xb8, 0x7e, |
996 | 0x41, 0x0b, 0x78, 0x63, 0x0a, 0x09, 0xcf, 0xc7, |
997 | ].as_slice()), |
998 | (AlgorithmId::SHA384, [ |
999 | 0x1d, 0xd6, 0xf7, 0xb4, 0x57, 0xad, 0x88, 0x0d, |
1000 | 0x84, 0x0d, 0x41, 0xc9, 0x61, 0x28, 0x3b, 0xab, |
1001 | 0x68, 0x8e, 0x94, 0xe4, 0xb5, 0x93, 0x59, 0xea, |
1002 | 0x45, 0x68, 0x65, 0x81, 0xe9, 0x0f, 0xec, 0xce, |
1003 | 0xa3, 0xc6, 0x24, 0xb1, 0x22, 0x61, 0x13, 0xf8, |
1004 | 0x24, 0xf3, 0x15, 0xeb, 0x60, 0xae, 0x0a, 0x7c, |
1005 | ].as_slice()), |
1006 | (AlgorithmId::SHA512, [ |
1007 | 0x5e, 0xa7, 0x1d, 0xc6, 0xd0, 0xb4, 0xf5, 0x7b, |
1008 | 0xf3, 0x9a, 0xad, 0xd0, 0x7c, 0x20, 0x8c, 0x35, |
1009 | 0xf0, 0x6c, 0xd2, 0xba, 0xc5, 0xfd, 0xe2, 0x10, |
1010 | 0x39, 0x7f, 0x70, 0xde, 0x11, 0xd4, 0x39, 0xc6, |
1011 | 0x2e, 0xc1, 0xcd, 0xf3, 0x18, 0x37, 0x58, 0x86, |
1012 | 0x5f, 0xd3, 0x87, 0xfc, 0xea, 0x0b, 0xad, 0xa2, |
1013 | 0xf6, 0xc3, 0x7a, 0x4a, 0x17, 0x85, 0x1d, 0xd1, |
1014 | 0xd7, 0x8f, 0xef, 0xe6, 0xf2, 0x04, 0xee, 0x54, |
1015 | ].as_slice()), |
1016 | ] |
1017 | ); |
1018 | assert_eq!(entry.event_data, [0, 0]); |
1019 | |
1020 | // Entry 2 |
1021 | let entry = iter.next().unwrap(); |
1022 | assert_eq!(entry.pcr_index, PcrIndex(0)); |
1023 | assert_eq!(entry.event_type, EventType::EFI_PLATFORM_FIRMWARE_BLOB); |
1024 | #[rustfmt::skip] |
1025 | assert_eq!( |
1026 | entry.digests().into_iter().collect::<Vec<_>>(), |
1027 | [ |
1028 | (AlgorithmId::SHA1, [ |
1029 | 0xc7, 0x06, 0xe7, 0xdd, 0x36, 0x39, 0x29, 0x84, 0xeb, 0x06, |
1030 | 0xaa, 0xa0, 0x8f, 0xf3, 0x36, 0x84, 0x40, 0x77, 0xb3, 0xed, |
1031 | ].as_slice()), |
1032 | (AlgorithmId::SHA256, [ |
1033 | 0x3a, 0x30, 0x8e, 0x95, 0x87, 0x84, 0xbf, 0xd0, |
1034 | 0xf6, 0xe3, 0xf1, 0xbd, 0x4d, 0x42, 0x14, 0xd3, |
1035 | 0x0a, 0x4c, 0x55, 0x00, 0xa4, 0x5b, 0x06, 0xda, |
1036 | 0x96, 0xfc, 0x90, 0x33, 0x8f, 0x69, 0xb3, 0x61, |
1037 | ].as_slice()), |
1038 | (AlgorithmId::SHA384, [ |
1039 | 0xc0, 0xd0, 0x75, 0x96, 0xc5, 0x9a, 0x90, 0x7b, |
1040 | 0x79, 0x71, 0x6f, 0xc9, 0xf3, 0x6a, 0xad, 0x8f, |
1041 | 0x0f, 0x26, 0xf2, 0x02, 0x67, 0x5b, 0x42, 0x5a, |
1042 | 0x52, 0x3f, 0x72, 0xec, 0xb6, 0xf2, 0x53, 0x99, |
1043 | 0x57, 0xf0, 0xd9, 0x2c, 0x0a, 0x7d, 0xce, 0xaa, |
1044 | 0xf9, 0x9e, 0x60, 0x0e, 0x54, 0x18, 0xf1, 0xdc, |
1045 | ].as_slice()), |
1046 | (AlgorithmId::SHA512, [ |
1047 | 0x9a, 0xe9, 0x25, 0xdc, 0x9c, 0xd2, 0x9d, 0xf0, |
1048 | 0xe5, 0x80, 0x54, 0x35, 0xa5, 0x99, 0x06, 0x1f, |
1049 | 0xcf, 0x32, 0x98, 0xcc, 0x2a, 0x15, 0xe4, 0x87, |
1050 | 0x99, 0xa2, 0x0c, 0x9c, 0xe5, 0x6c, 0x8f, 0xe5, |
1051 | 0x84, 0x09, 0x75, 0xaf, 0xf0, 0xe1, 0xb6, 0x98, |
1052 | 0x20, 0x07, 0x5e, 0xe4, 0x29, 0x79, 0x8b, 0x5d, |
1053 | 0xbb, 0xe5, 0xd1, 0xa2, 0x74, 0x36, 0xab, 0x49, |
1054 | 0xf1, 0x9b, 0x7a, 0x04, 0x11, 0xd2, 0x96, 0x2c, |
1055 | ].as_slice()), |
1056 | ] |
1057 | ); |
1058 | #[rustfmt::skip] |
1059 | assert_eq!(entry.event_data, [ |
1060 | 0x00, 0x00, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, |
1061 | 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, |
1062 | ]); |
1063 | |
1064 | assert!(iter.next().is_none()); |
1065 | } |
1066 | } |
1067 | |