use anyhow::{format_err, Context, Error};
use cm_rust::{ExposeDecl, ExposeProtocolDecl, ExposeSource, ExposeTarget};
use fidl::endpoints::{self as f_end, DiscoverableProtocolMarker};
use fidl_fuchsia_logger::LogSinkMarker;
use fuchsia_async::{self as fasync, DurationExt, TimeoutExt};
use fuchsia_bluetooth::types as bt_types;
use fuchsia_component::server::ServiceFs;
use fuchsia_component_test::{
Capability, ChildOptions, LocalComponentHandles, RealmBuilder, RealmInstance, Ref, Route,
};
use futures::stream::StreamExt;
use futures::{TryFutureExt, TryStreamExt};
use tracing::info;
use zx::{self as zx, MonotonicDuration};
use {
fidl_fuchsia_bluetooth_bredr as bredr, fidl_fuchsia_bluetooth_bredr_test as bredr_test,
fidl_fuchsia_component_test as ftest,
};
const TIMEOUT_SECONDS: i64 = 2 * 60;
pub fn peer_observer_timeout() -> MonotonicDuration {
MonotonicDuration::from_seconds(TIMEOUT_SECONDS)
}
static MOCK_PICONET_SERVER_URL: &str = "#meta/mock-piconet-server.cm";
static PROFILE_INTERPOSER_PREFIX: &str = "profile-interposer";
static BT_RFCOMM_PREFIX: &str = "bt-rfcomm";
fn protocol_name_from_capability(capability: &ftest::Capability) -> Result<String, Error> {
if let ftest::Capability::Protocol(ftest::Protocol { name: Some(name), .. }) = capability {
Ok(name.clone())
} else {
Err(format_err!("Not a protocol capability: {:?}", capability))
}
}
fn expose_decl(name: &str, id: bt_types::PeerId, capability_name: &str) -> ExposeDecl {
ExposeDecl::Protocol(ExposeProtocolDecl {
source: ExposeSource::Child(name.parse().unwrap()),
source_name: capability_name.parse().unwrap(),
source_dictionary: Default::default(),
target: ExposeTarget::Parent,
target_name: capability_path_for_peer_id(id, capability_name).parse().unwrap(),
availability: cm_rust::Availability::Required,
})
}
#[derive(Clone, Debug)]
pub struct PiconetMemberSpec {
pub name: String,
pub id: bt_types::PeerId,
rfcomm_url: Option<String>,
expose_decls: Vec<ExposeDecl>,
observer: Option<bredr_test::PeerObserverProxy>,
}
impl PiconetMemberSpec {
pub fn get_profile_proxy(
&self,
topology: &RealmInstance,
) -> Result<bredr::ProfileProxy, anyhow::Error> {
info!("Received request to get `bredr.Profile` for piconet member: {:?}", self.id);
let (client, server) = f_end::create_proxy::<bredr::ProfileMarker>();
topology.root.connect_request_to_named_protocol_at_exposed_dir(
&capability_path_for_mock::<bredr::ProfileMarker>(self),
server.into_channel(),
)?;
Ok(client)
}
pub fn for_profile(
name: String,
rfcomm_url: Option<String>,
expose_capabilities: Vec<ftest::Capability>,
) -> Result<(Self, bredr_test::PeerObserverRequestStream), Error> {
let id = bt_types::PeerId::random();
let capability_names = expose_capabilities
.iter()
.map(protocol_name_from_capability)
.collect::<Result<Vec<_>, _>>()?;
let expose_decls = capability_names
.iter()
.map(|capability_name| expose_decl(&name, id, capability_name))
.collect();
let (peer_proxy, peer_stream) =
f_end::create_proxy_and_stream::<bredr_test::PeerObserverMarker>();
Ok((Self { name, id, rfcomm_url, expose_decls, observer: Some(peer_proxy) }, peer_stream))
}
pub fn for_mock_peer(name: String, rfcomm_url: Option<String>) -> Self {
let id = bt_types::PeerId::random();
let expose_decls = if rfcomm_url.is_some() {
let rfcomm_name = bt_rfcomm_moniker_for_member(&name);
vec![expose_decl(&rfcomm_name, id, bredr::ProfileMarker::PROTOCOL_NAME)]
} else {
Vec::new()
};
Self { name, id, rfcomm_url, expose_decls, observer: None }
}
}
fn capability_path_for_mock<S: DiscoverableProtocolMarker>(mock: &PiconetMemberSpec) -> String {
capability_path_for_peer_id(mock.id, S::PROTOCOL_NAME)
}
fn capability_path_for_peer_id(id: bt_types::PeerId, capability_name: &str) -> String {
format!("{}-{}", capability_name, id)
}
pub struct PiconetMember {
id: bt_types::PeerId,
profile_svc: bredr::ProfileProxy,
}
impl PiconetMember {
pub fn peer_id(&self) -> bt_types::PeerId {
self.id
}
pub fn new_from_spec(
mock: PiconetMemberSpec,
realm: &RealmInstance,
) -> Result<Self, anyhow::Error> {
Ok(Self {
id: mock.id,
profile_svc: mock
.get_profile_proxy(realm)
.context("failed to open mock's profile proxy")?,
})
}
pub fn register_service_search(
&self,
svc_id: bredr::ServiceClassProfileIdentifier,
attributes: Vec<u16>,
) -> Result<bredr::SearchResultsRequestStream, Error> {
let (results_client, results_requests) = f_end::create_request_stream();
self.profile_svc.search(bredr::ProfileSearchRequest {
service_uuid: Some(svc_id),
attr_ids: Some(attributes),
results: Some(results_client),
..Default::default()
})?;
Ok(results_requests)
}
pub fn register_service_advertisement(
&self,
service_defs: Vec<bredr::ServiceDefinition>,
) -> Result<bredr::ConnectionReceiverRequestStream, Error> {
let (connect_client, connect_requests) = f_end::create_request_stream();
let _ = self.profile_svc.advertise(bredr::ProfileAdvertiseRequest {
services: Some(service_defs),
receiver: Some(connect_client),
..Default::default()
});
Ok(connect_requests)
}
pub async fn make_connection(
&self,
peer_id: bt_types::PeerId,
params: bredr::ConnectParameters,
) -> Result<bredr::Channel, Error> {
self.profile_svc
.connect(&peer_id.into(), ¶ms)
.await?
.map_err(|e| format_err!("{:?}", e))
}
}
pub struct BtProfileComponent {
observer_stream: bredr_test::PeerObserverRequestStream,
profile_id: bt_types::PeerId,
}
impl BtProfileComponent {
pub fn new(stream: bredr_test::PeerObserverRequestStream, id: bt_types::PeerId) -> Self {
Self { observer_stream: stream, profile_id: id }
}
pub fn peer_id(&self) -> bt_types::PeerId {
self.profile_id
}
pub fn connect_to_protocol<S: DiscoverableProtocolMarker>(
&self,
topology: &RealmInstance,
) -> Result<S::Proxy, Error> {
let (client, server) = f_end::create_proxy::<S>();
topology.root.connect_request_to_named_protocol_at_exposed_dir(
&capability_path_for_peer_id(self.profile_id, S::PROTOCOL_NAME),
server.into_channel(),
)?;
Ok(client)
}
pub async fn expect_observer_request(
&mut self,
) -> Result<bredr_test::PeerObserverRequest, Error> {
self.observer_stream
.select_next_some()
.map_err(|e| format_err!("{:?}", e))
.on_timeout(peer_observer_timeout().after_now(), move || {
Err(format_err!("observer timed out"))
})
.await
}
pub async fn expect_observer_connection_request(
&mut self,
other: bt_types::PeerId,
) -> Result<(), Error> {
let request = self.expect_observer_request().await?;
match request {
bredr_test::PeerObserverRequest::PeerConnected { peer_id, responder, .. } => {
responder.send().unwrap();
if other == peer_id.into() {
Ok(())
} else {
Err(format_err!("Connection request for unexpected peer: {:?}", peer_id))
}
}
x => Err(format_err!("Expected PeerConnected but got: {:?}", x)),
}
}
pub async fn expect_observer_service_found_request(
&mut self,
other: bt_types::PeerId,
) -> Result<(), Error> {
let request = self.expect_observer_request().await?;
match request {
bredr_test::PeerObserverRequest::ServiceFound { peer_id, responder, .. } => {
responder.send().unwrap();
if other == peer_id.into() {
Ok(())
} else {
Err(format_err!("ServiceFound request for unexpected peer: {:?}", peer_id))
}
}
x => Err(format_err!("Expected PeerConnected but got: {:?}", x)),
}
}
}
async fn add_profile_to_topology<'a>(
builder: &RealmBuilder,
spec: &'a mut PiconetMemberSpec,
server_moniker: String,
profile_url: String,
additional_routes: Vec<Route>,
) -> Result<(), Error> {
let mock_piconet_member_name = interposer_name_for_profile(&spec.name);
add_mock_piconet_component(
builder,
mock_piconet_member_name.clone(),
spec.id,
bredr::ProfileMarker::PROTOCOL_NAME.to_string(),
spec.observer.take(),
)
.await?;
let rfcomm_moniker = bt_rfcomm_moniker_for_member(&spec.name);
if let Some(url) = &spec.rfcomm_url {
add_bt_rfcomm_intermediary(builder, rfcomm_moniker.clone(), url.clone()).await?;
}
{
let _ = builder
.add_child(spec.name.to_string(), profile_url, ChildOptions::new().eager())
.await?;
}
{
if spec.rfcomm_url.is_some() {
builder
.add_route(
Route::new()
.capability(Capability::protocol::<bredr::ProfileMarker>())
.from(Ref::child(&mock_piconet_member_name))
.to(Ref::child(&rfcomm_moniker)),
)
.await?;
builder
.add_route(
Route::new()
.capability(Capability::protocol::<bredr::ProfileMarker>())
.from(Ref::child(&rfcomm_moniker))
.to(Ref::child(&spec.name)),
)
.await?;
} else {
builder
.add_route(
Route::new()
.capability(Capability::protocol::<bredr::ProfileMarker>())
.from(Ref::child(&mock_piconet_member_name))
.to(Ref::child(&spec.name)),
)
.await?;
}
builder
.add_route(
Route::new()
.capability(Capability::protocol::<bredr_test::ProfileTestMarker>())
.from(Ref::child(&server_moniker))
.to(Ref::child(&mock_piconet_member_name)),
)
.await?;
builder
.add_route(
Route::new()
.capability(Capability::protocol::<LogSinkMarker>())
.from(Ref::parent())
.to(Ref::child(&spec.name))
.to(Ref::child(&mock_piconet_member_name)),
)
.await?;
for route in additional_routes {
let _ = builder.add_route(route).await?;
}
}
Ok(())
}
async fn add_mock_piconet_member<'a, 'b>(
builder: &RealmBuilder,
mock: &'a mut PiconetMemberSpec,
server_moniker: String,
) -> Result<(), Error> {
let profile_path = if mock.rfcomm_url.is_some() {
bredr::ProfileMarker::PROTOCOL_NAME.to_string()
} else {
capability_path_for_mock::<bredr::ProfileMarker>(mock)
};
add_mock_piconet_component(
builder,
mock.name.to_string(),
mock.id,
profile_path.clone(),
mock.observer.take(),
)
.await?;
let rfcomm_moniker = bt_rfcomm_moniker_for_member(&mock.name);
if let Some(url) = &mock.rfcomm_url {
add_bt_rfcomm_intermediary(builder, rfcomm_moniker.clone(), url.clone()).await?;
}
{
builder
.add_route(
Route::new()
.capability(Capability::protocol::<bredr_test::ProfileTestMarker>())
.from(Ref::child(&server_moniker))
.to(Ref::child(&mock.name)),
)
.await?;
if mock.rfcomm_url.is_some() {
builder
.add_route(
Route::new()
.capability(Capability::protocol_by_name(profile_path))
.from(Ref::child(&mock.name))
.to(Ref::child(&rfcomm_moniker)),
)
.await?;
} else {
builder
.add_route(
Route::new()
.capability(Capability::protocol_by_name(profile_path))
.from(Ref::child(&mock.name))
.to(Ref::parent()),
)
.await?;
}
}
Ok(())
}
async fn add_mock_piconet_component(
builder: &RealmBuilder,
name: String,
id: bt_types::PeerId,
profile_svc_path: String,
observer_src: Option<bredr_test::PeerObserverProxy>,
) -> Result<(), Error> {
let observer = observer_src.unwrap_or_else(|| {
let (proxy, stream) = f_end::create_proxy_and_stream::<bredr_test::PeerObserverMarker>();
fasync::Task::local(async move {
let _ = drain_observer(stream).await;
})
.detach();
proxy
});
builder
.add_local_child(
name,
move |m: LocalComponentHandles| {
let observer = observer.clone();
Box::pin(piconet_member(m, id, profile_svc_path.clone(), observer))
},
ChildOptions::new(),
)
.await
.map(|_| ())
.map_err(|e| e.into())
}
async fn piconet_member(
handles: LocalComponentHandles,
id: bt_types::PeerId,
profile_svc_path: String,
peer_observer: bredr_test::PeerObserverProxy,
) -> Result<(), Error> {
let pro_test = handles.connect_to_protocol::<bredr_test::ProfileTestMarker>()?;
let mut fs = ServiceFs::new();
let _ = fs.dir("svc").add_service_at(profile_svc_path, move |chan: zx::Channel| {
info!("Received ServiceFs `Profile` connection request for piconet_member: {:?}", id);
let profile_test = pro_test.clone();
let observer = peer_observer.clone();
fasync::Task::local(async move {
let (client, observer_req_stream) =
register_piconet_member(&profile_test, id).await.unwrap();
let err_str = format!("Couldn't connect to `Profile` for peer {:?}", id);
let _ = client.connect_proxy_(chan.into()).await.expect(&err_str);
fwd_observer_callbacks(observer_req_stream, &observer, id).await.unwrap();
})
.detach();
Some(())
});
let _ = fs.serve_connection(handles.outgoing_dir).expect("failed to serve service fs");
fs.collect::<()>().await;
Ok(())
}
async fn register_piconet_member(
profile_test_proxy: &bredr_test::ProfileTestProxy,
id: bt_types::PeerId,
) -> Result<(bredr_test::MockPeerProxy, bredr_test::PeerObserverRequestStream), Error> {
info!("Sending RegisterPeer request for peer {:?} to the Mock Piconet Server.", id);
let (client, server) = f_end::create_proxy::<bredr_test::MockPeerMarker>();
let (observer_client, observer_server) =
f_end::create_request_stream::<bredr_test::PeerObserverMarker>();
profile_test_proxy
.register_peer(&id.into(), server, observer_client)
.await
.context("registering peer failed!")?;
Ok((client, observer_server))
}
fn handle_fidl_err(fidl_err: fidl::Error, ctx: String) -> Result<(), Error> {
if fidl_err.is_closed() {
Ok(())
} else {
Err(anyhow::Error::from(fidl_err).context(ctx))
}
}
async fn fwd_observer_callbacks(
mut source_req_stream: bredr_test::PeerObserverRequestStream,
observer: &bredr_test::PeerObserverProxy,
id: bt_types::PeerId,
) -> Result<(), Error> {
while let Some(req) =
source_req_stream.try_next().await.context("reading peer observer failed")?
{
match req {
bredr_test::PeerObserverRequest::ServiceFound {
peer_id,
protocol,
attributes,
responder,
} => {
let proto = match protocol {
Some(desc) => desc,
None => vec![],
};
observer.service_found(&peer_id, Some(&proto), &attributes).await.or_else(|e| {
handle_fidl_err(
e,
format!("unexpected error forwarding observer event for: {}", id),
)
})?;
responder.send().or_else(|e| {
handle_fidl_err(
e,
format!("unexpected error acking observer event for: {}", id),
)
})?;
}
bredr_test::PeerObserverRequest::PeerConnected { peer_id, protocol, responder } => {
observer.peer_connected(&peer_id, &protocol).await.or_else(|e| {
handle_fidl_err(
e,
format!("unexpected error forwarding observer event for: {}", id),
)
})?;
responder.send().or_else(|e| {
handle_fidl_err(
e,
format!("unexpected error acking observer event for: {}", id),
)
})?;
}
}
}
Ok(())
}
async fn drain_observer(
mut stream: bredr_test::PeerObserverRequestStream,
) -> Result<(), fidl::Error> {
while let Some(req) = stream.try_next().await? {
match req {
bredr_test::PeerObserverRequest::ServiceFound { responder, .. } => {
responder.send()?;
}
bredr_test::PeerObserverRequest::PeerConnected { responder, .. } => {
responder.send()?;
}
}
}
Ok(())
}
async fn add_mock_piconet_server(builder: &RealmBuilder) -> String {
let name = mock_piconet_server_moniker().to_string();
let mock_piconet_server = builder
.add_child(name.clone(), MOCK_PICONET_SERVER_URL, ChildOptions::new())
.await
.expect("failed to add");
builder
.add_route(
Route::new()
.capability(Capability::protocol::<LogSinkMarker>())
.from(Ref::parent())
.to(&mock_piconet_server),
)
.await
.unwrap();
name
}
async fn add_bt_rfcomm_intermediary(
builder: &RealmBuilder,
moniker: String,
url: String,
) -> Result<(), Error> {
let bt_rfcomm = builder.add_child(moniker.clone(), url, ChildOptions::new().eager()).await?;
let _ = builder
.add_route(
Route::new()
.capability(Capability::protocol::<LogSinkMarker>())
.from(Ref::parent())
.to(&bt_rfcomm),
)
.await?;
Ok(())
}
fn mock_piconet_server_moniker() -> String {
"mock-piconet-server".to_string()
}
fn bt_rfcomm_moniker_for_member(member_name: &'_ str) -> String {
format!("{}-for-{}", BT_RFCOMM_PREFIX, member_name)
}
fn interposer_name_for_profile(profile_name: &'_ str) -> String {
format!("{}-{}", PROFILE_INTERPOSER_PREFIX, profile_name)
}
pub struct PiconetHarness {
pub builder: RealmBuilder,
pub ps_moniker: String,
profiles: Vec<PiconetMemberSpec>,
piconet_members: Vec<PiconetMemberSpec>,
}
impl PiconetHarness {
pub async fn new() -> Self {
let builder = RealmBuilder::new().await.expect("Couldn't create realm builder");
let ps_moniker = add_mock_piconet_server(&builder).await;
PiconetHarness { builder, ps_moniker, profiles: Vec::new(), piconet_members: Vec::new() }
}
pub async fn add_mock_piconet_members(
&mut self,
mocks: &'_ mut Vec<PiconetMemberSpec>,
) -> Result<(), Error> {
for mock in mocks {
self.add_mock_piconet_member_from_spec(mock).await?;
}
Ok(())
}
pub async fn add_mock_piconet_member(
&mut self,
name: String,
rfcomm_url: Option<String>,
) -> Result<PiconetMemberSpec, Error> {
let mut mock = PiconetMemberSpec::for_mock_peer(name, rfcomm_url);
self.add_mock_piconet_member_from_spec(&mut mock).await?;
Ok(mock)
}
async fn add_mock_piconet_member_from_spec(
&mut self,
mock: &'_ mut PiconetMemberSpec,
) -> Result<(), Error> {
add_mock_piconet_member(&self.builder, mock, self.ps_moniker.clone()).await?;
self.piconet_members.push(mock.clone());
Ok(())
}
async fn update_routes(&self) -> Result<(), Error> {
info!(
"Building test realm with profiles: {:?} and piconet members: {:?}",
self.profiles, self.piconet_members
);
let mut root_decl = self.builder.get_realm_decl().await.expect("failed to get root");
let mut piconet_member_exposes =
self.piconet_members.iter().map(|spec| spec.expose_decls.clone()).flatten().collect();
let mut profile_member_exposes =
self.profiles.iter().map(|spec| spec.expose_decls.clone()).flatten().collect();
root_decl.exposes.append(&mut piconet_member_exposes);
root_decl.exposes.append(&mut profile_member_exposes);
self.builder.replace_realm_decl(root_decl).await.expect("Should be able to set root decl");
Ok(())
}
pub async fn build(self) -> Result<RealmInstance, Error> {
self.update_routes().await?;
self.builder.build().await.map_err(|e| e.into())
}
pub async fn add_profile(
&mut self,
name: String,
profile_url: String,
) -> Result<BtProfileComponent, Error> {
self.add_profile_with_capabilities(name, profile_url, None, vec![], vec![]).await
}
pub async fn add_profile_with_capabilities(
&mut self,
name: String,
profile_url: String,
rfcomm_url: Option<String>,
use_capabilities: Vec<ftest::Capability>,
expose_capabilities: Vec<ftest::Capability>,
) -> Result<BtProfileComponent, Error> {
let (mut spec, request_stream) =
PiconetMemberSpec::for_profile(name, rfcomm_url, expose_capabilities.clone())?;
let route = route_from_capabilities(
use_capabilities,
Ref::parent(),
vec![Ref::child(spec.name.clone())],
);
self.add_profile_from_spec(&mut spec, profile_url, vec![route]).await?;
Ok(BtProfileComponent::new(request_stream, spec.id))
}
async fn add_profile_from_spec(
&mut self,
spec: &mut PiconetMemberSpec,
profile_url: String,
capabilities: Vec<Route>,
) -> Result<(), Error> {
add_profile_to_topology(
&self.builder,
spec,
self.ps_moniker.clone(),
profile_url,
capabilities,
)
.await?;
self.profiles.push(spec.clone());
Ok(())
}
}
pub fn route_from_capabilities(
capabilities: Vec<ftest::Capability>,
source: Ref,
targets: Vec<Ref>,
) -> Route {
let mut route = Route::new().from(source);
for capability in capabilities {
route = route.capability(capability);
}
for target in targets {
route = route.to(target);
}
route
}
#[cfg(test)]
mod tests {
use super::*;
use assert_matches::assert_matches;
use cm_rust::{
Availability, ChildRef, DependencyType, OfferDecl, OfferProtocolDecl, OfferSource,
OfferTarget, UseDecl, UseProtocolDecl, UseSource,
};
use cm_types::Name;
use fidl_fuchsia_component_test as fctest;
use fuchsia_component_test::error::Error as RealmBuilderError;
fn offer_source_static_child(name: &str) -> OfferSource {
OfferSource::Child(ChildRef { name: name.parse().unwrap(), collection: None })
}
fn offer_target_static_child(name: &str) -> cm_rust::OfferTarget {
OfferTarget::Child(ChildRef { name: name.parse().unwrap(), collection: None })
}
async fn assert_realm_contains(builder: &RealmBuilder, child_name: &str) {
let err = builder
.add_child(child_name, "test://example-url", ChildOptions::new())
.await
.expect_err("failed to check realm contents");
assert_matches!(
err,
RealmBuilderError::ServerError(fctest::RealmBuilderError::ChildAlreadyExists)
);
}
#[fasync::run_singlethreaded(test)]
async fn test_profile_server_added() {
let test_harness = PiconetHarness::new().await;
test_harness.update_routes().await.expect("should update routes");
assert_realm_contains(&test_harness.builder, &super::mock_piconet_server_moniker()).await;
let _ = test_harness.builder.build().await.expect("build failed");
}
#[fasync::run_singlethreaded(test)]
async fn test_add_piconet_member() {
let mut test_harness = PiconetHarness::new().await;
let member_name = "test-piconet-member";
let member_spec = test_harness
.add_mock_piconet_member(member_name.to_string(), None)
.await
.expect("failed to add piconet member");
assert_eq!(member_spec.name, member_name);
test_harness.update_routes().await.expect("should update routes");
validate_mock_piconet_member(&test_harness.builder, &member_spec).await;
let _profile_test_offer = test_harness.builder.build().await.expect("build failed");
}
#[fasync::run_singlethreaded(test)]
async fn test_add_piconet_member_with_rfcomm() {
let mut test_harness = PiconetHarness::new().await;
let member_name = "test-piconet-member";
let rfcomm_url = "fuchsia-pkg://fuchsia.com/example#meta/bt-rfcomm.cm".to_string();
let member_spec = test_harness
.add_mock_piconet_member(member_name.to_string(), Some(rfcomm_url))
.await
.expect("failed to add piconet member");
assert_eq!(member_spec.name, member_name);
test_harness.update_routes().await.expect("should update routes");
validate_mock_piconet_member(&test_harness.builder, &member_spec).await;
}
#[fasync::run_singlethreaded(test)]
async fn test_add_multiple_piconet_members() {
let mut test_harness = PiconetHarness::new().await;
let member1_name = "test-piconet-member".to_string();
let member2_name = "test-piconet-member-two".to_string();
let mut members = vec![
PiconetMemberSpec::for_mock_peer(member1_name, None),
PiconetMemberSpec::for_mock_peer(member2_name, None),
];
test_harness
.add_mock_piconet_members(&mut members)
.await
.expect("failed to add piconet members");
test_harness.update_routes().await.expect("should update routes");
for member in &members {
validate_mock_piconet_member(&test_harness.builder, member).await;
}
let _profile_test_offer = test_harness.builder.build().await.expect("build failed");
}
#[fasync::run_singlethreaded(test)]
async fn test_add_multiple_piconet_members_with_rfcomm() {
let mut test_harness = PiconetHarness::new().await;
let rfcomm_url = "fuchsia-pkg://fuchsia.com/example#meta/bt-rfcomm.cm".to_string();
let member1_name = "test-piconet-member-1".to_string();
let member2_name = "test-piconet-member-2".to_string();
let member3_name = "test-piconet-member-3".to_string();
let mut members = vec![
PiconetMemberSpec::for_mock_peer(member1_name, None),
PiconetMemberSpec::for_mock_peer(member2_name, Some(rfcomm_url.clone())),
PiconetMemberSpec::for_mock_peer(member3_name, Some(rfcomm_url)),
];
test_harness
.add_mock_piconet_members(&mut members)
.await
.expect("failed to add piconet members");
test_harness.update_routes().await.expect("should update routes");
for member in &members {
validate_mock_piconet_member(&test_harness.builder, member).await;
}
}
async fn validate_profile_routes_for_member_with_rfcomm<'a>(
builder: &RealmBuilder,
member_spec: &'a PiconetMemberSpec,
) {
let profile_capability_name = bredr::ProfileMarker::PROTOCOL_NAME.to_string();
let pico_member_decl = builder
.get_component_decl(member_spec.name.clone())
.await
.expect("piconet member had no decl");
let expose_profile_decl = ExposeProtocolDecl {
source: ExposeSource::Self_,
source_name: profile_capability_name.clone().parse().unwrap(),
source_dictionary: Default::default(),
target: ExposeTarget::Parent,
target_name: profile_capability_name.clone().parse().unwrap(),
availability: cm_rust::Availability::Required,
};
let expose_decl = ExposeDecl::Protocol(expose_profile_decl.clone());
assert!(pico_member_decl.exposes.contains(&expose_decl));
{
let bt_rfcomm_name = super::bt_rfcomm_moniker_for_member(&member_spec.name);
let custom_profile_capability_name =
Name::new(super::capability_path_for_mock::<bredr::ProfileMarker>(&member_spec))
.unwrap();
let custom_expose_profile_decl = ExposeProtocolDecl {
source: ExposeSource::Child(bt_rfcomm_name.parse().unwrap()),
source_name: profile_capability_name.parse().unwrap(),
source_dictionary: Default::default(),
target: ExposeTarget::Parent,
target_name: custom_profile_capability_name,
availability: cm_rust::Availability::Required,
};
let root_expose_decl = ExposeDecl::Protocol(custom_expose_profile_decl);
let root = builder.get_realm_decl().await.expect("failed to get root");
assert!(root.exposes.contains(&root_expose_decl));
}
}
async fn validate_profile_routes_for_member<'a>(
builder: &RealmBuilder,
member_spec: &'a PiconetMemberSpec,
) {
let pico_member_decl = builder
.get_component_decl(member_spec.name.clone())
.await
.expect("piconet member had no decl");
let profile_capability_name =
Name::new(super::capability_path_for_mock::<bredr::ProfileMarker>(&member_spec))
.unwrap();
let mut expose_proto_decl = ExposeProtocolDecl {
source: ExposeSource::Self_,
source_name: profile_capability_name.clone(),
source_dictionary: Default::default(),
target: ExposeTarget::Parent,
target_name: profile_capability_name,
availability: cm_rust::Availability::Required,
};
let expose_decl = ExposeDecl::Protocol(expose_proto_decl.clone());
assert!(pico_member_decl.exposes.contains(&expose_decl));
{
expose_proto_decl.source = ExposeSource::Child(member_spec.name.parse().unwrap());
let root_expose_decl = ExposeDecl::Protocol(expose_proto_decl);
let root = builder.get_realm_decl().await.expect("failed to get root");
assert!(root.exposes.contains(&root_expose_decl));
}
}
async fn validate_mock_piconet_member<'a>(
builder: &RealmBuilder,
member_spec: &'a PiconetMemberSpec,
) {
assert_realm_contains(builder, &member_spec.name).await;
if member_spec.rfcomm_url.is_some() {
validate_profile_routes_for_member_with_rfcomm(builder, member_spec).await;
} else {
validate_profile_routes_for_member(builder, member_spec).await;
}
let pico_member_decl = builder
.get_component_decl(member_spec.name.clone())
.await
.expect("piconet member had no decl");
let use_decl = UseDecl::Protocol(UseProtocolDecl {
source: UseSource::Parent,
source_name: bredr_test::ProfileTestMarker::PROTOCOL_NAME.parse().unwrap(),
source_dictionary: Default::default(),
target_path: format!("/svc/{}", bredr_test::ProfileTestMarker::PROTOCOL_NAME)
.parse()
.unwrap(),
dependency_type: DependencyType::Strong,
availability: Availability::Required,
});
assert!(pico_member_decl.uses.contains(&use_decl));
let profile_test_name = Name::new(bredr_test::ProfileTestMarker::PROTOCOL_NAME).unwrap();
let root = builder.get_realm_decl().await.expect("failed to get root");
let offer_profile_test = OfferDecl::Protocol(OfferProtocolDecl {
source: offer_source_static_child(&super::mock_piconet_server_moniker()),
source_name: profile_test_name.clone(),
source_dictionary: Default::default(),
target: offer_target_static_child(&member_spec.name),
target_name: profile_test_name,
dependency_type: DependencyType::Strong,
availability: Availability::Required,
});
assert!(root.offers.contains(&offer_profile_test));
}
#[fasync::run_singlethreaded(test)]
async fn test_add_profile() {
let mut test_harness = PiconetHarness::new().await;
let profile_name = "test-profile-member";
let interposer_name = super::interposer_name_for_profile(profile_name);
let _profile_member = test_harness
.add_profile(
profile_name.to_string(),
"fuchsia-pkg://fuchsia.com/example#meta/example.cm".to_string(),
)
.await
.expect("failed to add profile");
test_harness.update_routes().await.expect("should update routes");
assert_realm_contains(&test_harness.builder, &profile_name).await;
assert_realm_contains(&test_harness.builder, &interposer_name).await;
let profile_capability_name = Name::new(bredr::ProfileMarker::PROTOCOL_NAME).unwrap();
let profile_expose = ExposeDecl::Protocol(ExposeProtocolDecl {
source: ExposeSource::Self_,
source_name: profile_capability_name.clone(),
source_dictionary: Default::default(),
target: ExposeTarget::Parent,
target_name: profile_capability_name.clone(),
availability: cm_rust::Availability::Required,
});
let interposer = test_harness
.builder
.get_component_decl(interposer_name.clone())
.await
.expect("interposer not found!");
assert!(interposer.exposes.contains(&profile_expose));
let profile_test_name = Name::new(bredr_test::ProfileTestMarker::PROTOCOL_NAME).unwrap();
let profile_test_use = UseDecl::Protocol(UseProtocolDecl {
source: UseSource::Parent,
source_name: profile_test_name.clone(),
source_dictionary: Default::default(),
target_path: format!("/svc/{}", bredr_test::ProfileTestMarker::PROTOCOL_NAME)
.parse()
.unwrap(),
dependency_type: DependencyType::Strong,
availability: Availability::Required,
});
assert!(interposer.uses.contains(&profile_test_use));
let profile_offer = OfferDecl::Protocol(OfferProtocolDecl {
source: offer_source_static_child(&interposer_name),
source_name: profile_capability_name.clone(),
source_dictionary: Default::default(),
target: offer_target_static_child(&profile_name),
target_name: profile_capability_name.clone(),
dependency_type: DependencyType::Strong,
availability: Availability::Required,
});
let root = test_harness.builder.get_realm_decl().await.expect("unable to get root decl");
assert!(root.offers.contains(&profile_offer));
let profile_test_offer = OfferDecl::Protocol(OfferProtocolDecl {
source: offer_source_static_child(&super::mock_piconet_server_moniker()),
source_name: profile_test_name.clone(),
source_dictionary: Default::default(),
target: offer_target_static_child(&interposer_name),
target_name: profile_test_name.clone(),
dependency_type: DependencyType::Strong,
availability: Availability::Required,
});
assert!(root.offers.contains(&profile_test_offer));
let log_capability_name = Name::new(LogSinkMarker::PROTOCOL_NAME).unwrap();
let log_offer = OfferDecl::Protocol(OfferProtocolDecl {
source: OfferSource::Parent,
source_name: log_capability_name.clone(),
source_dictionary: Default::default(),
target: offer_target_static_child(&profile_name),
target_name: log_capability_name.clone(),
dependency_type: DependencyType::Strong,
availability: Availability::Required,
});
assert!(root.offers.contains(&log_offer));
}
#[fasync::run_singlethreaded(test)]
async fn test_add_profile_with_rfcomm() {
let mut test_harness = PiconetHarness::new().await;
let profile_name = "test-profile-member";
let interposer_name = super::interposer_name_for_profile(profile_name);
let bt_rfcomm_name = super::bt_rfcomm_moniker_for_member(profile_name);
let profile_url = "fuchsia-pkg://fuchsia.com/example#meta/example.cm".to_string();
let rfcomm_url = "fuchsia-pkg://fuchsia.com/example#meta/bt-rfcomm.cm".to_string();
let _profile_member = test_harness
.add_profile_with_capabilities(
profile_name.to_string(),
profile_url,
Some(rfcomm_url),
vec![],
vec![],
)
.await
.expect("failed to add profile");
test_harness.update_routes().await.expect("should update routes");
assert_realm_contains(&test_harness.builder, &profile_name).await;
assert_realm_contains(&test_harness.builder, &interposer_name).await;
assert_realm_contains(&test_harness.builder, &bt_rfcomm_name).await;
let profile_capability_name = Name::new(bredr::ProfileMarker::PROTOCOL_NAME).unwrap();
let profile_offer1 = OfferDecl::Protocol(OfferProtocolDecl {
source: offer_source_static_child(&interposer_name),
source_name: profile_capability_name.clone(),
source_dictionary: Default::default(),
target: offer_target_static_child(&bt_rfcomm_name),
target_name: profile_capability_name.clone(),
dependency_type: DependencyType::Strong,
availability: Availability::Required,
});
let profile_offer2 = OfferDecl::Protocol(OfferProtocolDecl {
source: offer_source_static_child(&bt_rfcomm_name),
source_name: profile_capability_name.clone(),
source_dictionary: Default::default(),
target: offer_target_static_child(&profile_name),
target_name: profile_capability_name.clone(),
dependency_type: DependencyType::Strong,
availability: Availability::Required,
});
let root = test_harness.builder.get_realm_decl().await.expect("unable to get root decl");
assert!(root.offers.contains(&profile_offer1));
assert!(root.offers.contains(&profile_offer2));
}
#[fasync::run_singlethreaded(test)]
async fn test_add_profile_with_additional_capabilities() {
let mut test_harness = PiconetHarness::new().await;
let profile_name = "test-profile-member";
let fake_cap1 = "Foo".to_string();
let fake_cap2 = "Bar".to_string();
let expose_capabilities = vec![
Capability::protocol_by_name(fake_cap1.clone()).into(),
Capability::protocol_by_name(fake_cap2.clone()).into(),
];
let fake_cap3 = "Cat".to_string();
let use_capabilities = vec![Capability::protocol_by_name(fake_cap3.clone()).into()];
let profile_member = test_harness
.add_profile_with_capabilities(
profile_name.to_string(),
"fuchsia-pkg://fuchsia.com/example#meta/example.cm".to_string(),
None,
use_capabilities,
expose_capabilities,
)
.await
.expect("failed to add profile");
test_harness.update_routes().await.expect("should update routes");
assert_realm_contains(&test_harness.builder, &profile_name).await;
let fake_capability_expose1 = ExposeDecl::Protocol(ExposeProtocolDecl {
source: ExposeSource::Child(profile_name.parse().unwrap()),
source_name: fake_cap1.clone().parse().unwrap(),
source_dictionary: Default::default(),
target: ExposeTarget::Parent,
target_name: capability_path_for_peer_id(profile_member.peer_id(), &fake_cap1)
.parse()
.unwrap(),
availability: cm_rust::Availability::Required,
});
let fake_capability_expose2 = ExposeDecl::Protocol(ExposeProtocolDecl {
source: ExposeSource::Child(profile_name.parse().unwrap()),
source_name: fake_cap2.clone().parse().unwrap(),
source_dictionary: Default::default(),
target: ExposeTarget::Parent,
target_name: capability_path_for_peer_id(profile_member.peer_id(), &fake_cap2)
.parse()
.unwrap(),
availability: cm_rust::Availability::Required,
});
let fake_capability_offer3 = OfferDecl::Protocol(OfferProtocolDecl {
source: OfferSource::Parent,
source_name: fake_cap3.clone().parse().unwrap(),
source_dictionary: Default::default(),
target: offer_target_static_child(&profile_name),
target_name: fake_cap3.parse().unwrap(),
dependency_type: DependencyType::Strong,
availability: Availability::Required,
});
let root = test_harness.builder.get_realm_decl().await.expect("unable to get root decl");
assert!(root.exposes.contains(&fake_capability_expose1));
assert!(root.exposes.contains(&fake_capability_expose2));
assert!(root.offers.contains(&fake_capability_offer3));
}
#[fasync::run_singlethreaded(test)]
async fn test_multiple_profiles_with_same_expose_is_ok() {
let mut test_harness = PiconetHarness::new().await;
let profile_name1 = "test-profile-1";
let profile_name2 = "test-profile-2";
let fake_cap = "FooBarAmazing".to_string();
let expose_capabilities = vec![Capability::protocol_by_name(fake_cap.clone()).into()];
let profile_member1 = test_harness
.add_profile_with_capabilities(
profile_name1.to_string(),
"fuchsia-pkg://fuchsia.com/example#meta/example-profile1.cm".to_string(),
None,
vec![],
expose_capabilities.clone(),
)
.await
.expect("failed to add profile1");
let profile_member2 = test_harness
.add_profile_with_capabilities(
profile_name2.to_string(),
"fuchsia-pkg://fuchsia.com/example#meta/example-profile2.cm".to_string(),
None,
vec![],
expose_capabilities,
)
.await
.expect("failed to add profile2");
test_harness.update_routes().await.expect("should update routes");
assert_realm_contains(&test_harness.builder, &profile_name1).await;
assert_realm_contains(&test_harness.builder, &profile_name2).await;
let profile1_expose = ExposeDecl::Protocol(ExposeProtocolDecl {
source: ExposeSource::Child(profile_name1.parse().unwrap()),
source_name: fake_cap.clone().parse().unwrap(),
source_dictionary: Default::default(),
target: ExposeTarget::Parent,
target_name: capability_path_for_peer_id(profile_member1.peer_id(), &fake_cap)
.parse()
.unwrap(),
availability: cm_rust::Availability::Required,
});
let profile2_expose = ExposeDecl::Protocol(ExposeProtocolDecl {
source: ExposeSource::Child(profile_name2.parse().unwrap()),
source_name: fake_cap.clone().parse().unwrap(),
source_dictionary: Default::default(),
target: ExposeTarget::Parent,
target_name: capability_path_for_peer_id(profile_member2.peer_id(), &fake_cap)
.parse()
.unwrap(),
availability: cm_rust::Availability::Required,
});
let root = test_harness.builder.get_realm_decl().await.expect("unable to get root decl");
assert!(root.exposes.contains(&profile1_expose));
assert!(root.exposes.contains(&profile2_expose));
}
}