blob: 1a55b6d167be02e9a83ef997d3e0e12b3972e121 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use failure::Error;
use fuchsia_bluetooth::error::Error as BTError;
use fuchsia_syslog::macros::*;
use futures::channel::mpsc;
use parking_lot::{Mutex, RwLock};
use rouille::{self, router, Request, Response};
use serde;
use serde_json;
use serde_json::{to_value, Value};
use std;
use std::collections::HashMap;
use std::io::Read;
use std::sync::Arc;
// Standardized sl4f types and constants
use crate::server::constants::{COMMAND_DELIMITER, COMMAND_SIZE};
use crate::server::sl4f_types::{
AsyncRequest, AsyncResponse, ClientData, CommandRequest, CommandResponse,
};
// Bluetooth related includes
use crate::bluetooth::ble_advertise_facade::BleAdvertiseFacade;
use crate::bluetooth::facade::BluetoothFacade;
use crate::bluetooth::gatt_client_facade::GattClientFacade;
use crate::bluetooth::gatt_server_facade::GattServerFacade;
// Netstack related includes
use crate::netstack::facade::NetstackFacade;
// Wlan related includes
use crate::wlan::facade::WlanFacade;
pub mod macros {
pub use crate::dtag;
}
// Create a short way to describe tags within facades.
//
// dtag is short for "descriptive tag"
//
// While using the dtag macro within a custom facade, the log
// tag will print as such:
// TestFacade::test_func:123
// TestFacade - is the class.
// test_func - is the function where the log message was called from.
// 123 - is the line number the log message was called from.
//
// Example usage and output:
// Class TestFacade {
// pub fn test_func {
// fx_log_info!(tag: &dtag!(), "tests this random func.");
// }
//
// "fx syslog" output:
// [sl4f, TestFacade::test_func:123] INFO: tests this random func.
#[macro_export]
macro_rules! dtag {
() => {{
fn f() {}
fn type_name_of<T>(_: T) -> &'static str {
extern crate core;
unsafe { core::intrinsics::type_name::<T>() }
}
let name = type_name_of(f);
// In some circumstances an additional ::{{closure}} tag is added
// to the function name. Remove it.
let re = Regex::new(r"::\{\{closure\}\}").unwrap();
let mut result = re.replace_all(name, "").to_string();
// Remove the full module path as we only want the class and
// function being called for less verbosity.
let mut prefix_to_remove = module_path!().to_string() + &"::".to_string();
// Remove beginning "sl4f::" from the prefix_to_remove.
prefix_to_remove = prefix_to_remove[6..prefix_to_remove.len()].to_string();
let re = Regex::new(&prefix_to_remove).unwrap();
result = re.replace_all(&result, "").to_string();
// Remove beginning "fn() {" and ending "::f}".
result = result[6..result.len() - 4].to_string();
let line = line!();
// Return and add line number to the tag.
let result = result + &":".to_string() + &line.to_string();
result
}};
}
/// Sl4f object. This stores all information about state for each connectivity stack.
/// Every session will have a new Sl4f object.
/// For example, to add WLAN stack support, add "wlan_facade" to the struct definition and update
/// the impl functions. Then, update method_to_fidl() to support the "wlan" method type.
#[derive(Debug, Clone)]
pub struct Sl4f {
// bt_facade: Thread safe object for state for ble functions.
ble_advertise_facade: Arc<BleAdvertiseFacade>,
// bt_facade: Thread safe object for state for bluetooth connectivity tests
bt_facade: Arc<RwLock<BluetoothFacade>>,
// gatt_client_facade: Thread safe object for state for Gatt Client tests
gatt_client_facade: Arc<GattClientFacade>,
// gatt_server_facade: Thread safe object for state for Gatt Server tests
gatt_server_facade: Arc<GattServerFacade>,
// netstack_facade: Thread safe object for state for netstack functions.
netstack_facade: Arc<NetstackFacade>,
// wlan_facade: Thread safe object for state for wlan connectivity tests
wlan_facade: Arc<WlanFacade>,
// clients: Thread safe map for clients that are connected to the sl4f server.
// key = session_id (unique for every ACTS instance) and value = Data about client (see
// sl4f_types.rs)
clients: Arc<Mutex<HashMap<String, Vec<ClientData>>>>,
}
impl Sl4f {
pub fn new() -> Result<Arc<RwLock<Sl4f>>, Error> {
let ble_advertise_facade = Arc::new(BleAdvertiseFacade::new());
let gatt_client_facade = Arc::new(GattClientFacade::new());
let gatt_server_facade = Arc::new(GattServerFacade::new());
let netstack_facade = Arc::new(NetstackFacade::new());
let wlan_facade = Arc::new(WlanFacade::new()?);
Ok(Arc::new(RwLock::new(Sl4f {
ble_advertise_facade,
bt_facade: BluetoothFacade::new(),
gatt_client_facade,
gatt_server_facade,
netstack_facade,
wlan_facade,
clients: Arc::new(Mutex::new(HashMap::new())),
})))
}
pub fn get_netstack_facade(&self) -> Arc<NetstackFacade> {
self.netstack_facade.clone()
}
pub fn get_ble_advertise_facade(&self) -> Arc<BleAdvertiseFacade> {
self.ble_advertise_facade.clone()
}
pub fn get_gatt_client_facade(&self) -> Arc<GattClientFacade> {
self.gatt_client_facade.clone()
}
pub fn get_gatt_server_facade(&self) -> Arc<GattServerFacade> {
self.gatt_server_facade.clone()
}
pub fn get_bt_facade(&self) -> Arc<RwLock<BluetoothFacade>> {
self.bt_facade.clone()
}
pub fn get_wlan_facade(&self) -> Arc<WlanFacade> {
self.wlan_facade.clone()
}
pub fn get_clients(&self) -> Arc<Mutex<HashMap<String, Vec<ClientData>>>> {
self.clients.clone()
}
pub fn cleanup_clients(&self) {
self.clients.lock().clear();
}
pub fn cleanup(&mut self) {
BluetoothFacade::cleanup(self.bt_facade.clone());
self.ble_advertise_facade.cleanup();
self.gatt_client_facade.cleanup();
self.gatt_server_facade.cleanup();
self.cleanup_clients();
}
pub fn print_clients(&self) {
fx_log_info!("SL4F Clients: {:?}", self.clients);
}
// Add *_facade.print() when new Facade objects are added (i.e WlanFacade)
pub fn print(&self) {
self.bt_facade.read().print();
self.ble_advertise_facade.print();
self.gatt_client_facade.print();
self.gatt_server_facade.print();
}
}
// Handles all incoming requests to SL4F server, routes accordingly
pub fn serve(
request: &Request, sl4f_session: Arc<RwLock<Sl4f>>,
rouille_sender: mpsc::UnboundedSender<AsyncRequest>,
) -> Response {
router!(request,
(GET) (/) => {
// Parse the command request
fx_log_info!(tag: "serve", "Received command request.");
client_request(sl4f_session.clone(), &request, rouille_sender.clone())
},
(GET) (/init) => {
// Initialize a client
fx_log_info!(tag: "serve", "Received init request.");
client_init(&request, sl4f_session.write().get_clients().clone())
},
(GET) (/print_clients) => {
// Print information about all clients
fx_log_info!(tag: "serve", "Received print client request.");
const PRINT_ACK: &str = "Successfully printed clients.";
sl4f_session.read().print_clients();
rouille::Response::json(&PRINT_ACK)
},
(GET) (/cleanup) => {
fx_log_info!(tag: "serve", "Received server cleanup request.");
server_cleanup(&request, sl4f_session.clone())
},
_ => {
fx_log_err!(tag: "serve", "Received unknown server request.");
const FAIL_REQUEST_ACK: &str = "Unknown GET request.";
let res = CommandResponse::new("".to_string(), None, serde::export::Some(FAIL_REQUEST_ACK.to_string()));
rouille::Response::json(&res)
}
)
}
// Given the session id, method id, and result of FIDL call, store the result for this client
fn store_response(
sl4f_session: Arc<RwLock<Sl4f>>, client_id: String, method_id: String, result: AsyncResponse,
) {
let clients = sl4f_session.write().clients.clone();
// If the current client session is found, append the result of the FIDL call to the result
// history
if clients.lock().contains_key(&client_id) {
let command_response = ClientData::new(method_id.clone(), result.clone());
clients
.lock()
.entry(client_id.clone())
.or_insert(Vec::new())
.push(command_response);
} else {
fx_log_err!(tag: "store_response", "Client doesn't exist in server database: {:?}", client_id);
}
fx_log_info!(tag: "store_response", "Stored response. Updated clients: {:?}", clients);
}
// Given the request, map the test request to a FIDL query and execute
// asynchronously
fn client_request(
sl4f_session: Arc<RwLock<Sl4f>>, request: &Request,
rouille_sender: mpsc::UnboundedSender<AsyncRequest>,
) -> Response {
const FAIL_TEST_ACK: &str = "Command failed";
let (session_id, method_id, method_type, method_name, method_params) =
match parse_request(request) {
Ok(res) => res,
Err(e) => {
fx_log_err!(tag: "client_request", "Failed to parse request. {:?}", e);
return Response::json(&FAIL_TEST_ACK);
}
};
// Create channel for async thread to respond to
// Package response and ship over JSON RPC
let (async_sender, rouille_receiver) = std::sync::mpsc::channel();
let req = AsyncRequest::new(
async_sender,
method_id.clone(),
method_type,
method_name,
method_params,
);
rouille_sender
.unbounded_send(req)
.expect("Failed to send request to async thread.");
let resp: AsyncResponse = rouille_receiver.recv().unwrap();
store_response(
sl4f_session,
session_id.clone(),
method_id.clone(),
resp.clone(),
);
fx_log_info!(tag: "client_request", "Received async thread response: {:?}", resp);
// If the response has a return value, package into response, otherwise use error code
match resp.result {
Some(async_res) => {
let res = CommandResponse::new(method_id, Some(async_res), None);
rouille::Response::json(&res)
}
None => {
let res = CommandResponse::new(method_id, None, resp.error);
rouille::Response::json(&res)
}
}
}
// Initializes a new client, adds to clients, a thread-safe HashMap
// Returns a rouille::Response
fn client_init(
request: &Request, clients: Arc<Mutex<HashMap<String, Vec<ClientData>>>>,
) -> Response {
const INIT_ACK: &str = "Recieved init request.";
const FAIL_INIT_ACK: &str = "Failed to init client.";
let (_, _, _, _, method_params) = match parse_request(request) {
Ok(res) => res,
Err(_) => return Response::json(&FAIL_INIT_ACK),
};
let client_id_raw = match method_params.get("client_id") {
Some(id) => Some(id).unwrap().clone(),
None => return Response::json(&FAIL_INIT_ACK),
};
// Initialize client with key = id, val = client data
let client_id = client_id_raw.as_str().map(String::from).unwrap();
let client_data = Vec::new();
if clients.lock().contains_key(&client_id) {
fx_log_warn!(tag: "client_init",
"Key: {:?} already exists in clients. ",
&client_id
);
return rouille::Response::json(&FAIL_INIT_ACK);
}
clients.lock().insert(client_id, client_data);
fx_log_info!(tag: "client_init", "Updated clients: {:?}", clients);
rouille::Response::json(&INIT_ACK)
}
// Given the name of the ACTS method, derive method type + method name
// Returns two "", "" on invalid input which will later propagate to
// method_to_fidl and raise an error
fn split_string(method_name_raw: String) -> (String, String) {
let split = method_name_raw.split(COMMAND_DELIMITER);
let string_split: Vec<&str> = split.collect();
// Input must be two strings separated by "."
if string_split.len() != COMMAND_SIZE {
return ("".to_string(), "".to_string());
};
(string_split[0].to_string(), string_split[1].to_string())
}
// Given a request, grabs the method id, name, and parameters
// Return BTError if fail
fn parse_request(request: &Request) -> Result<(String, String, String, String, Value), Error> {
let mut data = match request.data() {
Some(d) => d,
None => return Err(BTError::new("Failed to parse request buffer.").into()),
};
let mut buf: String = String::new();
if data.read_to_string(&mut buf).is_err() {
return Err(BTError::new("Failed to read request buffer.").into());
}
// Ignore the json_rpc field
let request_data: CommandRequest = match serde_json::from_str(&buf) {
Ok(tdata) => tdata,
Err(_) => return Err(BTError::new("Failed to unpack request data.").into()),
};
let method_id_raw = request_data.id.clone();
let method_name_raw = request_data.method.clone();
let method_params = request_data.params.clone();
fx_log_info!(tag: "parse_request",
"method id: {:?}, name: {:?}, args: {:?}",
method_id_raw, method_name_raw, method_params
);
// Separate the method_name field of the request into the method type (e.g bluetooth) and the
// actual method name itself
let (method_type, method_name) = split_string(method_name_raw.clone());
let (session_id, method_id) = split_string(method_id_raw.clone());
Ok((
session_id,
method_id,
method_type,
method_name,
method_params,
))
}
fn server_cleanup(request: &Request, sl4f_session: Arc<RwLock<Sl4f>>) -> Response {
const FAIL_CLEANUP_ACK: &str = "Failed to cleanup SL4F resources.";
const CLEANUP_ACK: &str = "Successful cleanup of SL4F resources.";
fx_log_info!(tag: "server_cleanup", "Cleaning up server state");
let (_, method_id, _, _, _) = match parse_request(request) {
Ok(res) => res,
Err(_) => return Response::json(&FAIL_CLEANUP_ACK),
};
// Cleanup all resources associated with session
// Validate result
let session = sl4f_session.clone();
session.write().cleanup();
session.read().print();
let ack = match to_value(serde::export::Some(CLEANUP_ACK.to_string())) {
Ok(v) => CommandResponse::new(method_id, Some(v), None),
Err(e) => CommandResponse::new(method_id, None, Some(e.to_string())),
};
rouille::Response::json(&ack)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn split_string_test() {
// Standard command
let mut method_name = "bt.send".to_string();
assert_eq!(
("bt".to_string(), "send".to_string()),
split_string(method_name)
);
// Invalid command (should result in empty result)
method_name = "bluetooth_send".to_string();
assert_eq!(("".to_string(), "".to_string()), split_string(method_name));
// Too many separators in command
method_name = "wlan.scan.start".to_string();
assert_eq!(("".to_string(), "".to_string()), split_string(method_name));
// Empty command
method_name = "".to_string();
assert_eq!(("".to_string(), "".to_string()), split_string(method_name));
// No separator
method_name = "BluetoothSend".to_string();
assert_eq!(("".to_string(), "".to_string()), split_string(method_name));
// Invalid separator
method_name = "Bluetooth,Scan".to_string();
assert_eq!(("".to_string(), "".to_string()), split_string(method_name));
}
}