1use anyhow::{format_err, Context, Error};
6use cm_rust::{ExposeDecl, ExposeProtocolDecl, ExposeSource, ExposeTarget};
7use fidl::endpoints::{self as f_end, DiscoverableProtocolMarker};
8use fidl_fuchsia_logger::LogSinkMarker;
9use fuchsia_async::{self as fasync, DurationExt, TimeoutExt};
10use fuchsia_bluetooth::types as bt_types;
11use fuchsia_component::server::ServiceFs;
12use fuchsia_component_test::{
13 Capability, ChildOptions, LocalComponentHandles, RealmBuilder, RealmInstance, Ref, Route,
14};
15use futures::stream::StreamExt;
16use futures::{TryFutureExt, TryStreamExt};
17use log::info;
18use zx::{self as zx, MonotonicDuration};
19use {
20 fidl_fuchsia_bluetooth_bredr as bredr, fidl_fuchsia_bluetooth_bredr_test as bredr_test,
21 fidl_fuchsia_component_test as ftest,
22};
23
24const TIMEOUT_SECONDS: i64 = 2 * 60;
32
33pub fn peer_observer_timeout() -> MonotonicDuration {
34 MonotonicDuration::from_seconds(TIMEOUT_SECONDS)
35}
36
37static MOCK_PICONET_SERVER_URL: &str = "#meta/mock-piconet-server.cm";
38static PROFILE_INTERPOSER_PREFIX: &str = "profile-interposer";
39static BT_RFCOMM_PREFIX: &str = "bt-rfcomm";
40
41fn protocol_name_from_capability(capability: &ftest::Capability) -> Result<String, Error> {
43 if let ftest::Capability::Protocol(ftest::Protocol { name: Some(name), .. }) = capability {
44 Ok(name.clone())
45 } else {
46 Err(format_err!("Not a protocol capability: {:?}", capability))
47 }
48}
49
50fn expose_decl(name: &str, id: bt_types::PeerId, capability_name: &str) -> ExposeDecl {
51 ExposeDecl::Protocol(ExposeProtocolDecl {
52 source: ExposeSource::Child(name.parse().unwrap()),
53 source_name: capability_name.parse().unwrap(),
54 source_dictionary: Default::default(),
55 target: ExposeTarget::Parent,
56 target_name: capability_path_for_peer_id(id, capability_name).parse().unwrap(),
57 availability: cm_rust::Availability::Required,
58 })
59}
60
61#[derive(Clone, Debug)]
65pub struct PiconetMemberSpec {
66 pub name: String,
67 pub id: bt_types::PeerId,
68 rfcomm_url: Option<String>,
71 expose_decls: Vec<ExposeDecl>,
78 observer: Option<bredr_test::PeerObserverProxy>,
79}
80
81impl PiconetMemberSpec {
82 pub fn get_profile_proxy(
83 &self,
84 topology: &RealmInstance,
85 ) -> Result<bredr::ProfileProxy, anyhow::Error> {
86 info!("Received request to get `bredr.Profile` for piconet member: {:?}", self.id);
87 let (client, server) = f_end::create_proxy::<bredr::ProfileMarker>();
88 topology.root.connect_request_to_named_protocol_at_exposed_dir(
89 &capability_path_for_mock::<bredr::ProfileMarker>(self),
90 server.into_channel(),
91 )?;
92 Ok(client)
93 }
94
95 pub fn for_profile(
102 name: String,
103 rfcomm_url: Option<String>,
104 expose_capabilities: Vec<ftest::Capability>,
105 ) -> Result<(Self, bredr_test::PeerObserverRequestStream), Error> {
106 let id = bt_types::PeerId::random();
107 let capability_names = expose_capabilities
108 .iter()
109 .map(protocol_name_from_capability)
110 .collect::<Result<Vec<_>, _>>()?;
111 let expose_decls = capability_names
112 .iter()
113 .map(|capability_name| expose_decl(&name, id, capability_name))
114 .collect();
115 let (peer_proxy, peer_stream) =
116 f_end::create_proxy_and_stream::<bredr_test::PeerObserverMarker>();
117
118 Ok((Self { name, id, rfcomm_url, expose_decls, observer: Some(peer_proxy) }, peer_stream))
119 }
120
121 pub fn for_mock_peer(name: String, rfcomm_url: Option<String>) -> Self {
126 let id = bt_types::PeerId::random();
127 let expose_decls = if rfcomm_url.is_some() {
130 let rfcomm_name = bt_rfcomm_moniker_for_member(&name);
131 vec![expose_decl(&rfcomm_name, id, bredr::ProfileMarker::PROTOCOL_NAME)]
132 } else {
133 Vec::new()
134 };
135 Self { name, id, rfcomm_url, expose_decls, observer: None }
136 }
137}
138
139fn capability_path_for_mock<S: DiscoverableProtocolMarker>(mock: &PiconetMemberSpec) -> String {
140 capability_path_for_peer_id(mock.id, S::PROTOCOL_NAME)
141}
142
143fn capability_path_for_peer_id(id: bt_types::PeerId, capability_name: &str) -> String {
144 format!("{}-{}", capability_name, id)
145}
146
147pub struct PiconetMember {
148 id: bt_types::PeerId,
149 profile_svc: bredr::ProfileProxy,
150}
151
152impl PiconetMember {
153 pub fn peer_id(&self) -> bt_types::PeerId {
154 self.id
155 }
156
157 pub fn new_from_spec(
158 mock: PiconetMemberSpec,
159 realm: &RealmInstance,
160 ) -> Result<Self, anyhow::Error> {
161 Ok(Self {
162 id: mock.id,
163 profile_svc: mock
164 .get_profile_proxy(realm)
165 .context("failed to open mock's profile proxy")?,
166 })
167 }
168
169 pub fn register_service_search(
173 &self,
174 svc_id: bredr::ServiceClassProfileIdentifier,
175 attributes: Vec<u16>,
176 ) -> Result<bredr::SearchResultsRequestStream, Error> {
177 let (results_client, results_requests) = f_end::create_request_stream();
178 self.profile_svc.search(bredr::ProfileSearchRequest {
179 service_uuid: Some(svc_id),
180 attr_ids: Some(attributes),
181 results: Some(results_client),
182 ..Default::default()
183 })?;
184 Ok(results_requests)
185 }
186
187 pub fn register_service_advertisement(
192 &self,
193 service_defs: Vec<bredr::ServiceDefinition>,
194 ) -> Result<bredr::ConnectionReceiverRequestStream, Error> {
195 let (connect_client, connect_requests) = f_end::create_request_stream();
196 let _ = self.profile_svc.advertise(bredr::ProfileAdvertiseRequest {
197 services: Some(service_defs),
198 receiver: Some(connect_client),
199 ..Default::default()
200 });
201
202 Ok(connect_requests)
203 }
204
205 pub async fn make_connection(
206 &self,
207 peer_id: bt_types::PeerId,
208 params: bredr::ConnectParameters,
209 ) -> Result<bredr::Channel, Error> {
210 self.profile_svc
211 .connect(&peer_id.into(), ¶ms)
212 .await?
213 .map_err(|e| format_err!("{:?}", e))
214 }
215}
216
217pub struct BtProfileComponent {
224 observer_stream: bredr_test::PeerObserverRequestStream,
225 profile_id: bt_types::PeerId,
226}
227
228impl BtProfileComponent {
229 pub fn new(stream: bredr_test::PeerObserverRequestStream, id: bt_types::PeerId) -> Self {
230 Self { observer_stream: stream, profile_id: id }
231 }
232
233 pub fn peer_id(&self) -> bt_types::PeerId {
234 self.profile_id
235 }
236
237 pub fn connect_to_protocol<S: DiscoverableProtocolMarker>(
240 &self,
241 topology: &RealmInstance,
242 ) -> Result<S::Proxy, Error> {
243 let (client, server) = f_end::create_proxy::<S>();
244 topology.root.connect_request_to_named_protocol_at_exposed_dir(
245 &capability_path_for_peer_id(self.profile_id, S::PROTOCOL_NAME),
246 server.into_channel(),
247 )?;
248 Ok(client)
249 }
250
251 pub async fn expect_observer_request(
255 &mut self,
256 ) -> Result<bredr_test::PeerObserverRequest, Error> {
257 self.observer_stream
259 .select_next_some()
260 .map_err(|e| format_err!("{:?}", e))
261 .on_timeout(peer_observer_timeout().after_now(), move || {
262 Err(format_err!("observer timed out"))
263 })
264 .await
265 }
266
267 pub async fn expect_observer_connection_request(
272 &mut self,
273 other: bt_types::PeerId,
274 ) -> Result<(), Error> {
275 let request = self.expect_observer_request().await?;
276 match request {
277 bredr_test::PeerObserverRequest::PeerConnected { peer_id, responder, .. } => {
278 responder.send().unwrap();
279 if other == peer_id.into() {
280 Ok(())
281 } else {
282 Err(format_err!("Connection request for unexpected peer: {:?}", peer_id))
283 }
284 }
285 x => Err(format_err!("Expected PeerConnected but got: {:?}", x)),
286 }
287 }
288
289 pub async fn expect_observer_service_found_request(
294 &mut self,
295 other: bt_types::PeerId,
296 ) -> Result<(), Error> {
297 let request = self.expect_observer_request().await?;
298 match request {
299 bredr_test::PeerObserverRequest::ServiceFound { peer_id, responder, .. } => {
300 responder.send().unwrap();
301 if other == peer_id.into() {
302 Ok(())
303 } else {
304 Err(format_err!("ServiceFound request for unexpected peer: {:?}", peer_id))
305 }
306 }
307 x => Err(format_err!("Expected PeerConnected but got: {:?}", x)),
308 }
309 }
310}
311
312async fn add_profile_to_topology<'a>(
321 builder: &RealmBuilder,
322 spec: &'a mut PiconetMemberSpec,
323 server_moniker: String,
324 profile_url: String,
325 additional_routes: Vec<Route>,
326) -> Result<(), Error> {
327 let mock_piconet_member_name = interposer_name_for_profile(&spec.name);
329 add_mock_piconet_component(
330 builder,
331 mock_piconet_member_name.clone(),
332 spec.id,
333 bredr::ProfileMarker::PROTOCOL_NAME.to_string(),
334 spec.observer.take(),
335 )
336 .await?;
337
338 let rfcomm_moniker = bt_rfcomm_moniker_for_member(&spec.name);
340 if let Some(url) = &spec.rfcomm_url {
341 add_bt_rfcomm_intermediary(builder, rfcomm_moniker.clone(), url.clone()).await?;
342 }
343
344 {
346 let _ = builder
347 .add_child(spec.name.to_string(), profile_url, ChildOptions::new().eager())
348 .await?;
349 }
350
351 {
360 if spec.rfcomm_url.is_some() {
361 builder
362 .add_route(
363 Route::new()
364 .capability(Capability::protocol::<bredr::ProfileMarker>())
365 .from(Ref::child(&mock_piconet_member_name))
366 .to(Ref::child(&rfcomm_moniker)),
367 )
368 .await?;
369 builder
370 .add_route(
371 Route::new()
372 .capability(Capability::protocol::<bredr::ProfileMarker>())
373 .from(Ref::child(&rfcomm_moniker))
374 .to(Ref::child(&spec.name)),
375 )
376 .await?;
377 } else {
378 builder
379 .add_route(
380 Route::new()
381 .capability(Capability::protocol::<bredr::ProfileMarker>())
382 .from(Ref::child(&mock_piconet_member_name))
383 .to(Ref::child(&spec.name)),
384 )
385 .await?;
386 }
387
388 builder
389 .add_route(
390 Route::new()
391 .capability(Capability::protocol::<bredr_test::ProfileTestMarker>())
392 .from(Ref::child(&server_moniker))
393 .to(Ref::child(&mock_piconet_member_name)),
394 )
395 .await?;
396
397 builder
398 .add_route(
399 Route::new()
400 .capability(Capability::protocol::<LogSinkMarker>())
401 .from(Ref::parent())
402 .to(Ref::child(&spec.name))
403 .to(Ref::child(&mock_piconet_member_name)),
404 )
405 .await?;
406
407 for route in additional_routes {
408 let _ = builder.add_route(route).await?;
409 }
410 }
411 Ok(())
412}
413
414async fn add_mock_piconet_member<'a, 'b>(
415 builder: &RealmBuilder,
416 mock: &'a mut PiconetMemberSpec,
417 server_moniker: String,
418) -> Result<(), Error> {
419 let profile_path = if mock.rfcomm_url.is_some() {
426 bredr::ProfileMarker::PROTOCOL_NAME.to_string()
427 } else {
428 capability_path_for_mock::<bredr::ProfileMarker>(mock)
429 };
430 add_mock_piconet_component(
431 builder,
432 mock.name.to_string(),
433 mock.id,
434 profile_path.clone(),
435 mock.observer.take(),
436 )
437 .await?;
438
439 let rfcomm_moniker = bt_rfcomm_moniker_for_member(&mock.name);
441 if let Some(url) = &mock.rfcomm_url {
442 add_bt_rfcomm_intermediary(builder, rfcomm_moniker.clone(), url.clone()).await?;
443 }
444
445 {
455 builder
456 .add_route(
457 Route::new()
458 .capability(Capability::protocol::<bredr_test::ProfileTestMarker>())
459 .from(Ref::child(&server_moniker))
460 .to(Ref::child(&mock.name)),
461 )
462 .await?;
463
464 if mock.rfcomm_url.is_some() {
465 builder
466 .add_route(
467 Route::new()
468 .capability(Capability::protocol_by_name(profile_path))
469 .from(Ref::child(&mock.name))
470 .to(Ref::child(&rfcomm_moniker)),
471 )
472 .await?;
473 } else {
474 builder
475 .add_route(
476 Route::new()
477 .capability(Capability::protocol_by_name(profile_path))
478 .from(Ref::child(&mock.name))
479 .to(Ref::parent()),
480 )
481 .await?;
482 }
483 }
484
485 Ok(())
486}
487
488async fn add_mock_piconet_component(
491 builder: &RealmBuilder,
492 name: String,
493 id: bt_types::PeerId,
494 profile_svc_path: String,
495 observer_src: Option<bredr_test::PeerObserverProxy>,
496) -> Result<(), Error> {
497 let observer = observer_src.unwrap_or_else(|| {
501 let (proxy, stream) = f_end::create_proxy_and_stream::<bredr_test::PeerObserverMarker>();
502 fasync::Task::local(async move {
503 let _ = drain_observer(stream).await;
504 })
505 .detach();
506 proxy
507 });
508
509 builder
510 .add_local_child(
511 name,
512 move |m: LocalComponentHandles| {
513 let observer = observer.clone();
514 Box::pin(piconet_member(m, id, profile_svc_path.clone(), observer))
515 },
516 ChildOptions::new(),
517 )
518 .await
519 .map(|_| ())
520 .map_err(|e| e.into())
521}
522
523async fn piconet_member(
527 handles: LocalComponentHandles,
528 id: bt_types::PeerId,
529 profile_svc_path: String,
530 peer_observer: bredr_test::PeerObserverProxy,
531) -> Result<(), Error> {
532 let pro_test = handles.connect_to_protocol::<bredr_test::ProfileTestMarker>()?;
534 let mut fs = ServiceFs::new();
535
536 let _ = fs.dir("svc").add_service_at(profile_svc_path, move |chan: zx::Channel| {
537 info!("Received ServiceFs `Profile` connection request for piconet_member: {:?}", id);
538 let profile_test = pro_test.clone();
539 let observer = peer_observer.clone();
540
541 fasync::Task::local(async move {
542 let (client, observer_req_stream) =
543 register_piconet_member(&profile_test, id).await.unwrap();
544
545 let err_str = format!("Couldn't connect to `Profile` for peer {:?}", id);
546
547 let _ = client.connect_proxy_(chan.into()).await.expect(&err_str);
548
549 fwd_observer_callbacks(observer_req_stream, &observer, id).await.unwrap();
551 })
552 .detach();
553 Some(())
554 });
555
556 let _ = fs.serve_connection(handles.outgoing_dir).expect("failed to serve service fs");
557 fs.collect::<()>().await;
558
559 Ok(())
560}
561
562async fn register_piconet_member(
565 profile_test_proxy: &bredr_test::ProfileTestProxy,
566 id: bt_types::PeerId,
567) -> Result<(bredr_test::MockPeerProxy, bredr_test::PeerObserverRequestStream), Error> {
568 info!("Sending RegisterPeer request for peer {:?} to the Mock Piconet Server.", id);
569 let (client, server) = f_end::create_proxy::<bredr_test::MockPeerMarker>();
570 let (observer_client, observer_server) =
571 f_end::create_request_stream::<bredr_test::PeerObserverMarker>();
572
573 profile_test_proxy
574 .register_peer(&id.into(), server, observer_client)
575 .await
576 .context("registering peer failed!")?;
577 Ok((client, observer_server))
578}
579
580fn handle_fidl_err(fidl_err: fidl::Error, ctx: String) -> Result<(), Error> {
581 if fidl_err.is_closed() {
582 Ok(())
583 } else {
584 Err(anyhow::Error::from(fidl_err).context(ctx))
585 }
586}
587
588async fn fwd_observer_callbacks(
590 mut source_req_stream: bredr_test::PeerObserverRequestStream,
591 observer: &bredr_test::PeerObserverProxy,
592 id: bt_types::PeerId,
593) -> Result<(), Error> {
594 while let Some(req) =
595 source_req_stream.try_next().await.context("reading peer observer failed")?
596 {
597 match req {
598 bredr_test::PeerObserverRequest::ServiceFound {
599 peer_id,
600 protocol,
601 attributes,
602 responder,
603 } => {
604 let proto = match protocol {
605 Some(desc) => desc,
606 None => vec![],
607 };
608
609 observer.service_found(&peer_id, Some(&proto), &attributes).await.or_else(|e| {
610 handle_fidl_err(
611 e,
612 format!("unexpected error forwarding observer event for: {}", id),
613 )
614 })?;
615
616 responder.send().or_else(|e| {
617 handle_fidl_err(
618 e,
619 format!("unexpected error acking observer event for: {}", id),
620 )
621 })?;
622 }
623
624 bredr_test::PeerObserverRequest::PeerConnected { peer_id, protocol, responder } => {
625 observer.peer_connected(&peer_id, &protocol).await.or_else(|e| {
626 handle_fidl_err(
627 e,
628 format!("unexpected error forwarding observer event for: {}", id),
629 )
630 })?;
631 responder.send().or_else(|e| {
632 handle_fidl_err(
633 e,
634 format!("unexpected error acking observer event for: {}", id),
635 )
636 })?;
637 }
638 }
639 }
640 Ok(())
641}
642
643async fn drain_observer(
644 mut stream: bredr_test::PeerObserverRequestStream,
645) -> Result<(), fidl::Error> {
646 while let Some(req) = stream.try_next().await? {
647 match req {
648 bredr_test::PeerObserverRequest::ServiceFound { responder, .. } => {
649 responder.send()?;
650 }
651 bredr_test::PeerObserverRequest::PeerConnected { responder, .. } => {
652 responder.send()?;
653 }
654 }
655 }
656 Ok(())
657}
658
659async fn add_mock_piconet_server(builder: &RealmBuilder) -> String {
660 let name = mock_piconet_server_moniker().to_string();
661
662 let mock_piconet_server = builder
663 .add_child(name.clone(), MOCK_PICONET_SERVER_URL, ChildOptions::new())
664 .await
665 .expect("failed to add");
666
667 builder
668 .add_route(
669 Route::new()
670 .capability(Capability::protocol::<LogSinkMarker>())
671 .from(Ref::parent())
672 .to(&mock_piconet_server),
673 )
674 .await
675 .unwrap();
676 name
677}
678
679async fn add_bt_rfcomm_intermediary(
681 builder: &RealmBuilder,
682 moniker: String,
683 url: String,
684) -> Result<(), Error> {
685 let bt_rfcomm = builder.add_child(moniker.clone(), url, ChildOptions::new().eager()).await?;
686
687 let _ = builder
688 .add_route(
689 Route::new()
690 .capability(Capability::protocol::<LogSinkMarker>())
691 .from(Ref::parent())
692 .to(&bt_rfcomm),
693 )
694 .await?;
695 Ok(())
696}
697
698fn mock_piconet_server_moniker() -> String {
699 "mock-piconet-server".to_string()
700}
701
702fn bt_rfcomm_moniker_for_member(member_name: &'_ str) -> String {
703 format!("{}-for-{}", BT_RFCOMM_PREFIX, member_name)
704}
705
706fn interposer_name_for_profile(profile_name: &'_ str) -> String {
707 format!("{}-{}", PROFILE_INTERPOSER_PREFIX, profile_name)
708}
709
710pub struct PiconetHarness {
738 pub builder: RealmBuilder,
739 pub ps_moniker: String,
740 profiles: Vec<PiconetMemberSpec>,
741 piconet_members: Vec<PiconetMemberSpec>,
742}
743
744impl PiconetHarness {
745 pub async fn new() -> Self {
746 let builder = RealmBuilder::new().await.expect("Couldn't create realm builder");
747 let ps_moniker = add_mock_piconet_server(&builder).await;
748 PiconetHarness { builder, ps_moniker, profiles: Vec::new(), piconet_members: Vec::new() }
749 }
750
751 pub async fn add_mock_piconet_members(
752 &mut self,
753 mocks: &'_ mut Vec<PiconetMemberSpec>,
754 ) -> Result<(), Error> {
755 for mock in mocks {
756 self.add_mock_piconet_member_from_spec(mock).await?;
757 }
758 Ok(())
759 }
760
761 pub async fn add_mock_piconet_member(
762 &mut self,
763 name: String,
764 rfcomm_url: Option<String>,
765 ) -> Result<PiconetMemberSpec, Error> {
766 let mut mock = PiconetMemberSpec::for_mock_peer(name, rfcomm_url);
767
768 self.add_mock_piconet_member_from_spec(&mut mock).await?;
769 Ok(mock)
770 }
771
772 async fn add_mock_piconet_member_from_spec(
773 &mut self,
774 mock: &'_ mut PiconetMemberSpec,
775 ) -> Result<(), Error> {
776 add_mock_piconet_member(&self.builder, mock, self.ps_moniker.clone()).await?;
777 self.piconet_members.push(mock.clone());
778 Ok(())
779 }
780
781 async fn update_routes(&self) -> Result<(), Error> {
783 info!(
784 "Building test realm with profiles: {:?} and piconet members: {:?}",
785 self.profiles, self.piconet_members
786 );
787 let mut root_decl = self.builder.get_realm_decl().await.expect("failed to get root");
788
789 let mut piconet_member_exposes =
790 self.piconet_members.iter().map(|spec| spec.expose_decls.clone()).flatten().collect();
791 let mut profile_member_exposes =
792 self.profiles.iter().map(|spec| spec.expose_decls.clone()).flatten().collect();
793 root_decl.exposes.append(&mut piconet_member_exposes);
794 root_decl.exposes.append(&mut profile_member_exposes);
795
796 self.builder.replace_realm_decl(root_decl).await.expect("Should be able to set root decl");
798 Ok(())
799 }
800
801 pub async fn build(self) -> Result<RealmInstance, Error> {
802 self.update_routes().await?;
803 self.builder.build().await.map_err(|e| e.into())
804 }
805
806 pub async fn add_profile(
811 &mut self,
812 name: String,
813 profile_url: String,
814 ) -> Result<BtProfileComponent, Error> {
815 self.add_profile_with_capabilities(name, profile_url, None, vec![], vec![]).await
816 }
817
818 pub async fn add_profile_with_capabilities(
830 &mut self,
831 name: String,
832 profile_url: String,
833 rfcomm_url: Option<String>,
834 use_capabilities: Vec<ftest::Capability>,
835 expose_capabilities: Vec<ftest::Capability>,
836 ) -> Result<BtProfileComponent, Error> {
837 let (mut spec, request_stream) =
838 PiconetMemberSpec::for_profile(name, rfcomm_url, expose_capabilities.clone())?;
839 let route = route_from_capabilities(
841 use_capabilities,
842 Ref::parent(),
843 vec![Ref::child(spec.name.clone())],
844 );
845
846 self.add_profile_from_spec(&mut spec, profile_url, vec![route]).await?;
847 Ok(BtProfileComponent::new(request_stream, spec.id))
848 }
849
850 async fn add_profile_from_spec(
851 &mut self,
852 spec: &mut PiconetMemberSpec,
853 profile_url: String,
854 capabilities: Vec<Route>,
855 ) -> Result<(), Error> {
856 add_profile_to_topology(
857 &self.builder,
858 spec,
859 self.ps_moniker.clone(),
860 profile_url,
861 capabilities,
862 )
863 .await?;
864 self.profiles.push(spec.clone());
865 Ok(())
866 }
867}
868
869pub fn route_from_capabilities(
872 capabilities: Vec<ftest::Capability>,
873 source: Ref,
874 targets: Vec<Ref>,
875) -> Route {
876 let mut route = Route::new().from(source);
877 for capability in capabilities {
878 route = route.capability(capability);
879 }
880 for target in targets {
881 route = route.to(target);
882 }
883 route
884}
885
886#[cfg(test)]
887mod tests {
888 use super::*;
889 use assert_matches::assert_matches;
890 use cm_rust::{
891 Availability, ChildRef, DependencyType, OfferDecl, OfferProtocolDecl, OfferSource,
892 OfferTarget, UseDecl, UseProtocolDecl, UseSource,
893 };
894 use cm_types::Name;
895 use fidl_fuchsia_component_test as fctest;
896 use fuchsia_component_test::error::Error as RealmBuilderError;
897
898 fn offer_source_static_child(name: &str) -> OfferSource {
899 OfferSource::Child(ChildRef { name: name.parse().unwrap(), collection: None })
900 }
901
902 fn offer_target_static_child(name: &str) -> cm_rust::OfferTarget {
903 OfferTarget::Child(ChildRef { name: name.parse().unwrap(), collection: None })
904 }
905
906 async fn assert_realm_contains(builder: &RealmBuilder, child_name: &str) {
907 let err = builder
908 .add_child(child_name, "test://example-url", ChildOptions::new())
909 .await
910 .expect_err("failed to check realm contents");
911 assert_matches!(
912 err,
913 RealmBuilderError::ServerError(fctest::RealmBuilderError::ChildAlreadyExists)
914 );
915 }
916
917 #[fasync::run_singlethreaded(test)]
918 async fn test_profile_server_added() {
919 let test_harness = PiconetHarness::new().await;
920 test_harness.update_routes().await.expect("should update routes");
921 assert_realm_contains(&test_harness.builder, &super::mock_piconet_server_moniker()).await;
922 let _ = test_harness.builder.build().await.expect("build failed");
923 }
924
925 #[fasync::run_singlethreaded(test)]
926 async fn test_add_piconet_member() {
927 let mut test_harness = PiconetHarness::new().await;
928 let member_name = "test-piconet-member";
929 let member_spec = test_harness
930 .add_mock_piconet_member(member_name.to_string(), None)
931 .await
932 .expect("failed to add piconet member");
933 assert_eq!(member_spec.name, member_name);
934
935 test_harness.update_routes().await.expect("should update routes");
936 validate_mock_piconet_member(&test_harness.builder, &member_spec).await;
937 let _profile_test_offer = test_harness.builder.build().await.expect("build failed");
938 }
939
940 #[fasync::run_singlethreaded(test)]
941 async fn test_add_piconet_member_with_rfcomm() {
942 let mut test_harness = PiconetHarness::new().await;
943 let member_name = "test-piconet-member";
944 let rfcomm_url = "fuchsia-pkg://fuchsia.com/example#meta/bt-rfcomm.cm".to_string();
945 let member_spec = test_harness
946 .add_mock_piconet_member(member_name.to_string(), Some(rfcomm_url))
947 .await
948 .expect("failed to add piconet member");
949 assert_eq!(member_spec.name, member_name);
950
951 test_harness.update_routes().await.expect("should update routes");
952 validate_mock_piconet_member(&test_harness.builder, &member_spec).await;
953
954 }
957
958 #[fasync::run_singlethreaded(test)]
959 async fn test_add_multiple_piconet_members() {
960 let mut test_harness = PiconetHarness::new().await;
961 let member1_name = "test-piconet-member".to_string();
962 let member2_name = "test-piconet-member-two".to_string();
963 let mut members = vec![
964 PiconetMemberSpec::for_mock_peer(member1_name, None),
965 PiconetMemberSpec::for_mock_peer(member2_name, None),
966 ];
967
968 test_harness
969 .add_mock_piconet_members(&mut members)
970 .await
971 .expect("failed to add piconet members");
972
973 test_harness.update_routes().await.expect("should update routes");
974
975 for member in &members {
976 validate_mock_piconet_member(&test_harness.builder, member).await;
977 }
978 let _profile_test_offer = test_harness.builder.build().await.expect("build failed");
979 }
980
981 #[fasync::run_singlethreaded(test)]
982 async fn test_add_multiple_piconet_members_with_rfcomm() {
983 let mut test_harness = PiconetHarness::new().await;
984 let rfcomm_url = "fuchsia-pkg://fuchsia.com/example#meta/bt-rfcomm.cm".to_string();
985 let member1_name = "test-piconet-member-1".to_string();
986 let member2_name = "test-piconet-member-2".to_string();
987 let member3_name = "test-piconet-member-3".to_string();
988 let mut members = vec![
990 PiconetMemberSpec::for_mock_peer(member1_name, None),
991 PiconetMemberSpec::for_mock_peer(member2_name, Some(rfcomm_url.clone())),
992 PiconetMemberSpec::for_mock_peer(member3_name, Some(rfcomm_url)),
993 ];
994
995 test_harness
996 .add_mock_piconet_members(&mut members)
997 .await
998 .expect("failed to add piconet members");
999
1000 test_harness.update_routes().await.expect("should update routes");
1001
1002 for member in &members {
1003 validate_mock_piconet_member(&test_harness.builder, member).await;
1004 }
1005
1006 }
1009
1010 async fn validate_profile_routes_for_member_with_rfcomm<'a>(
1011 builder: &RealmBuilder,
1012 member_spec: &'a PiconetMemberSpec,
1013 ) {
1014 let profile_capability_name = bredr::ProfileMarker::PROTOCOL_NAME.to_string();
1016 let pico_member_decl = builder
1017 .get_component_decl(member_spec.name.clone())
1018 .await
1019 .expect("piconet member had no decl");
1020 let expose_profile_decl = ExposeProtocolDecl {
1021 source: ExposeSource::Self_,
1022 source_name: profile_capability_name.clone().parse().unwrap(),
1023 source_dictionary: Default::default(),
1024 target: ExposeTarget::Parent,
1025 target_name: profile_capability_name.clone().parse().unwrap(),
1026 availability: cm_rust::Availability::Required,
1027 };
1028 let expose_decl = ExposeDecl::Protocol(expose_profile_decl.clone());
1029 assert!(pico_member_decl.exposes.contains(&expose_decl));
1030
1031 {
1033 let bt_rfcomm_name = super::bt_rfcomm_moniker_for_member(&member_spec.name);
1034 let custom_profile_capability_name =
1035 Name::new(super::capability_path_for_mock::<bredr::ProfileMarker>(&member_spec))
1036 .unwrap();
1037 let custom_expose_profile_decl = ExposeProtocolDecl {
1038 source: ExposeSource::Child(bt_rfcomm_name.parse().unwrap()),
1039 source_name: profile_capability_name.parse().unwrap(),
1040 source_dictionary: Default::default(),
1041 target: ExposeTarget::Parent,
1042 target_name: custom_profile_capability_name,
1043 availability: cm_rust::Availability::Required,
1044 };
1045 let root_expose_decl = ExposeDecl::Protocol(custom_expose_profile_decl);
1046 let root = builder.get_realm_decl().await.expect("failed to get root");
1047 assert!(root.exposes.contains(&root_expose_decl));
1048 }
1049 }
1050
1051 async fn validate_profile_routes_for_member<'a>(
1052 builder: &RealmBuilder,
1053 member_spec: &'a PiconetMemberSpec,
1054 ) {
1055 let pico_member_decl = builder
1057 .get_component_decl(member_spec.name.clone())
1058 .await
1059 .expect("piconet member had no decl");
1060 let profile_capability_name =
1061 Name::new(super::capability_path_for_mock::<bredr::ProfileMarker>(&member_spec))
1062 .unwrap();
1063 let mut expose_proto_decl = ExposeProtocolDecl {
1064 source: ExposeSource::Self_,
1065 source_name: profile_capability_name.clone(),
1066 source_dictionary: Default::default(),
1067 target: ExposeTarget::Parent,
1068 target_name: profile_capability_name,
1069 availability: cm_rust::Availability::Required,
1070 };
1071 let expose_decl = ExposeDecl::Protocol(expose_proto_decl.clone());
1072 assert!(pico_member_decl.exposes.contains(&expose_decl));
1073
1074 {
1077 expose_proto_decl.source = ExposeSource::Child(member_spec.name.parse().unwrap());
1078 let root_expose_decl = ExposeDecl::Protocol(expose_proto_decl);
1079 let root = builder.get_realm_decl().await.expect("failed to get root");
1080 assert!(root.exposes.contains(&root_expose_decl));
1081 }
1082 }
1083
1084 async fn validate_mock_piconet_member<'a>(
1085 builder: &RealmBuilder,
1086 member_spec: &'a PiconetMemberSpec,
1087 ) {
1088 assert_realm_contains(builder, &member_spec.name).await;
1090
1091 if member_spec.rfcomm_url.is_some() {
1093 validate_profile_routes_for_member_with_rfcomm(builder, member_spec).await;
1094 } else {
1095 validate_profile_routes_for_member(builder, member_spec).await;
1096 }
1097
1098 let pico_member_decl = builder
1100 .get_component_decl(member_spec.name.clone())
1101 .await
1102 .expect("piconet member had no decl");
1103 let use_decl = UseDecl::Protocol(UseProtocolDecl {
1104 source: UseSource::Parent,
1105 source_name: bredr_test::ProfileTestMarker::PROTOCOL_NAME.parse().unwrap(),
1106 source_dictionary: Default::default(),
1107 target_path: format!("/svc/{}", bredr_test::ProfileTestMarker::PROTOCOL_NAME)
1108 .parse()
1109 .unwrap(),
1110 dependency_type: DependencyType::Strong,
1111 availability: Availability::Required,
1112 });
1113 assert!(pico_member_decl.uses.contains(&use_decl));
1114
1115 let profile_test_name = Name::new(bredr_test::ProfileTestMarker::PROTOCOL_NAME).unwrap();
1118 let root = builder.get_realm_decl().await.expect("failed to get root");
1119 let offer_profile_test = OfferDecl::Protocol(OfferProtocolDecl {
1120 source: offer_source_static_child(&super::mock_piconet_server_moniker()),
1121 source_name: profile_test_name.clone(),
1122 source_dictionary: Default::default(),
1123 target: offer_target_static_child(&member_spec.name),
1124 target_name: profile_test_name,
1125 dependency_type: DependencyType::Strong,
1126 availability: Availability::Required,
1127 });
1128 assert!(root.offers.contains(&offer_profile_test));
1129
1130 }
1135
1136 #[fasync::run_singlethreaded(test)]
1137 async fn test_add_profile() {
1138 let mut test_harness = PiconetHarness::new().await;
1139 let profile_name = "test-profile-member";
1140 let interposer_name = super::interposer_name_for_profile(profile_name);
1141
1142 let _profile_member = test_harness
1144 .add_profile(
1145 profile_name.to_string(),
1146 "fuchsia-pkg://fuchsia.com/example#meta/example.cm".to_string(),
1147 )
1148 .await
1149 .expect("failed to add profile");
1150
1151 test_harness.update_routes().await.expect("should update routes");
1152 assert_realm_contains(&test_harness.builder, &profile_name).await;
1153 assert_realm_contains(&test_harness.builder, &interposer_name).await;
1154
1155 let profile_capability_name = Name::new(bredr::ProfileMarker::PROTOCOL_NAME).unwrap();
1159 let profile_expose = ExposeDecl::Protocol(ExposeProtocolDecl {
1160 source: ExposeSource::Self_,
1161 source_name: profile_capability_name.clone(),
1162 source_dictionary: Default::default(),
1163 target: ExposeTarget::Parent,
1164 target_name: profile_capability_name.clone(),
1165 availability: cm_rust::Availability::Required,
1166 });
1167 let interposer = test_harness
1168 .builder
1169 .get_component_decl(interposer_name.clone())
1170 .await
1171 .expect("interposer not found!");
1172 assert!(interposer.exposes.contains(&profile_expose));
1173
1174 let profile_test_name = Name::new(bredr_test::ProfileTestMarker::PROTOCOL_NAME).unwrap();
1176 let profile_test_use = UseDecl::Protocol(UseProtocolDecl {
1177 source: UseSource::Parent,
1178 source_name: profile_test_name.clone(),
1179 source_dictionary: Default::default(),
1180 target_path: format!("/svc/{}", bredr_test::ProfileTestMarker::PROTOCOL_NAME)
1181 .parse()
1182 .unwrap(),
1183 dependency_type: DependencyType::Strong,
1184 availability: Availability::Required,
1185 });
1186 assert!(interposer.uses.contains(&profile_test_use));
1187
1188 let profile_offer = OfferDecl::Protocol(OfferProtocolDecl {
1190 source: offer_source_static_child(&interposer_name),
1191 source_name: profile_capability_name.clone(),
1192 source_dictionary: Default::default(),
1193 target: offer_target_static_child(&profile_name),
1194 target_name: profile_capability_name.clone(),
1195 dependency_type: DependencyType::Strong,
1196 availability: Availability::Required,
1197 });
1198 let root = test_harness.builder.get_realm_decl().await.expect("unable to get root decl");
1199 assert!(root.offers.contains(&profile_offer));
1200
1201 let profile_test_offer = OfferDecl::Protocol(OfferProtocolDecl {
1203 source: offer_source_static_child(&super::mock_piconet_server_moniker()),
1204 source_name: profile_test_name.clone(),
1205 source_dictionary: Default::default(),
1206 target: offer_target_static_child(&interposer_name),
1207 target_name: profile_test_name.clone(),
1208 dependency_type: DependencyType::Strong,
1209 availability: Availability::Required,
1210 });
1211 assert!(root.offers.contains(&profile_test_offer));
1212
1213 let log_capability_name = Name::new(LogSinkMarker::PROTOCOL_NAME).unwrap();
1215 let log_offer = OfferDecl::Protocol(OfferProtocolDecl {
1216 source: OfferSource::Parent,
1217 source_name: log_capability_name.clone(),
1218 source_dictionary: Default::default(),
1219 target: offer_target_static_child(&profile_name),
1220 target_name: log_capability_name.clone(),
1221 dependency_type: DependencyType::Strong,
1222 availability: Availability::Required,
1223 });
1224 assert!(root.offers.contains(&log_offer));
1225 }
1226
1227 #[fasync::run_singlethreaded(test)]
1228 async fn test_add_profile_with_rfcomm() {
1229 let mut test_harness = PiconetHarness::new().await;
1230
1231 let profile_name = "test-profile-member";
1232 let interposer_name = super::interposer_name_for_profile(profile_name);
1233 let bt_rfcomm_name = super::bt_rfcomm_moniker_for_member(profile_name);
1234 let profile_url = "fuchsia-pkg://fuchsia.com/example#meta/example.cm".to_string();
1235 let rfcomm_url = "fuchsia-pkg://fuchsia.com/example#meta/bt-rfcomm.cm".to_string();
1236
1237 let _profile_member = test_harness
1239 .add_profile_with_capabilities(
1240 profile_name.to_string(),
1241 profile_url,
1242 Some(rfcomm_url),
1243 vec![],
1244 vec![],
1245 )
1246 .await
1247 .expect("failed to add profile");
1248
1249 test_harness.update_routes().await.expect("should update routes");
1250 assert_realm_contains(&test_harness.builder, &profile_name).await;
1251 assert_realm_contains(&test_harness.builder, &interposer_name).await;
1252 assert_realm_contains(&test_harness.builder, &bt_rfcomm_name).await;
1253
1254 let profile_capability_name = Name::new(bredr::ProfileMarker::PROTOCOL_NAME).unwrap();
1256
1257 let profile_offer1 = OfferDecl::Protocol(OfferProtocolDecl {
1259 source: offer_source_static_child(&interposer_name),
1260 source_name: profile_capability_name.clone(),
1261 source_dictionary: Default::default(),
1262 target: offer_target_static_child(&bt_rfcomm_name),
1263 target_name: profile_capability_name.clone(),
1264 dependency_type: DependencyType::Strong,
1265 availability: Availability::Required,
1266 });
1267 let profile_offer2 = OfferDecl::Protocol(OfferProtocolDecl {
1269 source: offer_source_static_child(&bt_rfcomm_name),
1270 source_name: profile_capability_name.clone(),
1271 source_dictionary: Default::default(),
1272 target: offer_target_static_child(&profile_name),
1273 target_name: profile_capability_name.clone(),
1274 dependency_type: DependencyType::Strong,
1275 availability: Availability::Required,
1276 });
1277 let root = test_harness.builder.get_realm_decl().await.expect("unable to get root decl");
1278 assert!(root.offers.contains(&profile_offer1));
1279 assert!(root.offers.contains(&profile_offer2));
1280 }
1281
1282 #[fasync::run_singlethreaded(test)]
1283 async fn test_add_profile_with_additional_capabilities() {
1284 let mut test_harness = PiconetHarness::new().await;
1285 let profile_name = "test-profile-member";
1286
1287 let fake_cap1 = "Foo".to_string();
1289 let fake_cap2 = "Bar".to_string();
1290 let expose_capabilities = vec![
1291 Capability::protocol_by_name(fake_cap1.clone()).into(),
1292 Capability::protocol_by_name(fake_cap2.clone()).into(),
1293 ];
1294 let fake_cap3 = "Cat".to_string();
1295 let use_capabilities = vec![Capability::protocol_by_name(fake_cap3.clone()).into()];
1296 let profile_member = test_harness
1297 .add_profile_with_capabilities(
1298 profile_name.to_string(),
1299 "fuchsia-pkg://fuchsia.com/example#meta/example.cm".to_string(),
1300 None,
1301 use_capabilities,
1302 expose_capabilities,
1303 )
1304 .await
1305 .expect("failed to add profile");
1306
1307 test_harness.update_routes().await.expect("should update routes");
1308 assert_realm_contains(&test_harness.builder, &profile_name).await;
1309
1310 let fake_capability_expose1 = ExposeDecl::Protocol(ExposeProtocolDecl {
1315 source: ExposeSource::Child(profile_name.parse().unwrap()),
1316 source_name: fake_cap1.clone().parse().unwrap(),
1317 source_dictionary: Default::default(),
1318 target: ExposeTarget::Parent,
1319 target_name: capability_path_for_peer_id(profile_member.peer_id(), &fake_cap1)
1320 .parse()
1321 .unwrap(),
1322 availability: cm_rust::Availability::Required,
1323 });
1324 let fake_capability_expose2 = ExposeDecl::Protocol(ExposeProtocolDecl {
1326 source: ExposeSource::Child(profile_name.parse().unwrap()),
1327 source_name: fake_cap2.clone().parse().unwrap(),
1328 source_dictionary: Default::default(),
1329 target: ExposeTarget::Parent,
1330 target_name: capability_path_for_peer_id(profile_member.peer_id(), &fake_cap2)
1331 .parse()
1332 .unwrap(),
1333 availability: cm_rust::Availability::Required,
1334 });
1335 let fake_capability_offer3 = OfferDecl::Protocol(OfferProtocolDecl {
1337 source: OfferSource::Parent,
1338 source_name: fake_cap3.clone().parse().unwrap(),
1339 source_dictionary: Default::default(),
1340 target: offer_target_static_child(&profile_name),
1341 target_name: fake_cap3.parse().unwrap(),
1342 dependency_type: DependencyType::Strong,
1343 availability: Availability::Required,
1344 });
1345
1346 let root = test_harness.builder.get_realm_decl().await.expect("unable to get root decl");
1347 assert!(root.exposes.contains(&fake_capability_expose1));
1348 assert!(root.exposes.contains(&fake_capability_expose2));
1349 assert!(root.offers.contains(&fake_capability_offer3));
1350 }
1351
1352 #[fasync::run_singlethreaded(test)]
1353 async fn test_multiple_profiles_with_same_expose_is_ok() {
1354 let mut test_harness = PiconetHarness::new().await;
1355 let profile_name1 = "test-profile-1";
1356 let profile_name2 = "test-profile-2";
1357
1358 let fake_cap = "FooBarAmazing".to_string();
1360 let expose_capabilities = vec![Capability::protocol_by_name(fake_cap.clone()).into()];
1361
1362 let profile_member1 = test_harness
1363 .add_profile_with_capabilities(
1364 profile_name1.to_string(),
1365 "fuchsia-pkg://fuchsia.com/example#meta/example-profile1.cm".to_string(),
1366 None,
1367 vec![],
1368 expose_capabilities.clone(),
1369 )
1370 .await
1371 .expect("failed to add profile1");
1372 let profile_member2 = test_harness
1373 .add_profile_with_capabilities(
1374 profile_name2.to_string(),
1375 "fuchsia-pkg://fuchsia.com/example#meta/example-profile2.cm".to_string(),
1376 None,
1377 vec![],
1378 expose_capabilities,
1379 )
1380 .await
1381 .expect("failed to add profile2");
1382
1383 test_harness.update_routes().await.expect("should update routes");
1384 assert_realm_contains(&test_harness.builder, &profile_name1).await;
1385 assert_realm_contains(&test_harness.builder, &profile_name2).await;
1386
1387 let profile1_expose = ExposeDecl::Protocol(ExposeProtocolDecl {
1389 source: ExposeSource::Child(profile_name1.parse().unwrap()),
1390 source_name: fake_cap.clone().parse().unwrap(),
1391 source_dictionary: Default::default(),
1392 target: ExposeTarget::Parent,
1393 target_name: capability_path_for_peer_id(profile_member1.peer_id(), &fake_cap)
1394 .parse()
1395 .unwrap(),
1396 availability: cm_rust::Availability::Required,
1397 });
1398 let profile2_expose = ExposeDecl::Protocol(ExposeProtocolDecl {
1399 source: ExposeSource::Child(profile_name2.parse().unwrap()),
1400 source_name: fake_cap.clone().parse().unwrap(),
1401 source_dictionary: Default::default(),
1402 target: ExposeTarget::Parent,
1403 target_name: capability_path_for_peer_id(profile_member2.peer_id(), &fake_cap)
1404 .parse()
1405 .unwrap(),
1406 availability: cm_rust::Availability::Required,
1407 });
1408
1409 let root = test_harness.builder.get_realm_decl().await.expect("unable to get root decl");
1410 assert!(root.exposes.contains(&profile1_expose));
1411 assert!(root.exposes.contains(&profile2_expose));
1412 }
1413}