1 // Copyright 2023 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 use std::{
16 fs::File,
17 io::{Result, Write},
18 time::Duration,
19 };
20 macro_rules! be_vec {
21 ( $( $x:expr ),* ) => {
22 Vec::<u8>::new().iter().copied()
23 $( .chain($x.to_be_bytes()) )*
24 .collect()
25 };
26 }
27
28 pub enum PacketDirection {
29 HostToController = 0,
30 ControllerToHost = 1,
31 }
32
write_pcap_header(output: &mut File) -> Result<usize>33 pub fn write_pcap_header(output: &mut File) -> Result<usize> {
34 let linktype: u32 = 201; // LINKTYPE_BLUETOOTH_HCI_H4_WITH_PHDR
35
36 // https://tools.ietf.org/id/draft-gharris-opsawg-pcap-00.html#name-file-header
37 let header: Vec<u8> = be_vec![
38 0xa1b2c3d4u32, // magic number
39 2u16, // major version
40 4u16, // minor version
41 0u32, // reserved 1
42 0u32, // reserved 2
43 u32::MAX, // snaplen
44 linktype
45 ];
46
47 output.write_all(&header)?;
48 Ok(header.len())
49 }
50
append_record( timestamp: Duration, output: &mut File, packet_direction: PacketDirection, packet_type: u32, packet: &[u8], ) -> Result<usize>51 pub fn append_record(
52 timestamp: Duration,
53 output: &mut File,
54 packet_direction: PacketDirection,
55 packet_type: u32,
56 packet: &[u8],
57 ) -> Result<usize> {
58 // Record (direciton, type, packet)
59 let record: Vec<u8> = be_vec![packet_direction as u32, packet_type as u8];
60
61 // https://tools.ietf.org/id/draft-gharris-opsawg-pcap-00.html#name-packet-record
62 let length = record.len() + packet.len();
63 let header: Vec<u8> = be_vec![
64 timestamp.as_secs() as u32, // seconds
65 timestamp.subsec_micros(), // microseconds
66 length as u32, // Captured Packet Length
67 length as u32 // Original Packet Length
68 ];
69 let mut bytes = Vec::<u8>::with_capacity(header.len() + length);
70 bytes.extend(&header);
71 bytes.extend(&record);
72 bytes.extend(packet);
73 output.write_all(&bytes)?;
74 output.flush()?;
75 Ok(header.len() + length)
76 }
77
78 #[cfg(test)]
79 mod tests {
80 use std::{fs::File, io::Read, time::Duration};
81
82 use crate::captures::pcap_util::{append_record, PacketDirection};
83
84 use super::write_pcap_header;
85
86 static EXPECTED: &[u8; 76] = include_bytes!("sample.pcap");
87
88 #[test]
89 /// The test is done with the golden file sample.pcap with following packets:
90 /// Packet 1: HCI_EVT from Controller to Host (Sent Command Complete (LE Set Advertise Enable))
91 /// Packet 2: HCI_CMD from Host to Controller (Rcvd LE Set Advertise Enable) [250 milisecs later]
test_pcap_file()92 fn test_pcap_file() {
93 let mut temp_dir = std::env::temp_dir();
94 temp_dir.push("test.pcap");
95 if let Ok(mut file) = File::create(temp_dir.clone()) {
96 write_pcap_header(&mut file).unwrap();
97 append_record(
98 Duration::from_secs(0),
99 &mut file,
100 PacketDirection::HostToController,
101 4u32,
102 &[14, 4, 1, 10, 32, 0],
103 )
104 .unwrap();
105 append_record(
106 Duration::from_millis(250),
107 &mut file,
108 PacketDirection::ControllerToHost,
109 1u32,
110 &[10, 32, 1, 0],
111 )
112 .unwrap();
113 } else {
114 panic!("Cannot create temp file")
115 }
116 if let Ok(mut file) = File::open(temp_dir) {
117 let mut buffer = [0u8; 76];
118 #[allow(clippy::unused_io_amount)]
119 {
120 file.read(&mut buffer).unwrap();
121 }
122 assert_eq!(&buffer, EXPECTED);
123 } else {
124 panic!("Cannot create temp file")
125 }
126 }
127 }
128