blob: 74443abe150f2d577163195fb1fa1c84e09beb5b [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 anyhow::Error;
use async_trait::async_trait;
use futures::channel::oneshot;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::{fmt::Debug, str::FromStr};
use thiserror::Error;
use crate::server::constants::{COMMAND_DELIMITER, COMMAND_SIZE};
/// An Sl4f facade that can handle incoming requests.
#[async_trait(?Send)]
pub trait Facade: Debug {
/// Asynchronously handle the incoming request for the given method and arguments, returning a
/// future object representing the pending operation.
async fn handle_request(&self, method: String, args: Value) -> Result<Value, Error>;
/// In response to a request to /cleanup, cleanup any cross-request state.
fn cleanup(&self) {}
/// In response to a request to /print, log relevant facade state.
fn print(&self) {}
}
/// Information about each client that has connected
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ClientData {
// client_id: String ID of client (ACTS test suite)
pub command_id: Value,
// command_result: The response of running the command (to be stored in the table)
pub command_result: AsyncResponse,
}
impl ClientData {
pub fn new(id: Value, result: AsyncResponse) -> ClientData {
ClientData { command_id: id, command_result: result }
}
}
/// The parsed `id` field from an incoming json-rpc request.
#[derive(Debug, PartialEq, Clone)]
pub struct RequestId {
/// If the request ID is a string that contains a single '.', the text leading up to the '.' is
/// extracted as the client identifier.
client: Option<String>,
/// The ID to send in the response. If client is Some(_), this will be a substring of the
/// request ID.
id: Value,
}
impl RequestId {
/// Parse a raw request ID into its session id (if present) and response id.
pub fn new(raw: Value) -> Self {
if let Some(s) = raw.as_str() {
let parts = s.split('.').collect::<Vec<_>>();
if parts.len() == 2 {
return Self {
client: Some(parts[0].to_owned()),
id: Value::String(parts[1..].join(".")),
};
}
}
// If the raw ID wasn't a string that contained exactly 1 '.', pass it through to the
// response unmodified.
Self { client: None, id: raw }
}
/// Returns a reference to the session id, if present.
pub fn session_id(&self) -> Option<&str> {
self.client.as_ref().map(String::as_str)
}
/// Returns a reference to the response id.
pub fn response_id(&self) -> &Value {
&self.id
}
/// Returns the response id, consuming self.
pub fn into_response_id(self) -> Value {
self.id
}
}
/// The parsed `method` field from an incoming json-rpc request.
#[derive(Debug, PartialEq, Eq, Clone, Default)]
pub struct MethodId {
/// Method type of the request (e.g bluetooth, wlan, etc...)
pub facade: String,
/// Name of the method
pub method: String,
}
impl FromStr for MethodId {
type Err = MethodIdParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts = s.split(COMMAND_DELIMITER).collect::<Vec<_>>();
if parts.len() != COMMAND_SIZE {
return Err(MethodIdParseError(s.to_string()));
}
Ok(Self { facade: parts[0].to_string(), method: parts[1].to_string() })
}
}
#[derive(Debug, PartialEq, Eq, Clone, Error)]
#[error("invalid method id: {}", _0)]
pub struct MethodIdParseError(String);
/// Required fields for making a request
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CommandRequest {
// method: name of method to be called
pub method: String,
// id: String id of command
pub id: Value,
// params: Arguments required for method
pub params: Value,
}
/// Return packet after SL4F runs command
#[derive(Serialize, Clone, Debug)]
pub struct CommandResponse {
// id: String id of command
pub id: Value,
// result: Result value of method call, can be None
pub result: Option<Value>,
// error: Error message of method call, can be None
pub error: Option<String>,
}
impl CommandResponse {
pub fn new(id: Value, result: Option<Value>, error: Option<String>) -> CommandResponse {
CommandResponse { id, result, error }
}
}
/// Represents a RPC request to be fulfilled by the FIDL event loop
#[derive(Debug)]
pub enum AsyncRequest {
Cleanup(oneshot::Sender<()>),
Command(AsyncCommandRequest),
}
/// Represents a RPC command request to be fulfilled by the FIDL event loop
#[derive(Debug)]
pub struct AsyncCommandRequest {
// tx: Transmit channel from FIDL event loop to RPC request side
pub tx: oneshot::Sender<AsyncResponse>,
// method_id: struct containing:
// * facade: Method type of the request (e.g bluetooth, wlan, etc...)
// * method: Name of the method
pub method_id: MethodId,
// params: serde_json::Value representing args for method
pub params: Value,
}
impl AsyncCommandRequest {
pub fn new(
tx: oneshot::Sender<AsyncResponse>,
method_id: MethodId,
params: Value,
) -> AsyncCommandRequest {
AsyncCommandRequest { tx, method_id, params }
}
}
/// Represents a RPC response from the FIDL event loop to the RPC request side
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AsyncResponse {
// res: serde_json::Value of FIDL method result
pub result: Option<Value>,
pub error: Option<String>,
}
impl AsyncResponse {
pub fn new(res: Result<Value, Error>) -> AsyncResponse {
match res {
Ok(v) => AsyncResponse { result: Some(v), error: None },
Err(e) => AsyncResponse { result: None, error: Some(e.to_string()) },
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn parse_method_id_ok() {
assert_eq!(
"bt.send".parse(),
Ok(MethodId { facade: "bt".to_string(), method: "send".to_string() })
);
assert_eq!(
"FooFacade.BarMethod".parse(),
Ok(MethodId { facade: "FooFacade".to_string(), method: "BarMethod".to_string() })
);
assert_eq!(
"EmptyMethod.".parse(),
Ok(MethodId { facade: "EmptyMethod".to_string(), method: "".to_string() })
);
assert_eq!(
".EmptyFacade".parse(),
Ok(MethodId { facade: "".to_string(), method: "EmptyFacade".to_string() })
);
}
#[test]
fn parse_method_id_invalid() {
fn assert_parse_error(s: &str) {
assert_eq!(s.parse::<MethodId>(), Err(MethodIdParseError(s.to_string())));
}
// Invalid command (should result in empty result)
assert_parse_error("bluetooth_send");
// Too many separators in command
assert_parse_error("wlan.scan.start");
// Empty command
assert_parse_error("");
// No separator
assert_parse_error("BluetoothSend");
// Invalid separator
assert_parse_error("Bluetooth,Scan");
}
#[test]
fn parse_request_id_int() {
let id = RequestId::new(json!(42));
assert_eq!(id, RequestId { client: None, id: json!(42) });
assert_eq!(id.session_id(), None);
assert_eq!(id.response_id(), &json!(42));
assert_eq!(id.into_response_id(), json!(42));
}
#[test]
fn parse_request_id_single_str() {
assert_eq!(RequestId::new(json!("123")), RequestId { client: None, id: json!("123") });
}
#[test]
fn parse_request_id_too_many_dots() {
assert_eq!(RequestId::new(json!("1.2.3")), RequestId { client: None, id: json!("1.2.3") });
}
#[test]
fn parse_request_id_with_session_id() {
let id = RequestId::new(json!("12.34"));
assert_eq!(id, RequestId { client: Some("12".to_string()), id: json!("34") });
assert_eq!(id.session_id(), Some("12"));
assert_eq!(id.response_id(), &json!("34"));
assert_eq!(id.into_response_id(), json!("34"));
}
}