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 //! A utility module for writing pcap files
16 //!
17 //! This module includes writing appropriate pcap headers with given
18 //! linktype and appending records with header based on the assigned
19 //! protocol.
20
21 use std::{
22 io::{Result, Write},
23 time::Duration,
24 };
25
26 macro_rules! ne_vec {
27 ( $( $x:expr ),* ) => {
28 Vec::<u8>::new().iter().copied()
29 $( .chain($x.to_ne_bytes()) )*
30 .collect()
31 };
32 }
33
34 macro_rules! be_vec {
35 ( $( $x:expr ),* ) => {
36 Vec::<u8>::new().iter().copied()
37 $( .chain($x.to_be_bytes()) )*
38 .collect()
39 };
40 }
41
42 macro_rules! le_vec {
43 ( $( $x:expr ),* ) => {
44 Vec::<u8>::new().iter().copied()
45 $( .chain($x.to_le_bytes()) )*
46 .collect()
47 };
48 }
49
50 /// The indication of packet direction for HCI packets.
51 pub enum PacketDirection {
52 /// Host To Controller as u32 value
53 HostToController = 0,
54 /// Controller to Host as u32 value
55 ControllerToHost = 1,
56 }
57
58 /// Supported LinkTypes for packet capture
59 /// https://www.tcpdump.org/linktypes.html
60 pub enum LinkType {
61 /// Radiotap link-layer information followed by an 802.11
62 /// header. Radiotap is used with mac80211_hwsim networking.
63 Ieee80211RadioTap = 127,
64 /// Bluetooth HCI UART transport layer
65 BluetoothHciH4WithPhdr = 201,
66 /// Ultra-wideband controller interface protocol
67 FiraUci = 299,
68 }
69
70 /// Returns the file size after writing the header of the
71 /// pcap file.
write_pcap_header<W: Write>(link_type: LinkType, output: &mut W) -> Result<usize>72 pub fn write_pcap_header<W: Write>(link_type: LinkType, output: &mut W) -> Result<usize> {
73 // https://tools.ietf.org/id/draft-gharris-opsawg-pcap-00.html#name-file-header
74 let header: Vec<u8> = ne_vec![
75 0xa1b2c3d4u32, // magic number
76 2u16, // major version
77 4u16, // minor version
78 0u32, // reserved 1
79 0u32, // reserved 2
80 u32::MAX, // snaplen
81 link_type as u32
82 ];
83
84 output.write_all(&header)?;
85 Ok(header.len())
86 }
87
88 /// Returns the file size after writing header of the
89 /// pcapng file
write_pcapng_header<W: Write>(link_type: LinkType, output: &mut W) -> Result<usize>90 pub fn write_pcapng_header<W: Write>(link_type: LinkType, output: &mut W) -> Result<usize> {
91 let header: Vec<u8> = le_vec![
92 // PCAPng files must start with a Section Header Block
93 0x0A0D0D0A_u32, // Block Type
94 28_u32, // Block Total Length
95 0x1A2B3C4D_u32, // Byte-Order Magic
96 1_u16, // Major Version
97 0_u16, // Minor Version
98 0xFFFFFFFFFFFFFFFF_u64, // Section Length (not specified)
99 28_u32, // Block Total Length
100 // Write the Interface Description Block used for all
101 // UCI records.
102 0x00000001_u32, // Block Type
103 20_u32, // Block Total Length
104 link_type as u16, // LinkType
105 0_u16, // Reserved
106 0_u32, // SnapLen (no limit)
107 20_u32 // Block Total Length
108 ];
109
110 output.write_all(&header)?;
111 Ok(header.len())
112 }
113
114 /// The BluetoothHciH4WithPhdr frame contains a 4-byte direction
115 /// field, followed by an HCI packet indicator byte, followed by an
116 /// HCI packet of the specified packet type.
wrap_bt_packet( packet_direction: PacketDirection, packet_type: u32, packet: &[u8], ) -> Vec<u8>117 pub fn wrap_bt_packet(
118 packet_direction: PacketDirection,
119 packet_type: u32,
120 packet: &[u8],
121 ) -> Vec<u8> {
122 let header: Vec<u8> = be_vec![packet_direction as u32, packet_type as u8];
123 let mut bytes = Vec::<u8>::with_capacity(header.len() + packet.len());
124 bytes.extend(&header);
125 bytes.extend(packet);
126 bytes
127 }
128
129 /// Returns the file size after appending a single packet record.
append_record<W: Write>( timestamp: Duration, output: &mut W, packet: &[u8], ) -> Result<usize>130 pub fn append_record<W: Write>(
131 timestamp: Duration,
132 output: &mut W,
133 packet: &[u8],
134 ) -> Result<usize> {
135 // https://tools.ietf.org/id/draft-gharris-opsawg-pcap-00.html#name-packet-record
136 let length = packet.len();
137 let header: Vec<u8> = ne_vec![
138 timestamp.as_secs() as u32, // seconds
139 timestamp.subsec_micros(), // microseconds
140 length as u32, // Captured Packet Length
141 length as u32 // Original Packet Length
142 ];
143 let mut bytes = Vec::<u8>::with_capacity(header.len() + length);
144 bytes.extend(&header);
145 bytes.extend(packet);
146 output.write_all(&bytes)?;
147 output.flush()?;
148 Ok(header.len() + length)
149 }
150
151 /// Returns the file size after appending a single packet record for pcapng.
append_record_pcapng<W: Write>( timestamp: Duration, output: &mut W, packet: &[u8], ) -> Result<usize>152 pub fn append_record_pcapng<W: Write>(
153 timestamp: Duration,
154 output: &mut W,
155 packet: &[u8],
156 ) -> Result<usize> {
157 let packet_data_padding: usize = 4 - packet.len() % 4;
158 let block_total_length: u32 = (packet.len() + packet_data_padding + 32) as u32;
159 let timestamp_micro = timestamp.as_micros() as u64;
160 // Wrap the packet inside an Enhanced Packet Block.
161 let header: Vec<u8> = le_vec![
162 0x00000006_u32, // Block Type
163 block_total_length, // Block Total Length
164 0_u32, // Interface ID
165 (timestamp_micro >> 32) as u32, // Timestamp Upper
166 (timestamp_micro & 0xFFFFFFFF_u64) as u32, // Timestamp Lower
167 packet.len() as u32, // Captured Packet Length
168 packet.len() as u32 // Original Packet Length
169 ];
170 output.write_all(&header)?;
171 output.write_all(packet)?;
172 output.write_all(&vec![0; packet_data_padding])?;
173 output.write_all(&block_total_length.to_le_bytes())?;
174 output.flush()?;
175 Ok(block_total_length as usize)
176 }
177
178 #[cfg(test)]
179 mod tests {
180 use std::time::Duration;
181
182 use super::*;
183
184 static EXPECTED_PCAP: &[u8; 76] = include_bytes!("sample.pcap");
185 static EXPECTED_PCAP_LE: &[u8; 76] = include_bytes!("sample_le.pcap");
186 static EXPECTED_PCAPNG: &[u8; 136] = include_bytes!("sample.pcapng");
187
is_little_endian() -> bool188 fn is_little_endian() -> bool {
189 0x12345678u32.to_le_bytes()[0] == 0x78
190 }
191
192 #[test]
193 /// The test is done with the golden file sample.pcap with following packets:
194 /// Packet 1: HCI_EVT from Controller to Host (Sent Command Complete (LE Set Advertise Enable))
195 /// Packet 2: HCI_CMD from Host to Controller (Rcvd LE Set Advertise Enable) [250 millisecs later]
test_pcap_file()196 fn test_pcap_file() {
197 let mut actual = Vec::<u8>::new();
198 write_pcap_header(LinkType::BluetoothHciH4WithPhdr, &mut actual).unwrap();
199 let _ = append_record(
200 Duration::from_secs(0),
201 &mut actual,
202 // H4_EVT_TYPE = 4
203 &wrap_bt_packet(PacketDirection::HostToController, 4, &[14, 4, 1, 10, 32, 0]),
204 )
205 .unwrap();
206 let _ = append_record(
207 Duration::from_millis(250),
208 &mut actual,
209 // H4_CMD_TYPE = 1
210 &wrap_bt_packet(PacketDirection::ControllerToHost, 1, &[10, 32, 1, 0]),
211 )
212 .unwrap();
213 match is_little_endian() {
214 true => assert_eq!(actual, EXPECTED_PCAP_LE),
215 false => assert_eq!(actual, EXPECTED_PCAP),
216 }
217 }
218
219 #[test]
220 // This test is done with the golden file sample.pcapng with following packets:
221 // Packet 1: UCI Core Get Device Info Cmd
222 // Packet 2: UCI Core Get Device Info Rsp [250 millisecs later]
test_pcapng_file()223 fn test_pcapng_file() {
224 let mut actual = Vec::<u8>::new();
225 write_pcapng_header(LinkType::FiraUci, &mut actual).unwrap();
226 let _ = append_record_pcapng(Duration::from_secs(0), &mut actual, &[32, 2, 0, 0]).unwrap();
227 let _ = append_record_pcapng(
228 Duration::from_millis(250),
229 &mut actual,
230 &[64, 2, 0, 10, 0, 2, 0, 1, 48, 1, 48, 1, 16, 0],
231 )
232 .unwrap();
233 assert_eq!(actual, EXPECTED_PCAPNG);
234 }
235 }
236