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 //! The internal structure of CaptureInfo and CaptureMaps
16 //!
17 //! CaptureInfo is the internal structure of any Capture that includes
18 //! the protobuf structure. CaptureMaps contains mappings of
19 //! ChipIdentifier and <FacadeIdentifier, Kind> to CaptureInfo.
20
21 use bytes::Bytes;
22
23 use std::collections::btree_map::{Iter, Values};
24 use std::collections::BTreeMap;
25 use std::fs::{File, OpenOptions};
26 use std::io::{Error, ErrorKind, Result};
27 use std::sync::mpsc::channel;
28 use std::sync::mpsc::{Receiver, Sender};
29 use std::sync::{Arc, Mutex};
30 use std::thread;
31 use std::time::{SystemTime, UNIX_EPOCH};
32
33 use super::pcap_util::{write_pcap_header, write_pcapng_header, LinkType};
34 use log::{info, warn};
35
36 use netsim_proto::{common::ChipKind, model::Capture as ProtoCapture};
37 use protobuf::well_known_types::timestamp::Timestamp;
38
39 use crate::config::get_pcap;
40 use crate::events::{ChipAdded, ChipRemoved, Event};
41 use crate::resource::clone_captures;
42
43 use crate::captures::captures_handler::handle_packet;
44 use crate::captures::pcap_util::PacketDirection;
45 use crate::devices::chip::ChipIdentifier;
46
47 /// Internal Capture struct
48 pub struct CaptureInfo {
49 /// Some(File) if the file is opened and capture is actively happening.
50 /// None if the file is not opened.
51 pub file: Option<File>,
52 // Following items will be returned as ProtoCapture. (state: file.is_some())
53 id: ChipIdentifier,
54 /// ChipKind (BLUETOOTH, WIFI, or UWB)
55 pub chip_kind: ChipKind,
56 /// Device name
57 pub device_name: String,
58 /// Size of pcap file
59 pub size: usize,
60 /// Number of packet records
61 pub records: i32,
62 /// Timestamp as seconds
63 pub seconds: i64,
64 /// Timestamp as sub-nanoseconds
65 pub nanos: i32,
66 /// Boolean status of whether the device is connected to netsim
67 pub valid: bool,
68 /// Extension (pcap or pcapng)
69 pub extension: String,
70 }
71
72 /// Captures contains a recent copy of all chips and their ChipKind, chip_id,
73 /// and owning device name.
74 ///
75 /// Information for any recent or ongoing captures is also stored in the ProtoCapture.
76 pub struct Captures {
77 /// A mapping of chip id to CaptureInfo.
78 ///
79 /// BTreeMap is used for chip_id_to_capture, so that the CaptureInfo can always be
80 /// ordered by ChipId. ListCaptureResponse will produce a ordered list of CaptureInfos.
81 pub chip_id_to_capture: BTreeMap<ChipIdentifier, Arc<Mutex<CaptureInfo>>>,
82 sender: Sender<CapturePacket>,
83 }
84
85 impl CaptureInfo {
86 /// Create an instance of CaptureInfo
new(chip_kind: ChipKind, chip_id: ChipIdentifier, device_name: String) -> Self87 pub fn new(chip_kind: ChipKind, chip_id: ChipIdentifier, device_name: String) -> Self {
88 let extension = match chip_kind {
89 ChipKind::UWB => "pcapng".to_string(),
90 _ => "pcap".to_string(),
91 };
92 CaptureInfo {
93 id: chip_id,
94 chip_kind,
95 device_name,
96 size: 0,
97 records: 0,
98 seconds: 0,
99 nanos: 0,
100 valid: true,
101 file: None,
102 extension,
103 }
104 }
105
106 /// Creates a pcap file with headers and store it under temp directory.
107 ///
108 /// The lifecycle of the file is NOT tied to the lifecycle of the struct
109 /// Format: /tmp/netsimd/$USER/pcaps/netsim-{chip_id}-{device_name}-{chip_kind}.{extension}
start_capture(&mut self) -> Result<()>110 pub fn start_capture(&mut self) -> Result<()> {
111 if self.file.is_some() {
112 return Ok(());
113 }
114 let mut filename = netsim_common::system::netsimd_temp_dir();
115 filename.push("pcaps");
116 std::fs::create_dir_all(&filename)?;
117 filename.push(format!(
118 "netsim-{:?}-{:}-{:?}.{}",
119 self.id, self.device_name, self.chip_kind, self.extension
120 ));
121 let mut file = OpenOptions::new().write(true).truncate(true).create(true).open(filename)?;
122 let link_type = match self.chip_kind {
123 ChipKind::BLUETOOTH => LinkType::BluetoothHciH4WithPhdr,
124 ChipKind::BLUETOOTH_BEACON => LinkType::BluetoothHciH4WithPhdr,
125 ChipKind::WIFI => LinkType::Ieee80211RadioTap,
126 ChipKind::UWB => LinkType::FiraUci,
127 _ => return Err(Error::new(ErrorKind::Other, "Unsupported link type")),
128 };
129 let size = match self.extension.as_str() {
130 "pcap" => write_pcap_header(link_type, &mut file)?,
131 "pcapng" => write_pcapng_header(link_type, &mut file)?,
132 _ => return Err(Error::new(ErrorKind::Other, "Incorrect Extension for file")),
133 };
134 let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards");
135 self.size = size;
136 self.records = 0;
137 self.seconds = timestamp.as_secs() as i64;
138 self.nanos = timestamp.subsec_nanos() as i32;
139 self.file = Some(file);
140 Ok(())
141 }
142
143 /// Closes file by removing ownership of self.file.
144 ///
145 /// Capture info will still retain the size and record count
146 /// So it can be downloaded easily when GetCapture is invoked.
stop_capture(&mut self)147 pub fn stop_capture(&mut self) {
148 self.file = None;
149 }
150
151 /// Returns a Capture protobuf from CaptureInfo
get_capture_proto(&self) -> ProtoCapture152 pub fn get_capture_proto(&self) -> ProtoCapture {
153 let timestamp =
154 Timestamp { seconds: self.seconds, nanos: self.nanos, ..Default::default() };
155 ProtoCapture {
156 id: self.id.0,
157 chip_kind: self.chip_kind.into(),
158 device_name: self.device_name.clone(),
159 state: Some(self.file.is_some()),
160 size: self.size as i32,
161 records: self.records,
162 timestamp: Some(timestamp).into(),
163 valid: self.valid,
164 ..Default::default()
165 }
166 }
167 }
168
169 struct CapturePacket {
170 chip_id: ChipIdentifier,
171 packet: Bytes,
172 packet_type: u32,
173 direction: PacketDirection,
174 }
175
176 impl Captures {
177 /// Create an instance of Captures, which includes 2 empty hashmaps
new() -> Self178 pub fn new() -> Self {
179 let (sender, rx) = channel::<CapturePacket>();
180 let _ =
181 thread::Builder::new().name("capture_packet_handler".to_string()).spawn(move || {
182 while let Ok(CapturePacket { chip_id, packet, packet_type, direction }) = rx.recv()
183 {
184 handle_packet(chip_id, &packet, packet_type, direction);
185 }
186 });
187 Captures {
188 chip_id_to_capture: BTreeMap::<ChipIdentifier, Arc<Mutex<CaptureInfo>>>::new(),
189 sender,
190 }
191 }
192
193 /// Sends a captured packet to the output processing thread.
send( &self, chip_id: ChipIdentifier, packet: &Bytes, packet_type: u32, direction: PacketDirection, )194 pub fn send(
195 &self,
196 chip_id: ChipIdentifier,
197 packet: &Bytes,
198 packet_type: u32,
199 direction: PacketDirection,
200 ) {
201 let _ = self.sender.send(CapturePacket {
202 chip_id,
203 packet: packet.clone(),
204 packet_type,
205 direction,
206 });
207 }
208
209 /// Returns true if key exists in Captures.chip_id_to_capture
contains(&self, key: ChipIdentifier) -> bool210 pub fn contains(&self, key: ChipIdentifier) -> bool {
211 self.chip_id_to_capture.contains_key(&key)
212 }
213
214 /// Returns an Option of lockable and mutable CaptureInfo with given key
get(&mut self, key: ChipIdentifier) -> Option<&mut Arc<Mutex<CaptureInfo>>>215 pub fn get(&mut self, key: ChipIdentifier) -> Option<&mut Arc<Mutex<CaptureInfo>>> {
216 self.chip_id_to_capture.get_mut(&key)
217 }
218
219 /// Inserts the given CatpureInfo into Captures hashmaps
insert(&mut self, capture: CaptureInfo)220 pub fn insert(&mut self, capture: CaptureInfo) {
221 let chip_id = capture.id;
222 let arc_capture = Arc::new(Mutex::new(capture));
223 self.chip_id_to_capture.insert(chip_id, arc_capture.clone());
224 }
225
226 /// Returns true if chip_id_to_capture is empty
is_empty(&self) -> bool227 pub fn is_empty(&self) -> bool {
228 self.chip_id_to_capture.is_empty()
229 }
230
231 /// Returns an iterable object of chip_id_to_capture hashmap
iter(&self) -> Iter<ChipIdentifier, Arc<Mutex<CaptureInfo>>>232 pub fn iter(&self) -> Iter<ChipIdentifier, Arc<Mutex<CaptureInfo>>> {
233 self.chip_id_to_capture.iter()
234 }
235
236 /// Removes a CaptureInfo with given key from Captures
237 ///
238 /// When Capture is removed, remove from each map and also invoke closing of files.
remove(&mut self, key: &ChipIdentifier)239 pub fn remove(&mut self, key: &ChipIdentifier) {
240 if let Some(arc_capture) = self.chip_id_to_capture.get(key) {
241 let mut capture = arc_capture.lock().expect("Failed to acquire lock on CaptureInfo");
242 // Valid is marked false when chip is disconnected from netsim
243 capture.valid = false;
244 capture.stop_capture();
245 } else {
246 info!("key does not exist in Captures");
247 }
248 // CaptureInfo is not removed even after chip is removed
249 }
250
251 /// Returns Values of chip_id_to_capture hashmap values
values(&self) -> Values<ChipIdentifier, Arc<Mutex<CaptureInfo>>>252 pub fn values(&self) -> Values<ChipIdentifier, Arc<Mutex<CaptureInfo>>> {
253 self.chip_id_to_capture.values()
254 }
255 }
256
257 impl Default for Captures {
default() -> Self258 fn default() -> Self {
259 Self::new()
260 }
261 }
262
263 /// Create a thread to process events that matter to the Capture resource.
264 ///
265 /// We maintain a CaptureInfo for each chip that has been
266 /// connected to the simulation. This procedure monitors ChipAdded
267 /// and ChipRemoved events and updates the collection of CaptureInfo.
268 ///
spawn_capture_event_subscriber(event_rx: Receiver<Event>)269 pub fn spawn_capture_event_subscriber(event_rx: Receiver<Event>) {
270 let _ =
271 thread::Builder::new().name("capture_event_subscriber".to_string()).spawn(move || loop {
272 match event_rx.recv() {
273 Ok(Event::ChipAdded(ChipAdded { chip_id, chip_kind, device_name, .. })) => {
274 let mut capture_info =
275 CaptureInfo::new(chip_kind, chip_id, device_name.clone());
276 if get_pcap() {
277 if let Err(err) = capture_info.start_capture() {
278 warn!("{err:?}");
279 }
280 }
281 clone_captures().write().unwrap().insert(capture_info);
282 info!("Capture event: ChipAdded chip_id: {chip_id} device_name: {device_name}");
283 }
284 Ok(Event::ChipRemoved(ChipRemoved { chip_id, .. })) => {
285 clone_captures().write().unwrap().remove(&chip_id);
286 info!("Capture event: ChipRemoved chip_id: {chip_id}");
287 }
288 _ => {}
289 }
290 });
291 }
292