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