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