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