1use anyhow::{Context, Error, format_err};
6use cm_rust::{ExposeDecl, ExposeProtocolDecl, ExposeSource, ExposeTarget, append_box};
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: bredr_test::ProfileTestProxy = handles.connect_to_protocol()?;
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() { Ok(()) } else { Err(anyhow::Error::from(fidl_err).context(ctx)) }
582}
583
584async fn fwd_observer_callbacks(
586 mut source_req_stream: bredr_test::PeerObserverRequestStream,
587 observer: &bredr_test::PeerObserverProxy,
588 id: bt_types::PeerId,
589) -> Result<(), Error> {
590 while let Some(req) =
591 source_req_stream.try_next().await.context("reading peer observer failed")?
592 {
593 match req {
594 bredr_test::PeerObserverRequest::ServiceFound {
595 peer_id,
596 protocol,
597 attributes,
598 responder,
599 } => {
600 let proto = match protocol {
601 Some(desc) => desc,
602 None => vec![],
603 };
604
605 observer.service_found(&peer_id, Some(&proto), &attributes).await.or_else(|e| {
606 handle_fidl_err(
607 e,
608 format!("unexpected error forwarding observer event for: {}", id),
609 )
610 })?;
611
612 responder.send().or_else(|e| {
613 handle_fidl_err(
614 e,
615 format!("unexpected error acking observer event for: {}", id),
616 )
617 })?;
618 }
619
620 bredr_test::PeerObserverRequest::PeerConnected { peer_id, protocol, responder } => {
621 observer.peer_connected(&peer_id, &protocol).await.or_else(|e| {
622 handle_fidl_err(
623 e,
624 format!("unexpected error forwarding observer event for: {}", id),
625 )
626 })?;
627 responder.send().or_else(|e| {
628 handle_fidl_err(
629 e,
630 format!("unexpected error acking observer event for: {}", id),
631 )
632 })?;
633 }
634 }
635 }
636 Ok(())
637}
638
639async fn drain_observer(
640 mut stream: bredr_test::PeerObserverRequestStream,
641) -> Result<(), fidl::Error> {
642 while let Some(req) = stream.try_next().await? {
643 match req {
644 bredr_test::PeerObserverRequest::ServiceFound { responder, .. } => {
645 responder.send()?;
646 }
647 bredr_test::PeerObserverRequest::PeerConnected { responder, .. } => {
648 responder.send()?;
649 }
650 }
651 }
652 Ok(())
653}
654
655async fn add_mock_piconet_server(builder: &RealmBuilder) -> String {
656 let name = mock_piconet_server_moniker().to_string();
657
658 let mock_piconet_server = builder
659 .add_child(name.clone(), MOCK_PICONET_SERVER_URL, ChildOptions::new())
660 .await
661 .expect("failed to add");
662
663 builder
664 .add_route(
665 Route::new()
666 .capability(Capability::protocol::<LogSinkMarker>())
667 .from(Ref::parent())
668 .to(&mock_piconet_server),
669 )
670 .await
671 .unwrap();
672 name
673}
674
675async fn add_bt_rfcomm_intermediary(
677 builder: &RealmBuilder,
678 moniker: String,
679 url: String,
680) -> Result<(), Error> {
681 let bt_rfcomm = builder.add_child(moniker.clone(), url, ChildOptions::new().eager()).await?;
682
683 let _ = builder
684 .add_route(
685 Route::new()
686 .capability(Capability::protocol::<LogSinkMarker>())
687 .from(Ref::parent())
688 .to(&bt_rfcomm),
689 )
690 .await?;
691 Ok(())
692}
693
694fn mock_piconet_server_moniker() -> String {
695 "mock-piconet-server".to_string()
696}
697
698fn bt_rfcomm_moniker_for_member(member_name: &'_ str) -> String {
699 format!("{}-for-{}", BT_RFCOMM_PREFIX, member_name)
700}
701
702fn interposer_name_for_profile(profile_name: &'_ str) -> String {
703 format!("{}-{}", PROFILE_INTERPOSER_PREFIX, profile_name)
704}
705
706pub struct PiconetHarness {
734 pub builder: RealmBuilder,
735 pub ps_moniker: String,
736 profiles: Vec<PiconetMemberSpec>,
737 piconet_members: Vec<PiconetMemberSpec>,
738}
739
740impl PiconetHarness {
741 pub async fn new() -> Self {
742 let builder = RealmBuilder::new().await.expect("Couldn't create realm builder");
743 let ps_moniker = add_mock_piconet_server(&builder).await;
744 PiconetHarness { builder, ps_moniker, profiles: Vec::new(), piconet_members: Vec::new() }
745 }
746
747 pub async fn add_mock_piconet_members(
748 &mut self,
749 mocks: &'_ mut Vec<PiconetMemberSpec>,
750 ) -> Result<(), Error> {
751 for mock in mocks {
752 self.add_mock_piconet_member_from_spec(mock).await?;
753 }
754 Ok(())
755 }
756
757 pub async fn add_mock_piconet_member(
758 &mut self,
759 name: String,
760 rfcomm_url: Option<String>,
761 ) -> Result<PiconetMemberSpec, Error> {
762 let mut mock = PiconetMemberSpec::for_mock_peer(name, rfcomm_url);
763
764 self.add_mock_piconet_member_from_spec(&mut mock).await?;
765 Ok(mock)
766 }
767
768 async fn add_mock_piconet_member_from_spec(
769 &mut self,
770 mock: &'_ mut PiconetMemberSpec,
771 ) -> Result<(), Error> {
772 add_mock_piconet_member(&self.builder, mock, self.ps_moniker.clone()).await?;
773 self.piconet_members.push(mock.clone());
774 Ok(())
775 }
776
777 async fn update_routes(&self) -> Result<(), Error> {
779 info!(
780 "Building test realm with profiles: {:?} and piconet members: {:?}",
781 self.profiles, self.piconet_members
782 );
783 let mut root_decl = self.builder.get_realm_decl().await.expect("failed to get root");
784
785 let mut piconet_member_exposes =
786 self.piconet_members.iter().map(|spec| spec.expose_decls.clone()).flatten().collect();
787 let mut profile_member_exposes =
788 self.profiles.iter().map(|spec| spec.expose_decls.clone()).flatten().collect();
789 append_box(&mut root_decl.exposes, &mut piconet_member_exposes);
790 append_box(&mut root_decl.exposes, &mut profile_member_exposes);
791
792 self.builder.replace_realm_decl(root_decl).await.expect("Should be able to set root decl");
794 Ok(())
795 }
796
797 pub async fn build(self) -> Result<RealmInstance, Error> {
798 self.update_routes().await?;
799 self.builder.build().await.map_err(|e| e.into())
800 }
801
802 pub async fn add_profile(
807 &mut self,
808 name: String,
809 profile_url: String,
810 ) -> Result<BtProfileComponent, Error> {
811 self.add_profile_with_capabilities(name, profile_url, None, vec![], vec![]).await
812 }
813
814 pub async fn add_profile_with_capabilities(
826 &mut self,
827 name: String,
828 profile_url: String,
829 rfcomm_url: Option<String>,
830 use_capabilities: Vec<ftest::Capability>,
831 expose_capabilities: Vec<ftest::Capability>,
832 ) -> Result<BtProfileComponent, Error> {
833 let (mut spec, request_stream) =
834 PiconetMemberSpec::for_profile(name, rfcomm_url, expose_capabilities.clone())?;
835 let route = route_from_capabilities(
837 use_capabilities,
838 Ref::parent(),
839 vec![Ref::child(spec.name.clone())],
840 );
841
842 self.add_profile_from_spec(&mut spec, profile_url, vec![route]).await?;
843 Ok(BtProfileComponent::new(request_stream, spec.id))
844 }
845
846 async fn add_profile_from_spec(
847 &mut self,
848 spec: &mut PiconetMemberSpec,
849 profile_url: String,
850 capabilities: Vec<Route>,
851 ) -> Result<(), Error> {
852 add_profile_to_topology(
853 &self.builder,
854 spec,
855 self.ps_moniker.clone(),
856 profile_url,
857 capabilities,
858 )
859 .await?;
860 self.profiles.push(spec.clone());
861 Ok(())
862 }
863}
864
865pub fn route_from_capabilities(
868 capabilities: Vec<ftest::Capability>,
869 source: Ref,
870 targets: Vec<Ref>,
871) -> Route {
872 let mut route = Route::new().from(source);
873 for capability in capabilities {
874 route = route.capability(capability);
875 }
876 for target in targets {
877 route = route.to(target);
878 }
879 route
880}
881
882#[cfg(test)]
883mod tests {
884 use super::*;
885 use assert_matches::assert_matches;
886 use cm_rust::{
887 Availability, ChildRef, DependencyType, OfferDecl, OfferProtocolDecl, OfferSource,
888 OfferTarget, UseDecl, UseProtocolDecl, UseSource,
889 };
890 use cm_types::Name;
891 use fidl_fuchsia_component_test as fctest;
892 use fuchsia_component_test::error::Error as RealmBuilderError;
893
894 fn offer_source_static_child(name: &str) -> OfferSource {
895 OfferSource::Child(ChildRef { name: name.parse().unwrap(), collection: None })
896 }
897
898 fn offer_target_static_child(name: &str) -> cm_rust::OfferTarget {
899 OfferTarget::Child(ChildRef { name: name.parse().unwrap(), collection: None })
900 }
901
902 async fn assert_realm_contains(builder: &RealmBuilder, child_name: &str) {
903 let err = builder
904 .add_child(child_name, "test://example-url", ChildOptions::new())
905 .await
906 .expect_err("failed to check realm contents");
907 assert_matches!(
908 err,
909 RealmBuilderError::ServerError(fctest::RealmBuilderError::ChildAlreadyExists)
910 );
911 }
912
913 #[fasync::run_singlethreaded(test)]
914 async fn test_profile_server_added() {
915 let test_harness = PiconetHarness::new().await;
916 test_harness.update_routes().await.expect("should update routes");
917 assert_realm_contains(&test_harness.builder, &super::mock_piconet_server_moniker()).await;
918 let _ = test_harness.builder.build().await.expect("build failed");
919 }
920
921 #[fasync::run_singlethreaded(test)]
922 async fn test_add_piconet_member() {
923 let mut test_harness = PiconetHarness::new().await;
924 let member_name = "test-piconet-member";
925 let member_spec = test_harness
926 .add_mock_piconet_member(member_name.to_string(), None)
927 .await
928 .expect("failed to add piconet member");
929 assert_eq!(member_spec.name, member_name);
930
931 test_harness.update_routes().await.expect("should update routes");
932 validate_mock_piconet_member(&test_harness.builder, &member_spec).await;
933 let _profile_test_offer = test_harness.builder.build().await.expect("build failed");
934 }
935
936 #[fasync::run_singlethreaded(test)]
937 async fn test_add_piconet_member_with_rfcomm() {
938 let mut test_harness = PiconetHarness::new().await;
939 let member_name = "test-piconet-member";
940 let rfcomm_url = "fuchsia-pkg://fuchsia.com/example#meta/bt-rfcomm.cm".to_string();
941 let member_spec = test_harness
942 .add_mock_piconet_member(member_name.to_string(), Some(rfcomm_url))
943 .await
944 .expect("failed to add piconet member");
945 assert_eq!(member_spec.name, member_name);
946
947 test_harness.update_routes().await.expect("should update routes");
948 validate_mock_piconet_member(&test_harness.builder, &member_spec).await;
949
950 }
953
954 #[fasync::run_singlethreaded(test)]
955 async fn test_add_multiple_piconet_members() {
956 let mut test_harness = PiconetHarness::new().await;
957 let member1_name = "test-piconet-member".to_string();
958 let member2_name = "test-piconet-member-two".to_string();
959 let mut members = vec![
960 PiconetMemberSpec::for_mock_peer(member1_name, None),
961 PiconetMemberSpec::for_mock_peer(member2_name, None),
962 ];
963
964 test_harness
965 .add_mock_piconet_members(&mut members)
966 .await
967 .expect("failed to add piconet members");
968
969 test_harness.update_routes().await.expect("should update routes");
970
971 for member in &members {
972 validate_mock_piconet_member(&test_harness.builder, member).await;
973 }
974 let _profile_test_offer = test_harness.builder.build().await.expect("build failed");
975 }
976
977 #[fasync::run_singlethreaded(test)]
978 async fn test_add_multiple_piconet_members_with_rfcomm() {
979 let mut test_harness = PiconetHarness::new().await;
980 let rfcomm_url = "fuchsia-pkg://fuchsia.com/example#meta/bt-rfcomm.cm".to_string();
981 let member1_name = "test-piconet-member-1".to_string();
982 let member2_name = "test-piconet-member-2".to_string();
983 let member3_name = "test-piconet-member-3".to_string();
984 let mut members = vec![
986 PiconetMemberSpec::for_mock_peer(member1_name, None),
987 PiconetMemberSpec::for_mock_peer(member2_name, Some(rfcomm_url.clone())),
988 PiconetMemberSpec::for_mock_peer(member3_name, Some(rfcomm_url)),
989 ];
990
991 test_harness
992 .add_mock_piconet_members(&mut members)
993 .await
994 .expect("failed to add piconet members");
995
996 test_harness.update_routes().await.expect("should update routes");
997
998 for member in &members {
999 validate_mock_piconet_member(&test_harness.builder, member).await;
1000 }
1001
1002 }
1005
1006 async fn validate_profile_routes_for_member_with_rfcomm<'a>(
1007 builder: &RealmBuilder,
1008 member_spec: &'a PiconetMemberSpec,
1009 ) {
1010 let profile_capability_name = bredr::ProfileMarker::PROTOCOL_NAME.to_string();
1012 let pico_member_decl = builder
1013 .get_component_decl(member_spec.name.clone())
1014 .await
1015 .expect("piconet member had no decl");
1016 let expose_profile_decl = ExposeProtocolDecl {
1017 source: ExposeSource::Self_,
1018 source_name: profile_capability_name.clone().parse().unwrap(),
1019 source_dictionary: Default::default(),
1020 target: ExposeTarget::Parent,
1021 target_name: profile_capability_name.clone().parse().unwrap(),
1022 availability: cm_rust::Availability::Required,
1023 };
1024 let expose_decl = ExposeDecl::Protocol(expose_profile_decl.clone());
1025 assert!(pico_member_decl.exposes.contains(&expose_decl));
1026
1027 {
1029 let bt_rfcomm_name = super::bt_rfcomm_moniker_for_member(&member_spec.name);
1030 let custom_profile_capability_name =
1031 Name::new(super::capability_path_for_mock::<bredr::ProfileMarker>(&member_spec))
1032 .unwrap();
1033 let custom_expose_profile_decl = ExposeProtocolDecl {
1034 source: ExposeSource::Child(bt_rfcomm_name.parse().unwrap()),
1035 source_name: profile_capability_name.parse().unwrap(),
1036 source_dictionary: Default::default(),
1037 target: ExposeTarget::Parent,
1038 target_name: custom_profile_capability_name,
1039 availability: cm_rust::Availability::Required,
1040 };
1041 let root_expose_decl = ExposeDecl::Protocol(custom_expose_profile_decl);
1042 let root = builder.get_realm_decl().await.expect("failed to get root");
1043 assert!(root.exposes.contains(&root_expose_decl));
1044 }
1045 }
1046
1047 async fn validate_profile_routes_for_member<'a>(
1048 builder: &RealmBuilder,
1049 member_spec: &'a PiconetMemberSpec,
1050 ) {
1051 let pico_member_decl = builder
1053 .get_component_decl(member_spec.name.clone())
1054 .await
1055 .expect("piconet member had no decl");
1056 let profile_capability_name =
1057 Name::new(super::capability_path_for_mock::<bredr::ProfileMarker>(&member_spec))
1058 .unwrap();
1059 let mut expose_proto_decl = ExposeProtocolDecl {
1060 source: ExposeSource::Self_,
1061 source_name: profile_capability_name.clone(),
1062 source_dictionary: Default::default(),
1063 target: ExposeTarget::Parent,
1064 target_name: profile_capability_name,
1065 availability: cm_rust::Availability::Required,
1066 };
1067 let expose_decl = ExposeDecl::Protocol(expose_proto_decl.clone());
1068 assert!(pico_member_decl.exposes.contains(&expose_decl));
1069
1070 {
1073 expose_proto_decl.source = ExposeSource::Child(member_spec.name.parse().unwrap());
1074 let root_expose_decl = ExposeDecl::Protocol(expose_proto_decl);
1075 let root = builder.get_realm_decl().await.expect("failed to get root");
1076 assert!(root.exposes.contains(&root_expose_decl));
1077 }
1078 }
1079
1080 async fn validate_mock_piconet_member<'a>(
1081 builder: &RealmBuilder,
1082 member_spec: &'a PiconetMemberSpec,
1083 ) {
1084 assert_realm_contains(builder, &member_spec.name).await;
1086
1087 if member_spec.rfcomm_url.is_some() {
1089 validate_profile_routes_for_member_with_rfcomm(builder, member_spec).await;
1090 } else {
1091 validate_profile_routes_for_member(builder, member_spec).await;
1092 }
1093
1094 let pico_member_decl = builder
1096 .get_component_decl(member_spec.name.clone())
1097 .await
1098 .expect("piconet member had no decl");
1099 let use_decl = UseDecl::Protocol(UseProtocolDecl {
1100 source: UseSource::Parent,
1101 source_name: bredr_test::ProfileTestMarker::PROTOCOL_NAME.parse().unwrap(),
1102 source_dictionary: Default::default(),
1103 target_path: Some(
1104 format!("/svc/{}", bredr_test::ProfileTestMarker::PROTOCOL_NAME).parse().unwrap(),
1105 ),
1106 numbered_handle: None,
1107 dependency_type: DependencyType::Strong,
1108 availability: Availability::Required,
1109 });
1110 assert!(pico_member_decl.uses.contains(&use_decl));
1111
1112 let profile_test_name = Name::new(bredr_test::ProfileTestMarker::PROTOCOL_NAME).unwrap();
1115 let root = builder.get_realm_decl().await.expect("failed to get root");
1116 let offer_profile_test = OfferDecl::Protocol(OfferProtocolDecl {
1117 source: offer_source_static_child(&super::mock_piconet_server_moniker()),
1118 source_name: profile_test_name.clone(),
1119 source_dictionary: Default::default(),
1120 target: offer_target_static_child(&member_spec.name),
1121 target_name: profile_test_name,
1122 dependency_type: DependencyType::Strong,
1123 availability: Availability::Required,
1124 });
1125 assert!(root.offers.contains(&offer_profile_test));
1126
1127 }
1132
1133 #[fasync::run_singlethreaded(test)]
1134 async fn test_add_profile() {
1135 let mut test_harness = PiconetHarness::new().await;
1136 let profile_name = "test-profile-member";
1137 let interposer_name = super::interposer_name_for_profile(profile_name);
1138
1139 let _profile_member = test_harness
1141 .add_profile(
1142 profile_name.to_string(),
1143 "fuchsia-pkg://fuchsia.com/example#meta/example.cm".to_string(),
1144 )
1145 .await
1146 .expect("failed to add profile");
1147
1148 test_harness.update_routes().await.expect("should update routes");
1149 assert_realm_contains(&test_harness.builder, &profile_name).await;
1150 assert_realm_contains(&test_harness.builder, &interposer_name).await;
1151
1152 let profile_capability_name = Name::new(bredr::ProfileMarker::PROTOCOL_NAME).unwrap();
1156 let profile_expose = ExposeDecl::Protocol(ExposeProtocolDecl {
1157 source: ExposeSource::Self_,
1158 source_name: profile_capability_name.clone(),
1159 source_dictionary: Default::default(),
1160 target: ExposeTarget::Parent,
1161 target_name: profile_capability_name.clone(),
1162 availability: cm_rust::Availability::Required,
1163 });
1164 let interposer = test_harness
1165 .builder
1166 .get_component_decl(interposer_name.clone())
1167 .await
1168 .expect("interposer not found!");
1169 assert!(interposer.exposes.contains(&profile_expose));
1170
1171 let profile_test_name = Name::new(bredr_test::ProfileTestMarker::PROTOCOL_NAME).unwrap();
1173 let profile_test_use = UseDecl::Protocol(UseProtocolDecl {
1174 source: UseSource::Parent,
1175 source_name: profile_test_name.clone(),
1176 source_dictionary: Default::default(),
1177 target_path: Some(
1178 format!("/svc/{}", bredr_test::ProfileTestMarker::PROTOCOL_NAME).parse().unwrap(),
1179 ),
1180 numbered_handle: None,
1181 dependency_type: DependencyType::Strong,
1182 availability: Availability::Required,
1183 });
1184 assert!(interposer.uses.contains(&profile_test_use));
1185
1186 let profile_offer = OfferDecl::Protocol(OfferProtocolDecl {
1188 source: offer_source_static_child(&interposer_name),
1189 source_name: profile_capability_name.clone(),
1190 source_dictionary: Default::default(),
1191 target: offer_target_static_child(&profile_name),
1192 target_name: profile_capability_name.clone(),
1193 dependency_type: DependencyType::Strong,
1194 availability: Availability::Required,
1195 });
1196 let root = test_harness.builder.get_realm_decl().await.expect("unable to get root decl");
1197 assert!(root.offers.contains(&profile_offer));
1198
1199 let profile_test_offer = OfferDecl::Protocol(OfferProtocolDecl {
1201 source: offer_source_static_child(&super::mock_piconet_server_moniker()),
1202 source_name: profile_test_name.clone(),
1203 source_dictionary: Default::default(),
1204 target: offer_target_static_child(&interposer_name),
1205 target_name: profile_test_name.clone(),
1206 dependency_type: DependencyType::Strong,
1207 availability: Availability::Required,
1208 });
1209 assert!(root.offers.contains(&profile_test_offer));
1210
1211 let log_capability_name = Name::new(LogSinkMarker::PROTOCOL_NAME).unwrap();
1213 let log_offer = OfferDecl::Protocol(OfferProtocolDecl {
1214 source: OfferSource::Parent,
1215 source_name: log_capability_name.clone(),
1216 source_dictionary: Default::default(),
1217 target: offer_target_static_child(&profile_name),
1218 target_name: log_capability_name.clone(),
1219 dependency_type: DependencyType::Strong,
1220 availability: Availability::Required,
1221 });
1222 assert!(root.offers.contains(&log_offer));
1223 }
1224
1225 #[fasync::run_singlethreaded(test)]
1226 async fn test_add_profile_with_rfcomm() {
1227 let mut test_harness = PiconetHarness::new().await;
1228
1229 let profile_name = "test-profile-member";
1230 let interposer_name = super::interposer_name_for_profile(profile_name);
1231 let bt_rfcomm_name = super::bt_rfcomm_moniker_for_member(profile_name);
1232 let profile_url = "fuchsia-pkg://fuchsia.com/example#meta/example.cm".to_string();
1233 let rfcomm_url = "fuchsia-pkg://fuchsia.com/example#meta/bt-rfcomm.cm".to_string();
1234
1235 let _profile_member = test_harness
1237 .add_profile_with_capabilities(
1238 profile_name.to_string(),
1239 profile_url,
1240 Some(rfcomm_url),
1241 vec![],
1242 vec![],
1243 )
1244 .await
1245 .expect("failed to add profile");
1246
1247 test_harness.update_routes().await.expect("should update routes");
1248 assert_realm_contains(&test_harness.builder, &profile_name).await;
1249 assert_realm_contains(&test_harness.builder, &interposer_name).await;
1250 assert_realm_contains(&test_harness.builder, &bt_rfcomm_name).await;
1251
1252 let profile_capability_name = Name::new(bredr::ProfileMarker::PROTOCOL_NAME).unwrap();
1254
1255 let profile_offer1 = OfferDecl::Protocol(OfferProtocolDecl {
1257 source: offer_source_static_child(&interposer_name),
1258 source_name: profile_capability_name.clone(),
1259 source_dictionary: Default::default(),
1260 target: offer_target_static_child(&bt_rfcomm_name),
1261 target_name: profile_capability_name.clone(),
1262 dependency_type: DependencyType::Strong,
1263 availability: Availability::Required,
1264 });
1265 let profile_offer2 = OfferDecl::Protocol(OfferProtocolDecl {
1267 source: offer_source_static_child(&bt_rfcomm_name),
1268 source_name: profile_capability_name.clone(),
1269 source_dictionary: Default::default(),
1270 target: offer_target_static_child(&profile_name),
1271 target_name: profile_capability_name.clone(),
1272 dependency_type: DependencyType::Strong,
1273 availability: Availability::Required,
1274 });
1275 let root = test_harness.builder.get_realm_decl().await.expect("unable to get root decl");
1276 assert!(root.offers.contains(&profile_offer1));
1277 assert!(root.offers.contains(&profile_offer2));
1278 }
1279
1280 #[fasync::run_singlethreaded(test)]
1281 async fn test_add_profile_with_additional_capabilities() {
1282 let mut test_harness = PiconetHarness::new().await;
1283 let profile_name = "test-profile-member";
1284
1285 let fake_cap1 = "Foo".to_string();
1287 let fake_cap2 = "Bar".to_string();
1288 let expose_capabilities = vec![
1289 Capability::protocol_by_name(fake_cap1.clone()).into(),
1290 Capability::protocol_by_name(fake_cap2.clone()).into(),
1291 ];
1292 let fake_cap3 = "Cat".to_string();
1293 let use_capabilities = vec![Capability::protocol_by_name(fake_cap3.clone()).into()];
1294 let profile_member = test_harness
1295 .add_profile_with_capabilities(
1296 profile_name.to_string(),
1297 "fuchsia-pkg://fuchsia.com/example#meta/example.cm".to_string(),
1298 None,
1299 use_capabilities,
1300 expose_capabilities,
1301 )
1302 .await
1303 .expect("failed to add profile");
1304
1305 test_harness.update_routes().await.expect("should update routes");
1306 assert_realm_contains(&test_harness.builder, &profile_name).await;
1307
1308 let fake_capability_expose1 = ExposeDecl::Protocol(ExposeProtocolDecl {
1313 source: ExposeSource::Child(profile_name.parse().unwrap()),
1314 source_name: fake_cap1.clone().parse().unwrap(),
1315 source_dictionary: Default::default(),
1316 target: ExposeTarget::Parent,
1317 target_name: capability_path_for_peer_id(profile_member.peer_id(), &fake_cap1)
1318 .parse()
1319 .unwrap(),
1320 availability: cm_rust::Availability::Required,
1321 });
1322 let fake_capability_expose2 = ExposeDecl::Protocol(ExposeProtocolDecl {
1324 source: ExposeSource::Child(profile_name.parse().unwrap()),
1325 source_name: fake_cap2.clone().parse().unwrap(),
1326 source_dictionary: Default::default(),
1327 target: ExposeTarget::Parent,
1328 target_name: capability_path_for_peer_id(profile_member.peer_id(), &fake_cap2)
1329 .parse()
1330 .unwrap(),
1331 availability: cm_rust::Availability::Required,
1332 });
1333 let fake_capability_offer3 = OfferDecl::Protocol(OfferProtocolDecl {
1335 source: OfferSource::Parent,
1336 source_name: fake_cap3.clone().parse().unwrap(),
1337 source_dictionary: Default::default(),
1338 target: offer_target_static_child(&profile_name),
1339 target_name: fake_cap3.parse().unwrap(),
1340 dependency_type: DependencyType::Strong,
1341 availability: Availability::Required,
1342 });
1343
1344 let root = test_harness.builder.get_realm_decl().await.expect("unable to get root decl");
1345 assert!(root.exposes.contains(&fake_capability_expose1));
1346 assert!(root.exposes.contains(&fake_capability_expose2));
1347 assert!(root.offers.contains(&fake_capability_offer3));
1348 }
1349
1350 #[fasync::run_singlethreaded(test)]
1351 async fn test_multiple_profiles_with_same_expose_is_ok() {
1352 let mut test_harness = PiconetHarness::new().await;
1353 let profile_name1 = "test-profile-1";
1354 let profile_name2 = "test-profile-2";
1355
1356 let fake_cap = "FooBarAmazing".to_string();
1358 let expose_capabilities = vec![Capability::protocol_by_name(fake_cap.clone()).into()];
1359
1360 let profile_member1 = test_harness
1361 .add_profile_with_capabilities(
1362 profile_name1.to_string(),
1363 "fuchsia-pkg://fuchsia.com/example#meta/example-profile1.cm".to_string(),
1364 None,
1365 vec![],
1366 expose_capabilities.clone(),
1367 )
1368 .await
1369 .expect("failed to add profile1");
1370 let profile_member2 = test_harness
1371 .add_profile_with_capabilities(
1372 profile_name2.to_string(),
1373 "fuchsia-pkg://fuchsia.com/example#meta/example-profile2.cm".to_string(),
1374 None,
1375 vec![],
1376 expose_capabilities,
1377 )
1378 .await
1379 .expect("failed to add profile2");
1380
1381 test_harness.update_routes().await.expect("should update routes");
1382 assert_realm_contains(&test_harness.builder, &profile_name1).await;
1383 assert_realm_contains(&test_harness.builder, &profile_name2).await;
1384
1385 let profile1_expose = ExposeDecl::Protocol(ExposeProtocolDecl {
1387 source: ExposeSource::Child(profile_name1.parse().unwrap()),
1388 source_name: fake_cap.clone().parse().unwrap(),
1389 source_dictionary: Default::default(),
1390 target: ExposeTarget::Parent,
1391 target_name: capability_path_for_peer_id(profile_member1.peer_id(), &fake_cap)
1392 .parse()
1393 .unwrap(),
1394 availability: cm_rust::Availability::Required,
1395 });
1396 let profile2_expose = ExposeDecl::Protocol(ExposeProtocolDecl {
1397 source: ExposeSource::Child(profile_name2.parse().unwrap()),
1398 source_name: fake_cap.clone().parse().unwrap(),
1399 source_dictionary: Default::default(),
1400 target: ExposeTarget::Parent,
1401 target_name: capability_path_for_peer_id(profile_member2.peer_id(), &fake_cap)
1402 .parse()
1403 .unwrap(),
1404 availability: cm_rust::Availability::Required,
1405 });
1406
1407 let root = test_harness.builder.get_realm_decl().await.expect("unable to get root decl");
1408 assert!(root.exposes.contains(&profile1_expose));
1409 assert!(root.exposes.contains(&profile2_expose));
1410 }
1411}