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