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