blob: 86b0e2500840f12db208d0f872520104a937896a [file] [log] [blame]
// Copyright 2021 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::{self, Context};
use bind_debugger as bind;
use bind_debugger::encode_bind_program_v1 as bind_v1;
use bind_debugger::instruction::DeviceProperty;
use fidl_fuchsia_driver_framework as fdf;
use fidl_fuchsia_driver_framework::{DriverIndexRequest, DriverIndexRequestStream};
use fuchsia_async as fasync;
use fuchsia_component::server::ServiceFs;
use fuchsia_zircon::{zx_status_t, Status};
use futures::prelude::*;
use std::collections::HashSet;
use std::rc::Rc;
/// Wraps all hosted protocols into a single type that can be matched against
/// and dispatched.
enum IncomingRequest {
DriverIndexProtocol(DriverIndexRequestStream),
}
struct Driver {
bind_program: Vec<bind_v1::RawInstruction<[u32; 3]>>,
url: String,
}
impl Driver {
fn matches(
&self,
properties: &Vec<DeviceProperty>,
) -> Result<Option<HashSet<DeviceProperty>>, bind::debugger::DebuggerError> {
bind::debugger::debug(&self.bind_program, properties)
}
}
impl std::fmt::Display for Driver {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", &self.url)
}
}
struct Indexer {
drivers: Vec<Driver>,
}
impl Indexer {
fn index_file(&mut self, path: std::path::PathBuf) -> std::io::Result<()> {
let file_name = match path.file_name() {
Some(file_name) => file_name,
None => {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
"Path must have file name",
));
}
};
let url = match file_name.to_os_string().into_string() {
Ok(url) => url,
Err(_) => {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
"File must be valid string",
));
}
};
let bytes = std::fs::read(&path)?;
let instructions = match bind_v1::decode_from_bytecode_v1(&bytes) {
Ok(instructions) => instructions,
Err(e) => {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!("Error decoding {}: {:?}", url, e),
));
}
};
self.add_driver(Driver { bind_program: instructions, url: url });
Ok(())
}
#[allow(dead_code)]
fn index_directory(&mut self, dir: &str) -> std::io::Result<()> {
let entries = std::fs::read_dir(dir)?;
for entry in entries {
let entry = entry?;
match self.index_file(entry.path()) {
Ok(_) => (),
Err(e) => eprintln!("Error indexing file: {:?}", e),
}
}
Ok(())
}
fn add_driver(&mut self, driver: Driver) {
self.drivers.push(driver);
}
fn match_driver(&self, args: fdf::NodeAddArgs) -> fdf::DriverIndexMatchDriverResult {
if args.properties.is_none() {
return Err(Status::INVALID_ARGS.into_raw());
}
let properties = args.properties.unwrap();
let properties = node_to_device_property(&properties)?;
for driver in &self.drivers {
match driver.matches(&properties) {
Ok(matched_properties) => {
if matched_properties.is_some() {
return Ok((driver.url.clone(), fdf::NodeAddArgs::EMPTY));
}
continue;
}
Err(e) => {
// If a driver has a bind error we will keep trying to match the other drivers
// instead of returning an error.
eprintln!("Driver {}: bind error: {}", driver, e);
continue;
}
}
}
Err(Status::NOT_FOUND.into_raw())
}
}
fn node_to_device_property(
node_properties: &Vec<fdf::NodeProperty>,
) -> Result<Vec<DeviceProperty>, zx_status_t> {
let mut device_properties = Vec::<DeviceProperty>::with_capacity(node_properties.len());
for property in node_properties {
if property.key.is_none() || property.value.is_none() {
return Err(Status::INVALID_ARGS.into_raw());
}
device_properties
.push(DeviceProperty { key: property.key.unwrap(), value: property.value.unwrap() });
}
Ok(device_properties)
}
async fn run_index_server(
indexer: Rc<Indexer>,
stream: DriverIndexRequestStream,
) -> Result<(), anyhow::Error> {
stream
.map(|result| result.context("failed request"))
.try_for_each(|request| async {
let indexer = indexer.clone();
match request {
DriverIndexRequest::MatchDriver { args, responder } => {
responder
.send(&mut indexer.match_driver(args))
.context("error sending response")?;
}
}
Ok(())
})
.await?;
Ok(())
}
#[fasync::run_singlethreaded]
async fn main() -> Result<(), anyhow::Error> {
let mut service_fs = ServiceFs::new_local();
let index = Rc::new(Indexer { drivers: Vec::<Driver>::new() });
service_fs.dir("svc").add_fidl_service(IncomingRequest::DriverIndexProtocol);
service_fs.take_and_serve_directory_handle().context("failed to serve outgoing namespace")?;
service_fs
.for_each_concurrent(None, |request: IncomingRequest| async {
// match on `request` and handle each protocol.
match request {
IncomingRequest::DriverIndexProtocol(stream) => {
run_index_server(index.clone(), stream).await
}
}
.unwrap_or_else(|e| println!("{:?}", e))
})
.await;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[fasync::run_singlethreaded(test)]
async fn match_driver_empty_indexer() {
let index = Rc::new(Indexer { drivers: vec![] });
let (proxy, stream) =
fidl::endpoints::create_proxy_and_stream::<fdf::DriverIndexMarker>().unwrap();
let (a, _) = future::join(run_index_server(index.clone(), stream), async move {
let property = fdf::NodeProperty {
key: Some(0x10),
value: Some(0x20),
..fdf::NodeProperty::EMPTY
};
let args =
fdf::NodeAddArgs { properties: Some(vec![property]), ..fdf::NodeAddArgs::EMPTY };
let result = proxy.match_driver(args).await.unwrap();
assert_eq!(result, Err(Status::NOT_FOUND.into_raw()));
})
.await;
a.unwrap();
}
#[fasync::run_singlethreaded(test)]
async fn match_driver_no_node_properties() {
let index = Rc::new(Indexer {
drivers: vec![Driver { bind_program: vec![], url: "my-url.cmx".to_string() }],
});
let (proxy, stream) =
fidl::endpoints::create_proxy_and_stream::<fdf::DriverIndexMarker>().unwrap();
let (a, _) = future::join(run_index_server(index.clone(), stream), async move {
let args = fdf::NodeAddArgs::EMPTY;
let result = proxy.match_driver(args).await.unwrap();
assert_eq!(result, Err(Status::INVALID_ARGS.into_raw()));
})
.await;
a.unwrap();
}
#[fasync::run_singlethreaded(test)]
async fn match_driver_bind_error() {
let mut index = Rc::new(Indexer { drivers: vec![] });
let property =
fdf::NodeProperty { key: Some(0x10), value: Some(0x20), ..fdf::NodeProperty::EMPTY };
let url = "my-url.cmx";
let mut_index = Rc::get_mut(&mut index).unwrap();
// Setting an empty bind program should give us an error.
mut_index.add_driver(Driver { bind_program: vec![], url: url.to_string() });
let (proxy, stream) =
fidl::endpoints::create_proxy_and_stream::<fdf::DriverIndexMarker>().unwrap();
let (a, _) = future::join(run_index_server(index.clone(), stream), async move {
let args =
fdf::NodeAddArgs { properties: Some(vec![property]), ..fdf::NodeAddArgs::EMPTY };
let result = proxy.match_driver(args).await.unwrap();
assert_eq!(result, Err(Status::NOT_FOUND.into_raw()));
})
.await;
a.unwrap();
}
#[fasync::run_singlethreaded(test)]
async fn match_driver_success() {
let mut index = Rc::new(Indexer { drivers: vec![] });
let property =
fdf::NodeProperty { key: Some(0x10), value: Some(0x20), ..fdf::NodeProperty::EMPTY };
let key = bind::compiler::Symbol::NumberValue(property.key.unwrap() as u64);
let value = bind::compiler::Symbol::NumberValue(property.value.unwrap() as u64);
let instruction =
bind::instruction::Instruction::Match(bind::instruction::Condition::Equal(key, value));
let instruction = bind::instruction::InstructionInfo::new(instruction);
let instruction = bind_v1::encode_instruction(instruction).unwrap();
let url = "my-url.cmx";
let mut_index = Rc::get_mut(&mut index).unwrap();
mut_index.add_driver(Driver { bind_program: vec![instruction], url: url.to_string() });
let (proxy, stream) =
fidl::endpoints::create_proxy_and_stream::<fdf::DriverIndexMarker>().unwrap();
let (a, _) = future::join(run_index_server(index.clone(), stream), async move {
let args =
fdf::NodeAddArgs { properties: Some(vec![property]), ..fdf::NodeAddArgs::EMPTY };
let result = proxy.match_driver(args).await.unwrap();
let (received_url, _) = result.unwrap();
assert_eq!(url.to_string(), received_url);
})
.await;
a.unwrap();
}
// In this test, we read from /pkg/bind/ for bind rules that were placed there by other build
// targets.
#[fasync::run_singlethreaded(test)]
async fn read_from_bind_dir() {
let mut index = Indexer { drivers: vec![] };
index.index_directory("/pkg/bind").unwrap();
let index = Rc::new(index);
let (proxy, stream) =
fidl::endpoints::create_proxy_and_stream::<fdf::DriverIndexMarker>().unwrap();
let (server_result, _) =
future::join(run_index_server(index.clone(), stream), async move {
// The values checked here need to match the values from the 'test-bind' build
// target.
let property = fdf::NodeProperty {
key: Some(bind::ddk_bind_constants::BIND_PROTOCOL),
value: Some(1),
..fdf::NodeProperty::EMPTY
};
let args = fdf::NodeAddArgs {
properties: Some(vec![property]),
..fdf::NodeAddArgs::EMPTY
};
let result = proxy.match_driver(args).await.unwrap();
let (received_url, _) = result.unwrap();
assert_eq!("my-test-driver-url.cm".to_string(), received_url);
})
.await;
server_result.unwrap();
}
}