1use crate::cobalt;
6use fidl::endpoints::{ServerEnd, create_proxy};
7use fidl_fuchsia_component as fcomponent;
8use fidl_fuchsia_component_decl as fdecl;
9use fidl_fuchsia_io as fio;
10use fidl_fuchsia_session as fsession;
11use fuchsia_async as fasync;
12use fuchsia_component::runtime::{Data, DataValue, Dictionary};
13use log::info;
14use thiserror::Error;
15
16#[derive(Debug, Error, Clone, PartialEq)]
18pub enum StartupError {
19 #[error("Existing session not destroyed at \"{}/{}\": {:?}", collection, name, err)]
20 NotDestroyed { name: String, collection: String, err: fcomponent::Error },
21
22 #[error("Session {} not created at \"{}/{}\": Bedrock error {:?}", url, collection, name, err)]
23 BedrockError { name: String, collection: String, url: String, err: String },
24
25 #[error("Session {} not created at \"{}/{}\": {:?}", url, collection, name, err)]
26 NotCreated { name: String, collection: String, url: String, err: fcomponent::Error },
27
28 #[error(
29 "Exposed directory of session {} at \"{}/{}\" not opened: {:?}",
30 url,
31 collection,
32 name,
33 err
34 )]
35 ExposedDirNotOpened { name: String, collection: String, url: String, err: fcomponent::Error },
36
37 #[error("Session {} not launched at \"{}/{}\": {:?}", url, collection, name, err)]
38 NotLaunched { name: String, collection: String, url: String, err: fcomponent::Error },
39
40 #[error("Attempt to restart a not running session")]
41 NotRunning,
42}
43
44impl From<StartupError> for fsession::LaunchError {
45 fn from(e: StartupError) -> fsession::LaunchError {
46 match e {
47 StartupError::NotDestroyed { .. } => fsession::LaunchError::DestroyComponentFailed,
48 StartupError::NotCreated { err, .. } => match err {
49 fcomponent::Error::InstanceCannotResolve => fsession::LaunchError::NotFound,
50 _ => fsession::LaunchError::CreateComponentFailed,
51 },
52 StartupError::ExposedDirNotOpened { .. }
53 | StartupError::BedrockError { .. }
54 | StartupError::NotLaunched { .. } => fsession::LaunchError::CreateComponentFailed,
55 StartupError::NotRunning => fsession::LaunchError::NotFound,
56 }
57 }
58}
59
60impl From<StartupError> for fsession::RestartError {
61 fn from(e: StartupError) -> fsession::RestartError {
62 match e {
63 StartupError::NotDestroyed { .. } => fsession::RestartError::DestroyComponentFailed,
64 StartupError::NotCreated { err, .. } => match err {
65 fcomponent::Error::InstanceCannotResolve => fsession::RestartError::NotFound,
66 _ => fsession::RestartError::CreateComponentFailed,
67 },
68 StartupError::ExposedDirNotOpened { .. }
69 | StartupError::BedrockError { .. }
70 | StartupError::NotLaunched { .. } => fsession::RestartError::CreateComponentFailed,
71 StartupError::NotRunning => fsession::RestartError::NotRunning,
72 }
73 }
74}
75
76impl From<StartupError> for fsession::LifecycleError {
77 fn from(e: StartupError) -> fsession::LifecycleError {
78 match e {
79 StartupError::NotDestroyed { .. } => fsession::LifecycleError::DestroyComponentFailed,
80 StartupError::NotCreated { err, .. } => match err {
81 fcomponent::Error::InstanceCannotResolve => {
82 fsession::LifecycleError::ResolveComponentFailed
83 }
84 _ => fsession::LifecycleError::CreateComponentFailed,
85 },
86 StartupError::ExposedDirNotOpened { .. }
87 | StartupError::BedrockError { .. }
88 | StartupError::NotLaunched { .. } => fsession::LifecycleError::CreateComponentFailed,
89 StartupError::NotRunning => fsession::LifecycleError::NotFound,
90 }
91 }
92}
93
94const SESSION_NAME: &str = "session";
96
97const SESSION_CHILD_COLLECTION: &str = "session";
100
101pub async fn launch_session(
116 session_url: &str,
117 config_capabilities: Vec<fdecl::Configuration>,
118 exposed_dir: ServerEnd<fio::DirectoryMarker>,
119 realm: &fcomponent::RealmProxy,
120) -> Result<fcomponent::ExecutionControllerProxy, StartupError> {
121 info!(session_url; "Launching session");
122
123 let start_time = zx::MonotonicInstant::get();
124 let controller = set_session(session_url, config_capabilities, realm, exposed_dir).await?;
125 let end_time = zx::MonotonicInstant::get();
126
127 fasync::Task::local(async move {
128 if let Ok(cobalt_logger) = cobalt::get_logger() {
129 let _ = cobalt::log_session_launch_time(cobalt_logger, start_time, end_time).await;
132 }
133 })
134 .detach();
135
136 Ok(controller)
137}
138
139pub async fn stop_session(realm: &fcomponent::RealmProxy) -> Result<(), StartupError> {
147 realm_management::destroy_child_component(SESSION_NAME, SESSION_CHILD_COLLECTION, realm)
148 .await
149 .map_err(|err| StartupError::NotDestroyed {
150 name: SESSION_NAME.to_string(),
151 collection: SESSION_CHILD_COLLECTION.to_string(),
152 err,
153 })
154}
155
156async fn create_config_dict(
157 config_capabilities: Vec<fdecl::Configuration>,
158) -> Result<Option<Dictionary>, anyhow::Error> {
159 if config_capabilities.is_empty() {
160 return Ok(None);
161 }
162 let dictionary = Dictionary::new().await;
163 for config in config_capabilities {
164 let Some(value) = config.value else { continue };
165 let Some(key) = config.name else { continue };
166 let data = Data::new(DataValue::Bytes(fidl::persist(&value)?)).await;
167 dictionary.insert(&key, data).await;
168 }
169 Ok(Some(dictionary))
170}
171
172async fn set_session(
186 session_url: &str,
187 config_capabilities: Vec<fdecl::Configuration>,
188 realm: &fcomponent::RealmProxy,
189 exposed_dir: ServerEnd<fio::DirectoryMarker>,
190) -> Result<fcomponent::ExecutionControllerProxy, StartupError> {
191 realm_management::destroy_child_component(SESSION_NAME, SESSION_CHILD_COLLECTION, realm)
192 .await
193 .or_else(|err: fcomponent::Error| match err {
194 fcomponent::Error::InvalidArguments
197 | fcomponent::Error::InstanceNotFound
198 | fcomponent::Error::CollectionNotFound => Ok(()),
199 _ => Err(err),
200 })
201 .map_err(|err| StartupError::NotDestroyed {
202 name: SESSION_NAME.to_string(),
203 collection: SESSION_CHILD_COLLECTION.to_string(),
204 err,
205 })?;
206
207 let (controller, controller_server_end) = create_proxy::<fcomponent::ControllerMarker>();
208 let dictionary = create_config_dict(config_capabilities).await.map_err(|err| {
209 StartupError::BedrockError {
210 name: SESSION_NAME.to_string(),
211 collection: SESSION_CHILD_COLLECTION.to_string(),
212 url: session_url.to_string(),
213 err: format!("{err:#?}"),
214 }
215 })?;
216 let create_child_args = fcomponent::CreateChildArgs {
217 controller: Some(controller_server_end),
218 additional_inputs: dictionary.map(|dictionary| dictionary.handle),
219 ..Default::default()
220 };
221 realm_management::create_child_component(
222 SESSION_NAME,
223 session_url,
224 SESSION_CHILD_COLLECTION,
225 create_child_args,
226 realm,
227 )
228 .await
229 .map_err(|err| StartupError::NotCreated {
230 name: SESSION_NAME.to_string(),
231 collection: SESSION_CHILD_COLLECTION.to_string(),
232 url: session_url.to_string(),
233 err,
234 })?;
235
236 realm_management::open_child_component_exposed_dir(
237 SESSION_NAME,
238 SESSION_CHILD_COLLECTION,
239 realm,
240 exposed_dir,
241 )
242 .await
243 .map_err(|err| StartupError::ExposedDirNotOpened {
244 name: SESSION_NAME.to_string(),
245 collection: SESSION_CHILD_COLLECTION.to_string(),
246 url: session_url.to_string(),
247 err,
248 })?;
249
250 let (execution_controller, execution_controller_server_end) =
252 create_proxy::<fcomponent::ExecutionControllerMarker>();
253 controller
254 .start(fcomponent::StartChildArgs::default(), execution_controller_server_end)
255 .await
256 .map_err(|_| fcomponent::Error::Internal)
257 .and_then(std::convert::identity)
258 .map_err(|_err| StartupError::NotLaunched {
259 name: SESSION_NAME.to_string(),
260 collection: SESSION_CHILD_COLLECTION.to_string(),
261 url: session_url.to_string(),
262 err: fcomponent::Error::InstanceCannotStart,
263 })?;
264
265 Ok(execution_controller)
266}
267
268#[cfg(test)]
269#[allow(clippy::unwrap_used)]
270mod tests {
271 use super::{SESSION_CHILD_COLLECTION, SESSION_NAME, set_session, stop_session};
272 use anyhow::Error;
273 use fidl::endpoints::create_endpoints;
274 use fidl_fuchsia_component as fcomponent;
275 use fidl_fuchsia_io as fio;
276 use fidl_test_util::spawn_stream_handler;
277 use session_testing::{spawn_directory_server, spawn_server};
278 use std::sync::LazyLock;
279 use test_util::Counter;
280
281 #[fuchsia::test]
282 async fn set_session_calls_realm_methods_in_appropriate_order() -> Result<(), Error> {
283 static NUM_REALM_REQUESTS: LazyLock<Counter> = LazyLock::new(|| Counter::new(0));
285
286 let session_url = "session";
287
288 let directory_request_handler = move |directory_request| match directory_request {
289 fio::DirectoryRequest::Open { .. } => {
290 assert_eq!(NUM_REALM_REQUESTS.get(), 4);
291 }
292 _ => panic!("Directory handler received an unexpected request"),
293 };
294
295 let realm = spawn_stream_handler(move |realm_request| async move {
296 match realm_request {
297 fcomponent::RealmRequest::DestroyChild { child, responder } => {
298 assert_eq!(NUM_REALM_REQUESTS.get(), 0);
299 assert_eq!(child.collection, Some(SESSION_CHILD_COLLECTION.to_string()));
300 assert_eq!(child.name, SESSION_NAME);
301
302 let _ = responder.send(Ok(()));
303 }
304 fcomponent::RealmRequest::CreateChild { collection, decl, args, responder } => {
305 assert_eq!(NUM_REALM_REQUESTS.get(), 1);
306 assert_eq!(decl.url.unwrap(), session_url);
307 assert_eq!(decl.name.unwrap(), SESSION_NAME);
308 assert_eq!(&collection.name, SESSION_CHILD_COLLECTION);
309
310 spawn_server(args.controller.unwrap(), move |controller_request| {
311 match controller_request {
312 fcomponent::ControllerRequest::Start { responder, .. } => {
313 let _ = responder.send(Ok(()));
314 }
315 fcomponent::ControllerRequest::IsStarted { .. } => unimplemented!(),
316 fcomponent::ControllerRequest::GetExposedDictionary { .. } => {
317 unimplemented!()
318 }
319 fcomponent::ControllerRequest::GetOutputDictionary { .. } => {
320 unimplemented!()
321 }
322 fcomponent::ControllerRequest::OpenExposedDir { .. } => {
323 unimplemented!()
324 }
325 fcomponent::ControllerRequest::Destroy { .. } => {
326 unimplemented!()
327 }
328 fcomponent::ControllerRequest::_UnknownMethod { .. } => {
329 unimplemented!()
330 }
331 }
332 });
333
334 let _ = responder.send(Ok(()));
335 }
336 fcomponent::RealmRequest::OpenExposedDir { child, exposed_dir, responder } => {
337 assert_eq!(NUM_REALM_REQUESTS.get(), 2);
338 assert_eq!(child.collection, Some(SESSION_CHILD_COLLECTION.to_string()));
339 assert_eq!(child.name, SESSION_NAME);
340
341 spawn_directory_server(exposed_dir, directory_request_handler);
342 let _ = responder.send(Ok(()));
343 }
344 _ => panic!("Realm handler received an unexpected request"),
345 }
346 NUM_REALM_REQUESTS.inc();
347 });
348
349 let (_exposed_dir, exposed_dir_server_end) = create_endpoints::<fio::DirectoryMarker>();
350 let _controller = set_session(session_url, vec![], &realm, exposed_dir_server_end).await?;
351
352 Ok(())
353 }
354
355 #[fuchsia::test]
356 async fn set_session_starts_component() -> Result<(), Error> {
357 static NUM_START_CALLS: LazyLock<Counter> = LazyLock::new(|| Counter::new(0));
358
359 let session_url = "session";
360
361 let realm = spawn_stream_handler(move |realm_request| async move {
362 match realm_request {
363 fcomponent::RealmRequest::DestroyChild { responder, .. } => {
364 let _ = responder.send(Ok(()));
365 }
366 fcomponent::RealmRequest::CreateChild { args, responder, .. } => {
367 spawn_server(args.controller.unwrap(), move |controller_request| {
368 match controller_request {
369 fcomponent::ControllerRequest::Start { responder, .. } => {
370 NUM_START_CALLS.inc();
371 let _ = responder.send(Ok(()));
372 }
373 fcomponent::ControllerRequest::IsStarted { .. } => unimplemented!(),
374 fcomponent::ControllerRequest::GetExposedDictionary { .. } => {
375 unimplemented!()
376 }
377 fcomponent::ControllerRequest::GetOutputDictionary { .. } => {
378 unimplemented!()
379 }
380 fcomponent::ControllerRequest::OpenExposedDir { .. } => {
381 unimplemented!()
382 }
383 fcomponent::ControllerRequest::Destroy { .. } => {
384 unimplemented!()
385 }
386 fcomponent::ControllerRequest::_UnknownMethod { .. } => {
387 unimplemented!()
388 }
389 }
390 });
391 let _ = responder.send(Ok(()));
392 }
393 fcomponent::RealmRequest::OpenExposedDir { responder, .. } => {
394 let _ = responder.send(Ok(()));
395 }
396 _ => panic!("Realm handler received an unexpected request"),
397 }
398 });
399
400 let (_exposed_dir, exposed_dir_server_end) = create_endpoints::<fio::DirectoryMarker>();
401 let _controller = set_session(session_url, vec![], &realm, exposed_dir_server_end).await?;
402 assert_eq!(NUM_START_CALLS.get(), 1);
403
404 Ok(())
405 }
406
407 #[fuchsia::test]
408 async fn stop_session_calls_destroy_child() -> Result<(), Error> {
409 static NUM_DESTROY_CHILD_CALLS: LazyLock<Counter> = LazyLock::new(|| Counter::new(0));
410
411 let realm = spawn_stream_handler(move |realm_request| async move {
412 match realm_request {
413 fcomponent::RealmRequest::DestroyChild { child, responder } => {
414 assert_eq!(NUM_DESTROY_CHILD_CALLS.get(), 0);
415 assert_eq!(child.collection, Some(SESSION_CHILD_COLLECTION.to_string()));
416 assert_eq!(child.name, SESSION_NAME);
417
418 let _ = responder.send(Ok(()));
419 }
420 _ => panic!("Realm handler received an unexpected request"),
421 }
422 NUM_DESTROY_CHILD_CALLS.inc();
423 });
424
425 stop_session(&realm).await?;
426 assert_eq!(NUM_DESTROY_CHILD_CALLS.get(), 1);
427
428 Ok(())
429 }
430}