• 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 //! Packet Capture handlers and singleton for HTTP and gRPC server.
16 //!
17 //! This module implements a handler for GET, PATCH, LIST capture
18 //!
19 //! /v1/captures --> handle_capture_list
20 //! /v1/captures/{id} --> handle_capture_patch, handle_capture_get
21 //! handle_capture_cxx calls handle_capture, which calls handle_capture_* based on uri
22 //! handle_packet_request and handle_packet_response is invoked by packet_hub
23 //! to write packets to files if capture state is on.
24 
25 // TODO(b/274506882): Implement gRPC status proto on error responses. Also write better
26 // and more descriptive error messages with proper error codes.
27 
28 use cxx::CxxVector;
29 use frontend_proto::common::ChipKind;
30 use frontend_proto::frontend::{GetDevicesResponse, ListCaptureResponse};
31 use lazy_static::lazy_static;
32 use netsim_common::util::time_display::TimeDisplay;
33 use protobuf::Message;
34 use protobuf_json_mapping::{print_to_string_with_options, PrintOptions};
35 use std::collections::HashSet;
36 use std::fs::File;
37 use std::io::{Read, Result};
38 use std::pin::Pin;
39 use std::sync::RwLock;
40 use std::time::{SystemTime, UNIX_EPOCH};
41 
42 use crate::captures::capture::{Captures, ChipId};
43 use crate::ffi::{get_devices_bytes, CxxServerResponseWriter};
44 use crate::http_server::http_request::{HttpHeaders, HttpRequest};
45 use crate::http_server::server_response::ResponseWritable;
46 use crate::CxxServerResponseWriterWrapper;
47 
48 use super::capture::CaptureInfo;
49 use super::pcap_util::{append_record, PacketDirection};
50 use super::PCAP_MIME_TYPE;
51 
52 const CHUNK_LEN: usize = 1_048_576;
53 const JSON_PRINT_OPTION: PrintOptions = PrintOptions {
54     enum_values_int: false,
55     proto_field_name: false,
56     always_output_default_values: true,
57     _future_options: (),
58 };
59 
60 // The Capture resource is a singleton that manages all captures
61 lazy_static! {
62     static ref RESOURCE: RwLock<Captures> = RwLock::new(Captures::new());
63 }
64 
65 // Update the Captures collection to reflect the currently connected devices.
66 // This function removes entries from Captures when devices/chips
67 // go away and adds entries when new devices/chips connect.
68 //
69 // Note: if a device disconnects and there is captured data, the entry
70 // remains with a flag valid = false so it can be retrieved.
update_captures(captures: &mut Captures)71 fn update_captures(captures: &mut Captures) {
72     // Perform get_devices_bytes ffi to receive bytes of GetDevicesResponse
73     // Print error and return empty hashmap if GetDevicesBytes fails.
74     let mut vec = Vec::<u8>::new();
75     if !get_devices_bytes(&mut vec) {
76         println!("netsim error: GetDevicesBytes failed - returning an empty set of captures");
77         return;
78     }
79 
80     // Parse get_devices_response
81     let device_response = GetDevicesResponse::parse_from_bytes(&vec).unwrap();
82 
83     // Adding to Captures hashmap
84     let mut chip_ids = HashSet::<ChipId>::new();
85     for device in device_response.devices {
86         for chip in device.chips {
87             chip_ids.insert(chip.id);
88             if !captures.contains(chip.id) {
89                 let capture = CaptureInfo::new(
90                     chip.kind.enum_value_or_default(),
91                     chip.id,
92                     device.name.clone(),
93                 );
94                 captures.insert(capture);
95             }
96         }
97     }
98 
99     // Two cases when device gets disconnected:
100     // 1. The device had no capture, remove completely.
101     // 2. The device had capture, indicate by capture.set_valid(false)
102     enum RemovalIndicator {
103         Gone(ChipId),   // type ChipId = i32
104         Unused(ChipId), // type ChipId = i32
105     }
106 
107     // Check if the active_capture entry still exists in the chips.
108     let mut removal = Vec::<RemovalIndicator>::new();
109     for (chip_id, capture) in captures.iter() {
110         let lock = capture.lock().unwrap();
111         let proto_capture = lock.get_capture_proto();
112         if !chip_ids.contains(chip_id) {
113             if proto_capture.size == 0 {
114                 removal.push(RemovalIndicator::Unused(chip_id.to_owned()));
115             } else {
116                 removal.push(RemovalIndicator::Gone(chip_id.to_owned()))
117             }
118         }
119     }
120 
121     // Now remove/update the captures based on the loop above
122     for indicator in removal {
123         match indicator {
124             RemovalIndicator::Unused(key) => captures.remove(&key),
125             RemovalIndicator::Gone(key) => {
126                 for capture in captures.get(key).iter() {
127                     capture.lock().unwrap().valid = false;
128                 }
129             }
130         }
131     }
132 }
133 
134 // Helper function for getting file name from the given fields.
get_file(id: ChipId, device_name: String, chip_kind: ChipKind) -> Result<File>135 fn get_file(id: ChipId, device_name: String, chip_kind: ChipKind) -> Result<File> {
136     let mut filename = std::env::temp_dir();
137     filename.push("netsim-pcaps");
138     filename.push(format!("{:?}-{:}-{:?}.pcap", id, device_name, chip_kind));
139     File::open(filename)
140 }
141 
142 // TODO: GetCapture should return the information of the capture. Need to reconsider
143 // uri hierarchy.
144 // GET /captures/id/{id} --> Get Capture information
145 // GET /captures/contents/{id} --> Download Pcap file
handle_capture_get(writer: ResponseWritable, captures: &mut Captures, id: ChipId)146 pub fn handle_capture_get(writer: ResponseWritable, captures: &mut Captures, id: ChipId) {
147     // Get the most updated active captures
148     update_captures(captures);
149 
150     if let Some(capture) = captures.get(id).map(|arc_capture| arc_capture.lock().unwrap()) {
151         if capture.size == 0 {
152             writer.put_error(404, "Capture file not found");
153         } else if let Ok(mut file) = get_file(id, capture.device_name.clone(), capture.chip_kind) {
154             let mut buffer = [0u8; CHUNK_LEN];
155             let time_display = TimeDisplay::new(capture.seconds, capture.nanos as u32);
156             let header_value = format!(
157                 "attachment; filename=\"{:?}-{:}-{:?}-{}.pcap\"",
158                 id,
159                 capture.device_name.clone(),
160                 capture.chip_kind,
161                 time_display.utc_display()
162             );
163             writer.put_ok_with_length(
164                 PCAP_MIME_TYPE,
165                 capture.size,
166                 &[("Content-Disposition", header_value.as_str())],
167             );
168             loop {
169                 match file.read(&mut buffer) {
170                     Ok(0) => break,
171                     Ok(length) => writer.put_chunk(&buffer[..length]),
172                     Err(_) => writer.put_error(404, "Error reading pcap file"),
173                 }
174             }
175         } else {
176             writer.put_error(404, "Cannot open Capture file");
177         }
178     } else {
179         writer.put_error(404, "Cannot access Capture Resource")
180     }
181 }
182 
handle_capture_list(writer: ResponseWritable, captures: &mut Captures)183 pub fn handle_capture_list(writer: ResponseWritable, captures: &mut Captures) {
184     // Get the most updated active captures
185     update_captures(captures);
186 
187     // Instantiate ListCaptureResponse and add Captures
188     let mut response = ListCaptureResponse::new();
189     for capture in captures.values() {
190         response.captures.push(capture.lock().unwrap().get_capture_proto());
191     }
192 
193     // Perform protobuf-json-mapping with the given protobuf
194     if let Ok(json_response) = print_to_string_with_options(&response, &JSON_PRINT_OPTION) {
195         writer.put_ok("text/json", &json_response, &[])
196     } else {
197         writer.put_error(404, "proto to JSON mapping failure")
198     }
199 }
200 
handle_capture_patch( writer: ResponseWritable, captures: &mut Captures, id: ChipId, state: bool, )201 pub fn handle_capture_patch(
202     writer: ResponseWritable,
203     captures: &mut Captures,
204     id: ChipId,
205     state: bool,
206 ) {
207     // Get the most updated active captures
208     update_captures(captures);
209 
210     if let Some(mut capture) = captures.get(id).map(|arc_capture| arc_capture.lock().unwrap()) {
211         match state {
212             true => {
213                 if let Err(err) = capture.start_capture() {
214                     writer.put_error(404, err.to_string().as_str());
215                     return;
216                 }
217             }
218             false => capture.stop_capture(),
219         }
220 
221         // Perform protobuf-json-mapping with the given protobuf
222         if let Ok(json_response) =
223             print_to_string_with_options(&capture.get_capture_proto(), &JSON_PRINT_OPTION)
224         {
225             writer.put_ok("text/json", &json_response, &[]);
226         } else {
227             writer.put_error(404, "proto to JSON mapping failure");
228         }
229     }
230 }
231 
232 /// The Rust capture handler used directly by Http frontend for LIST, GET, and PATCH
handle_capture(request: &HttpRequest, param: &str, writer: ResponseWritable)233 pub fn handle_capture(request: &HttpRequest, param: &str, writer: ResponseWritable) {
234     if request.uri.as_str() == "/v1/captures" {
235         match request.method.as_str() {
236             "GET" => {
237                 let mut captures = RESOURCE.write().unwrap();
238                 handle_capture_list(writer, &mut captures);
239             }
240             _ => writer.put_error(404, "Not found."),
241         }
242     } else {
243         match request.method.as_str() {
244             "GET" => {
245                 let mut captures = RESOURCE.write().unwrap();
246                 let id = match param.parse::<i32>() {
247                     Ok(num) => num,
248                     Err(_) => {
249                         writer.put_error(404, "Incorrect ID type for capture, ID should be i32.");
250                         return;
251                     }
252                 };
253                 handle_capture_get(writer, &mut captures, id);
254             }
255             "PATCH" => {
256                 let mut captures = RESOURCE.write().unwrap();
257                 let id = match param.parse::<i32>() {
258                     Ok(num) => num,
259                     Err(_) => {
260                         writer.put_error(404, "Incorrect ID type for capture, ID should be i32.");
261                         return;
262                     }
263                 };
264                 let body = &request.body;
265                 let state = String::from_utf8(body.to_vec()).unwrap();
266                 match state.as_str() {
267                     "1" => handle_capture_patch(writer, &mut captures, id, true),
268                     "2" => handle_capture_patch(writer, &mut captures, id, false),
269                     _ => writer.put_error(404, "Incorrect state for PatchCapture"),
270                 }
271             }
272             _ => writer.put_error(404, "Not found."),
273         }
274     }
275 }
276 
277 /// capture handle cxx for grpc server to call
handle_capture_cxx( responder: Pin<&mut CxxServerResponseWriter>, method: String, param: String, body: String, )278 pub fn handle_capture_cxx(
279     responder: Pin<&mut CxxServerResponseWriter>,
280     method: String,
281     param: String,
282     body: String,
283 ) {
284     let mut request = HttpRequest {
285         method,
286         uri: String::new(),
287         headers: HttpHeaders::new(),
288         version: "1.1".to_string(),
289         body: body.as_bytes().to_vec(),
290     };
291     if param.is_empty() {
292         request.uri = "/v1/captures".to_string();
293     } else {
294         request.uri = format!("/v1/captures/{}", param);
295     }
296     handle_capture(
297         &request,
298         param.as_str(),
299         &mut CxxServerResponseWriterWrapper { writer: responder },
300     );
301 }
302 
303 // Helper function for translating u32 representation of ChipKind
int_to_chip_kind(kind: u32) -> ChipKind304 fn int_to_chip_kind(kind: u32) -> ChipKind {
305     match kind {
306         1 => ChipKind::BLUETOOTH,
307         2 => ChipKind::WIFI,
308         3 => ChipKind::UWB,
309         _ => ChipKind::UNSPECIFIED,
310     }
311 }
312 
313 // A common code for handle_request and handle_response cxx mehtods
handle_packet( kind: u32, facade_id: u32, packet: &CxxVector<u8>, packet_type: u32, direction: PacketDirection, )314 fn handle_packet(
315     kind: u32,
316     facade_id: u32,
317     packet: &CxxVector<u8>,
318     packet_type: u32,
319     direction: PacketDirection,
320 ) {
321     let captures = RESOURCE.read().unwrap();
322     let facade_key = CaptureInfo::new_facade_key(int_to_chip_kind(kind), facade_id as i32);
323     if let Some(mut capture) = captures
324         .facade_key_to_capture
325         .get(&facade_key)
326         .map(|arc_capture| arc_capture.lock().unwrap())
327     {
328         if let Some(ref mut file) = capture.file {
329             if int_to_chip_kind(kind) == ChipKind::BLUETOOTH {
330                 let timestamp =
331                     SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards");
332                 match append_record(timestamp, file, direction, packet_type, packet.as_slice()) {
333                     Ok(size) => {
334                         capture.size += size;
335                         capture.records += 1;
336                     }
337                     Err(err) => {
338                         println!("netsimd: {err:?}");
339                     }
340                 }
341             }
342         }
343     };
344 }
345 
346 // Cxx Method for packet_hub to invoke (Host to Controller Packet Flow)
handle_packet_request(kind: u32, facade_id: u32, packet: &CxxVector<u8>, packet_type: u32)347 pub fn handle_packet_request(kind: u32, facade_id: u32, packet: &CxxVector<u8>, packet_type: u32) {
348     handle_packet(kind, facade_id, packet, packet_type, PacketDirection::HostToController)
349 }
350 
351 // Cxx Method for packet_hub to invoke (Controller to Host Packet Flow)
handle_packet_response(kind: u32, facade_id: u32, packet: &CxxVector<u8>, packet_type: u32)352 pub fn handle_packet_response(kind: u32, facade_id: u32, packet: &CxxVector<u8>, packet_type: u32) {
353     handle_packet(kind, facade_id, packet, packet_type, PacketDirection::ControllerToHost)
354 }
355 
356 // Cxx Method for clearing pcap files in temp directory
clear_pcap_files() -> bool357 pub fn clear_pcap_files() -> bool {
358     let mut path = std::env::temp_dir();
359     path.push("netsim-pcaps");
360 
361     // Check if the directory exists.
362     if std::fs::metadata(&path).is_err() {
363         return false;
364     }
365 
366     // Delete the directory.
367     std::fs::remove_dir_all(&path).is_ok()
368 }
369