• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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