Skip to main content

starnix_modules_functionfs/
lib.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#![recursion_limit = "256"]
6
7use fidl::endpoints::Proxy as _;
8use fidl_fuchsia_hardware_adb as fadb;
9use fuchsia_async as fasync;
10use fuchsia_component::client::Service;
11use fuchsia_inspect as inspect;
12use futures::{FutureExt, StreamExt, pin_mut};
13use futures_util as _;
14use starnix_core::power::{create_proxy_for_wake_events_counter_zero, mark_proxy_message_handled};
15use starnix_core::task::{CurrentTask, EventHandler, Kernel, WaitCanceler, WaitQueue, Waiter};
16use starnix_core::vfs::pseudo::vec_directory::{VecDirectory, VecDirectoryEntry};
17use starnix_core::vfs::{
18    CacheMode, DirEntry, DirectoryEntryType, FileObject, FileObjectState, FileOps, FileSystem,
19    FileSystemHandle, FileSystemOps, FileSystemOptions, FsNode, FsNodeInfo, FsNodeOps, FsStr,
20    InputBuffer, OutputBuffer, fileops_impl_noop_sync, fileops_impl_seekless, fs_args,
21    fs_node_impl_dir_readonly, fs_node_impl_not_dir,
22};
23use starnix_logging::{log_info, log_warn, track_stub};
24use starnix_sync::{FunctionFsResultLock, FunctionFsStateLock, InterruptibleEvent, LockDepMutex};
25use starnix_types::vfs::default_statfs;
26use starnix_uapi::auth::FsCred;
27use starnix_uapi::errors::Errno;
28use starnix_uapi::file_mode::mode;
29use starnix_uapi::open_flags::OpenFlags;
30use starnix_uapi::vfs::FdEvents;
31use starnix_uapi::{
32    errno, error, gid_t, ino_t, statfs, uid_t, usb_functionfs_event,
33    usb_functionfs_event_type_FUNCTIONFS_BIND, usb_functionfs_event_type_FUNCTIONFS_DISABLE,
34    usb_functionfs_event_type_FUNCTIONFS_ENABLE, usb_functionfs_event_type_FUNCTIONFS_UNBIND,
35};
36use std::borrow::Cow;
37use std::collections::VecDeque;
38use std::ops::Deref;
39use std::sync::Arc;
40use std::sync::atomic::{AtomicU64, Ordering};
41use zerocopy::IntoBytes;
42
43// The node identifiers of different nodes in FunctionFS.
44const ROOT_NODE_ID: ino_t = 1;
45
46// Control endpoint is always present in a mounted FunctionFS.
47const CONTROL_ENDPOINT: &str = "ep0";
48const CONTROL_ENDPOINT_NODE_ID: ino_t = 2;
49
50const OUTPUT_ENDPOINT: &str = "ep1";
51const OUTPUT_ENDPOINT_NODE_ID: ino_t = 3;
52
53const INPUT_ENDPOINT: &str = "ep2";
54const INPUT_ENDPOINT_NODE_ID: ino_t = 4;
55
56// Magic number of the file system, different from the magic used for Descriptors and Strings.
57// Set to the same value as Linux.
58const FUNCTIONFS_MAGIC: u32 = 0xa647361;
59
60const ADB_DIRECTORY: &str = "/svc/fuchsia.hardware.adb.Service";
61
62// How long to keep Starnix awake after an ADB interaction. If no ADB reads or
63// writes occur within this time period, Starnix will be allowed to suspend.
64const ADB_INTERACTION_TIMEOUT: zx::Duration<zx::MonotonicTimeline> = zx::Duration::from_seconds(2);
65
66#[derive(Default)]
67struct PendingResult<T: Default> {
68    event: Arc<InterruptibleEvent>,
69    result: LockDepMutex<Option<Result<T, Errno>>, FunctionFsResultLock>,
70}
71
72impl<T: Default> PendingResult<T> {
73    fn set_result(&self, res: Result<T, Errno>) {
74        let mut result = self.result.lock();
75        debug_assert!(result.is_none(), "PendingResult set more than once");
76
77        result.replace(res);
78        self.event.notify();
79    }
80}
81
82struct ReadCommand {
83    pending: Arc<PendingResult<Vec<u8>>>,
84}
85
86struct WriteCommand {
87    data: Vec<u8>,
88    pending: Arc<PendingResult<usize>>,
89}
90
91const MAX_INSPECT_EVENTS: usize = 32;
92
93#[derive(Clone)]
94struct EventRecord {
95    id: usize,
96    timestamp: zx::BootInstant,
97    event: Cow<'static, str>,
98}
99
100#[derive(Default)]
101struct FunctionFsStats {
102    ep1_total_bytes_read: AtomicU64,
103    ep1_read_count: AtomicU64,
104    ep1_read_errors: AtomicU64,
105    ep1_buffer_overflow_errors: AtomicU64,
106
107    ep2_total_bytes_written: AtomicU64,
108    ep2_write_count: AtomicU64,
109    ep2_write_errors: AtomicU64,
110}
111
112fn create_lazy_inspect_node(
113    parent: &inspect::Node,
114    state: &Arc<LockDepMutex<FunctionFsState, FunctionFsStateLock>>,
115    stats: &Arc<FunctionFsStats>,
116) -> inspect::LazyNode {
117    let state_weak = Arc::downgrade(state);
118    let stats_weak = Arc::downgrade(stats);
119    parent.create_lazy_child("usb-functionfs", move || {
120        let state_weak = state_weak.clone();
121        let stats_weak = stats_weak.clone();
122        Box::pin(async move {
123            let inspector = inspect::Inspector::default();
124            let root = inspector.root();
125
126            if let Some(state_arc) = state_weak.upgrade() {
127                let (
128                    is_online,
129                    num_open_file_objects,
130                    event_queue_depth,
131                    has_input_output_endpoints,
132                    control_resets,
133                    events,
134                ) = {
135                    let state = state_arc.lock();
136                    (
137                        state.is_online,
138                        state.num_open_file_objects as u64,
139                        state.event_queue.len() as u64,
140                        state.has_input_output_endpoints,
141                        state.control_resets,
142                        state.event_history.iter().cloned().collect::<Vec<_>>(),
143                    )
144                };
145
146                root.record_bool("is_online", is_online);
147                root.record_uint("num_open_file_objects", num_open_file_objects);
148                root.record_uint("event_queue_depth", event_queue_depth);
149                root.record_bool("has_input_output_endpoints", has_input_output_endpoints);
150                root.record_uint("control_resets", control_resets);
151
152                root.record_child("event_history", |history_node| {
153                    for record in events {
154                        history_node.record_child(record.id.to_string(), |entry_node| {
155                            entry_node.record_int("@time", record.timestamp.into_nanos());
156                            entry_node.record_string("event", record.event.as_ref());
157                        });
158                    }
159                });
160            }
161
162            if let Some(stats) = stats_weak.upgrade() {
163                root.record_child("ep1_bulk_out", |ep1_node| {
164                    ep1_node.record_uint(
165                        "total_bytes_read",
166                        stats.ep1_total_bytes_read.load(Ordering::Relaxed),
167                    );
168                    ep1_node
169                        .record_uint("read_count", stats.ep1_read_count.load(Ordering::Relaxed));
170                    ep1_node
171                        .record_uint("read_errors", stats.ep1_read_errors.load(Ordering::Relaxed));
172                    ep1_node.record_uint(
173                        "buffer_overflow_errors",
174                        stats.ep1_buffer_overflow_errors.load(Ordering::Relaxed),
175                    );
176                });
177
178                root.record_child("ep2_bulk_in", |ep2_node| {
179                    ep2_node.record_uint(
180                        "total_bytes_written",
181                        stats.ep2_total_bytes_written.load(Ordering::Relaxed),
182                    );
183                    ep2_node
184                        .record_uint("write_count", stats.ep2_write_count.load(Ordering::Relaxed));
185                    ep2_node.record_uint(
186                        "write_errors",
187                        stats.ep2_write_errors.load(Ordering::Relaxed),
188                    );
189                });
190            }
191
192            Ok(inspector)
193        })
194    })
195}
196
197/// Handle all of the ADB messages in an async context.
198/// We receive commands from the main thread and then proxy them into the ADB channel.
199/// We want to hold the wakelock until we have at least one outstanding read, because we
200/// are always woken up on a new message. (If we have no outstanding reads we will not
201/// receive any new messages).
202///
203/// At the same time we still need to handle writes and events. These are handled by always
204/// clearing the proxy signal, but only clearing the kernel signal if we have an outstanding read.
205async fn handle_adb(
206    proxy: fadb::UsbAdbImpl_Proxy,
207    message_counter: Option<zx::Counter>,
208    read_commands: async_channel::Receiver<ReadCommand>,
209    write_commands: async_channel::Receiver<WriteCommand>,
210    state: Arc<LockDepMutex<FunctionFsState, FunctionFsStateLock>>,
211    stats: Arc<FunctionFsStats>,
212    session_id: u64,
213) {
214    /// Handle all of the events coming from the ADB device.
215    ///
216    /// adbd expects to receive events FUNCTIONFS_BIND, FUNCTIONFS_ENABLE, FUNCTION_DISABLE, and
217    /// FUNCTIONFS_UNBIND in that order. If it receives these events out of order or does not
218    /// receive some of the adb events, it may behave unexpectedly. In particular, please reference
219    /// the `StartMonitor` function in `UsbFfsConnection` of `adb/daemon/usb.cpp`.
220    ///
221    /// A FUNCTIONFS_BIND event is enqueued synchronously by `create_endpoints` as soon as
222    /// descriptors are written to ep0 and endpoints are established. When the driver is ready to
223    /// take input it will send an `OnStatusChanged{ ONLINE }` event, which is when this module
224    /// sends the FUNCTIONFS_ENABLE event to indicate that adbd should start processing data.
225    ///
226    /// When the driver sends an `OnStatusChanged{}` event, meaning that it's not online anymore.
227    /// The module will send a FUNCTIONFS_DISABLE event to stop processing data. When the stream
228    /// closes, we've unbound from the driver, and the module sends a FUNCTIONFS_UNBIND event.
229    async fn handle_events(
230        mut stream: fadb::UsbAdbImpl_EventStream,
231        message_counter: &Option<zx::Counter>,
232        state: Arc<LockDepMutex<FunctionFsState, FunctionFsStateLock>>,
233        session_id: u64,
234    ) {
235        while let Some(Ok(fadb::UsbAdbImpl_Event::OnStatusChanged { status })) = stream.next().await
236        {
237            let is_online = status == fadb::StatusFlags::ONLINE;
238            {
239                let mut state_locked = state.lock();
240                if state_locked.session_id != session_id {
241                    return;
242                }
243                state_locked.is_online = is_online;
244                state_locked.event_queue.push_back(usb_functionfs_event {
245                    type_: if is_online {
246                        usb_functionfs_event_type_FUNCTIONFS_ENABLE
247                    } else {
248                        usb_functionfs_event_type_FUNCTIONFS_DISABLE
249                    } as u8,
250                    ..Default::default()
251                });
252                state_locked.record_event(if is_online { "ENABLE" } else { "DISABLE" });
253                state_locked.waiters.notify_fd_events(FdEvents::POLLIN);
254                state_locked.waiters.notify_all();
255            }
256
257            // We can simply clear this after getting a response because we care about
258            // reads. Allow new FIDL messages to come through and only go to sleep if
259            // we have an outstanding read.
260            message_counter.as_ref().map(mark_proxy_message_handled);
261        }
262
263        let mut state_locked = state.lock();
264        if state_locked.session_id == session_id {
265            let _ = state_locked.device_proxy.take();
266            state_locked.is_online = false;
267            state_locked.has_input_output_endpoints = false;
268            state_locked.adb_read_channel = None;
269            state_locked.adb_write_channel = None;
270            state_locked.event_queue.push_back(usb_functionfs_event {
271                type_: usb_functionfs_event_type_FUNCTIONFS_UNBIND as u8,
272                ..Default::default()
273            });
274            state_locked.record_event("UNBIND");
275            state_locked.waiters.notify_fd_events(FdEvents::POLLIN);
276            state_locked.waiters.notify_all();
277        }
278    }
279
280    /// Consumes a stream of instants and decrements `message_counter` after
281    /// each one. As long as one of the instants written to this channel is
282    /// still in the future, we want to keep the container awake.
283    ///
284    /// NOTE: We're reusing `message_counter` in a way that's perhaps confusing:
285    /// both as the number of "in flight" requests, and to track whether the ADB
286    /// session seems to be idle or not. It may be clearer to have two separate
287    /// counters.
288    async fn handle_idle_timeouts(
289        timeouts: async_channel::Receiver<zx::MonotonicInstant>,
290        message_counter: &Option<zx::Counter>,
291    ) {
292        timeouts
293            .for_each(|timeout| async move {
294                use fasync::WakeupTime;
295                timeout.into_timer().await;
296                message_counter.as_ref().map(mark_proxy_message_handled);
297            })
298            .await
299    }
300
301    /// Handle the commands coming from the main thread.
302    async fn handle_read_commands(
303        proxy: &fadb::UsbAdbImpl_Proxy,
304        timeouts_sender: async_channel::Sender<zx::MonotonicInstant>,
305        commands: async_channel::Receiver<ReadCommand>,
306        stats: &FunctionFsStats,
307    ) {
308        let timeouts_sender = &timeouts_sender;
309        commands
310            .for_each(|ReadCommand { pending }| async move {
311                // Queue up our receive future. We want to do this before we decrement the counter,
312                // which potentially allows the container to suspend.
313                let receive_future = proxy.receive();
314
315                // Don't decrement the message counter immediately. Instead, we
316                // keep the container awake for some amount of time to allow
317                // Starnix to react to the message. Otherwise, the container
318                // might go directly to sleep without doing anything.
319                timeouts_sender
320                    .send(zx::MonotonicInstant::after(ADB_INTERACTION_TIMEOUT))
321                    .await
322                    .expect("Should be able to send timeout");
323
324                let response = match receive_future.await {
325                    Err(err) => {
326                        if err.is_closed() {
327                            log_info!("Receive failed due to connection shutdown: {err}");
328                            error!(ESHUTDOWN)
329                        } else {
330                            log_warn!("Failed to call UsbAdbImpl.Receive: {err}");
331                            stats.ep1_read_errors.fetch_add(1, Ordering::Relaxed);
332                            error!(EINVAL)
333                        }
334                    }
335                    Ok(Err(err)) => {
336                        let status = zx::Status::err_from_raw(err);
337                        if matches!(
338                            status,
339                            zx::Status::BAD_STATE | zx::Status::CANCELED | zx::Status::PEER_CLOSED
340                        ) {
341                            log_info!("Receive failed due to connection shutdown: {status}");
342                            error!(ESHUTDOWN)
343                        } else {
344                            log_warn!("Failed to receive data from adb driver: {status}");
345                            stats.ep1_read_errors.fetch_add(1, Ordering::Relaxed);
346                            error!(EINVAL)
347                        }
348                    }
349                    Ok(Ok(payload)) => {
350                        stats
351                            .ep1_total_bytes_read
352                            .fetch_add(payload.len() as u64, Ordering::Relaxed);
353                        stats.ep1_read_count.fetch_add(1, Ordering::Relaxed);
354                        Ok(payload)
355                    }
356                };
357
358                pending.set_result(response);
359            })
360            .await;
361    }
362
363    /// Handle the commands coming from the main thread.
364    async fn handle_write_commands(
365        proxy: &fadb::UsbAdbImpl_Proxy,
366        timeouts_sender: async_channel::Sender<zx::MonotonicInstant>,
367        commands: async_channel::Receiver<WriteCommand>,
368        stats: &FunctionFsStats,
369    ) {
370        let timeouts_sender = &timeouts_sender;
371        commands
372            .for_each(|WriteCommand { data, pending }| async move {
373                let response = match proxy.queue_tx(&data).await {
374                    Err(err) => {
375                        if err.is_closed() {
376                            log_info!("QueueTx failed due to connection shutdown: {err}");
377                            error!(ESHUTDOWN)
378                        } else {
379                            log_warn!("Failed to call UsbAdbImpl.QueueTx: {err}");
380                            stats.ep2_write_errors.fetch_add(1, Ordering::Relaxed);
381                            error!(EINVAL)
382                        }
383                    }
384                    Ok(Err(err)) => {
385                        let status = zx::Status::err_from_raw(err);
386                        if matches!(
387                            status,
388                            zx::Status::BAD_STATE | zx::Status::CANCELED | zx::Status::PEER_CLOSED
389                        ) {
390                            log_info!("QueueTx failed due to connection shutdown: {status}");
391                            error!(ESHUTDOWN)
392                        } else {
393                            log_warn!("Failed to queue data to adb driver: {status}");
394                            stats.ep2_write_errors.fetch_add(1, Ordering::Relaxed);
395                            error!(EINVAL)
396                        }
397                    }
398                    Ok(Ok(_)) => {
399                        stats
400                            .ep2_total_bytes_written
401                            .fetch_add(data.len() as u64, Ordering::Relaxed);
402                        stats.ep2_write_count.fetch_add(1, Ordering::Relaxed);
403                        Ok(data.len())
404                    }
405                };
406
407                // Don't decrement the message counter immediately. We use the
408                // ADB output as a signal that the ADB session is still
409                // interactive.
410                timeouts_sender
411                    .send(zx::MonotonicInstant::after(ADB_INTERACTION_TIMEOUT))
412                    .await
413                    .expect("Should be able to send timeout");
414
415                pending.set_result(response);
416            })
417            .await;
418    }
419
420    let (timeouts_sender, timeouts_receiver) = async_channel::unbounded();
421    let event_future =
422        handle_events(proxy.take_event_stream(), &message_counter, state, session_id);
423    let write_commands_future =
424        handle_write_commands(&proxy, timeouts_sender.clone(), write_commands, &stats);
425    let read_commands_future = handle_read_commands(&proxy, timeouts_sender, read_commands, &stats);
426    let timeout_future = handle_idle_timeouts(timeouts_receiver, &message_counter);
427    futures::join!(event_future, write_commands_future, read_commands_future, timeout_future);
428}
429
430pub struct FunctionFs;
431impl FunctionFs {
432    pub fn new_fs(
433        current_task: &CurrentTask,
434        options: FileSystemOptions,
435    ) -> Result<FileSystemHandle, Errno> {
436        if options.source != "adb" {
437            track_stub!(TODO("https://fxbug.dev/329699340"), "FunctionFS supports other uses");
438            return error!(ENODEV);
439        }
440
441        // ADB daemon assumes that ADB works over USB if FunctionFS is able to mount.
442        // Check that the ADB directory capability is provided to the kernel, and fail to mount
443        // if it is not.
444        if let Err(e) = std::fs::read_dir(ADB_DIRECTORY) {
445            log_warn!(
446                "Attempted to mount FunctionFS for adb, but could not read {ADB_DIRECTORY}: {e}"
447            );
448            return error!(ENODEV);
449        }
450
451        let uid = if let Some(uid) = options.params.get(b"uid") {
452            fs_args::parse::<uid_t>(uid.as_ref())?
453        } else {
454            0
455        };
456        let gid = if let Some(gid) = options.params.get(b"gid") {
457            fs_args::parse::<gid_t>(gid.as_ref())?
458        } else {
459            0
460        };
461
462        let fs = FileSystem::new(current_task.kernel(), CacheMode::Uncached, FunctionFs, options)?;
463
464        let creds = FsCred { uid, gid };
465        let info = FsNodeInfo::new(mode!(IFDIR, 0o755), creds);
466        fs.create_root_with_info(
467            ROOT_NODE_ID,
468            FunctionFsRootDir::new(&current_task.kernel().inspect_node),
469            info,
470        );
471        Ok(fs)
472    }
473}
474
475impl FileSystemOps for FunctionFs {
476    fn statfs(&self, _fs: &FileSystem, _current_task: &CurrentTask) -> Result<statfs, Errno> {
477        Ok(default_statfs(FUNCTIONFS_MAGIC))
478    }
479
480    fn name(&self) -> &'static FsStr {
481        b"functionfs".into()
482    }
483}
484
485#[derive(Default)]
486struct FunctionFsState {
487    // Keeps track of the number of open FileObjects across FunctionFS endpoints (ep0, ep1, ep2).
488    // When all FileObjects are closed, the filesystem resets to its initial state (`ffs->opened`).
489    // See https://docs.kernel.org/usb/functionfs.html.
490    num_open_file_objects: usize,
491
492    // Monotonically increasing session ID to prevent stale async event streams from
493    // injecting FUNCTIONFS_UNBIND/DISABLE into a newly opened control session.
494    session_id: u64,
495
496    // Whether the FunctionFS has input/output endpoints, which are /ep2 and /ep1
497    // respectively. /ep0 is the control endpoint and is always available.
498    has_input_output_endpoints: bool,
499
500    // Whether the FunctionFS is currently online (host connected).
501    is_online: bool,
502
503    // Number of times all control endpoints have closed and reset the filesystem.
504    control_resets: u64,
505
506    // Bounded ring buffer of lifecycle events exposed via lazy Inspect.
507    event_history: VecDeque<EventRecord>,
508    next_event_id: usize,
509
510    adb_read_channel: Option<async_channel::Sender<ReadCommand>>,
511    adb_write_channel: Option<async_channel::Sender<WriteCommand>>,
512
513    // FIDL binding to the adb driver, for start and stop calls.
514    device_proxy: Option<fadb::DeviceSynchronousProxy>,
515
516    // FunctionFs events that indicate the connection state, to be read through
517    // the control endpoint.
518    event_queue: VecDeque<usb_functionfs_event>,
519
520    waiters: WaitQueue,
521}
522
523impl FunctionFsState {
524    fn record_event(&mut self, event: impl Into<Cow<'static, str>>) {
525        if self.event_history.len() == MAX_INSPECT_EVENTS {
526            self.event_history.pop_front();
527        }
528        let id = self.next_event_id;
529        self.next_event_id += 1;
530        self.event_history.push_back(EventRecord {
531            id,
532            timestamp: zx::BootInstant::get(),
533            event: event.into(),
534        });
535    }
536}
537
538pub enum AdbProxyMode {
539    /// Don't proxy events at all.
540    None,
541
542    /// Have the Starnix runner proxy events such that the container
543    /// will wake up if events are received while the container is
544    /// suspended.
545    WakeContainer,
546}
547
548async fn connect_to_device(
549    proxy: AdbProxyMode,
550) -> Result<(fadb::DeviceSynchronousProxy, fadb::UsbAdbImpl_Proxy, Option<zx::Counter>), Errno> {
551    let service = Service::open(fadb::ServiceMarker).map_err(|_| errno!(EINVAL))?;
552    let instances = service.enumerate().await.map_err(|_| errno!(EINVAL))?;
553
554    for instance in instances {
555        let Ok(device_proxy) = instance.connect_to_adb() else {
556            continue;
557        };
558
559        let (adb_proxy, server_end) =
560            fidl::endpoints::create_sync_proxy::<fadb::UsbAdbImpl_Marker>();
561        let (adb_channel, message_counter) = match proxy {
562            AdbProxyMode::None => (adb_proxy.into_channel(), None),
563            AdbProxyMode::WakeContainer => {
564                let (channel, message_counter) = create_proxy_for_wake_events_counter_zero(
565                    adb_proxy.into_channel(),
566                    "adb".to_string(),
567                );
568                (channel, Some(message_counter))
569            }
570        };
571
572        let timer =
573            fasync::Timer::new(zx::MonotonicInstant::after(zx::Duration::from_seconds(5))).fuse();
574        let start_fut = device_proxy.start_adb(server_end).fuse();
575        pin_mut!(timer, start_fut);
576        let start_ok = futures::select! {
577            res = start_fut => matches!(res, Ok(Ok(()))),
578            _ = timer => false,
579        };
580
581        if start_ok {
582            let Ok(channel) = device_proxy.into_channel() else {
583                continue;
584            };
585            let device_proxy = fadb::DeviceSynchronousProxy::new(channel.into_zx_channel());
586            let adb_proxy =
587                fadb::UsbAdbImpl_Proxy::new(fidl::AsyncChannel::from_channel(adb_channel));
588            return Ok((device_proxy, adb_proxy, message_counter));
589        }
590    }
591
592    error!(EBUSY)
593}
594
595async fn manage_adb_connection(
596    read_commands: async_channel::Receiver<ReadCommand>,
597    write_commands: async_channel::Receiver<WriteCommand>,
598    state: Arc<LockDepMutex<FunctionFsState, FunctionFsStateLock>>,
599    stats: Arc<FunctionFsStats>,
600    session_id: u64,
601) {
602    let mut watcher = match Service::open(fadb::ServiceMarker) {
603        Ok(service) => service.watch().await.ok(),
604        Err(_) => None,
605    };
606
607    loop {
608        // Verify that the session is still active.
609        {
610            let state_locked = state.lock();
611            if state_locked.session_id != session_id
612                || !state_locked.has_input_output_endpoints
613                || state_locked.num_open_file_objects == 0
614            {
615                return;
616            }
617        }
618
619        // Try connecting to the ADB device asynchronously.
620        if let Ok((device_proxy, adb_proxy, message_counter)) =
621            connect_to_device(AdbProxyMode::WakeContainer).await
622        {
623            {
624                let mut state_locked = state.lock();
625                if state_locked.session_id != session_id || !state_locked.has_input_output_endpoints
626                {
627                    let deadline = zx::MonotonicInstant::after(zx::Duration::from_seconds(5));
628                    if let Err(e) = device_proxy.stop_adb(deadline) {
629                        log_warn!("Failed to stop adb driver after session cancelled: {e:?}");
630                    }
631                    return;
632                }
633                state_locked.device_proxy = Some(device_proxy);
634            }
635
636            handle_adb(
637                adb_proxy,
638                message_counter,
639                read_commands.clone(),
640                write_commands.clone(),
641                Arc::clone(&state),
642                Arc::clone(&stats),
643                session_id,
644            )
645            .await;
646
647            {
648                let mut state_locked = state.lock();
649                if state_locked.session_id == session_id {
650                    let _ = state_locked.device_proxy.take();
651                }
652            }
653        } else {
654            // Sleep 500ms unconditionally when retrying a failed connection.
655            fasync::Timer::new(zx::MonotonicInstant::after(zx::Duration::from_millis(500))).await;
656        }
657
658        // Check if session ended before waiting for the next connection.
659        {
660            let state_locked = state.lock();
661            if state_locked.session_id != session_id
662                || !state_locked.has_input_output_endpoints
663                || state_locked.num_open_file_objects == 0
664            {
665                return;
666            }
667        }
668
669        // If no ADB instances currently exist, park on the watcher until an instance appears.
670        let has_instances = match Service::open(fadb::ServiceMarker) {
671            Ok(service) => {
672                service.enumerate().await.map(|instances| !instances.is_empty()).unwrap_or(false)
673            }
674            Err(_) => false,
675        };
676        if !has_instances {
677            if let Some(w) = watcher.as_mut() {
678                match w.next().await {
679                    Some(Ok(_)) => {}
680                    Some(Err(_)) | None => {
681                        // Watcher stream ended or errored; re-open service watcher.
682                        watcher = match Service::open(fadb::ServiceMarker) {
683                            Ok(service) => service.watch().await.ok(),
684                            Err(_) => None,
685                        };
686                    }
687                }
688            } else {
689                fasync::Timer::new(zx::MonotonicInstant::after(zx::Duration::from_millis(500)))
690                    .await;
691                watcher = match Service::open(fadb::ServiceMarker) {
692                    Ok(service) => service.watch().await.ok(),
693                    Err(_) => None,
694                };
695            }
696        }
697    }
698}
699
700#[derive(Default)]
701struct FunctionFsRootDir {
702    state: Arc<LockDepMutex<FunctionFsState, FunctionFsStateLock>>,
703    stats: Arc<FunctionFsStats>,
704    _inspect_node: inspect::LazyNode,
705}
706
707impl FunctionFsRootDir {
708    fn new(parent_inspect_node: &inspect::Node) -> Self {
709        let stats = Arc::new(FunctionFsStats::default());
710        let state = Arc::new(LockDepMutex::new(FunctionFsState {
711            event_history: VecDeque::with_capacity(MAX_INSPECT_EVENTS),
712            ..Default::default()
713        }));
714        let inspect_node = create_lazy_inspect_node(parent_inspect_node, &state, &stats);
715        Self { state, stats, _inspect_node: inspect_node }
716    }
717
718    fn create_endpoints(&self, kernel: &Kernel) -> Result<(), Errno> {
719        let mut state = self.state.lock();
720
721        // create_endpoints can be called multiple times as descriptors are written
722        // to the control endpoint.
723        if state.has_input_output_endpoints {
724            return Ok(());
725        }
726
727        state.session_id = state.session_id.wrapping_add(1);
728        let session_id = state.session_id;
729
730        let (read_command_sender, read_command_receiver) = async_channel::unbounded();
731        state.adb_read_channel = Some(read_command_sender);
732
733        let (write_command_sender, write_command_receiver) = async_channel::unbounded();
734        state.adb_write_channel = Some(write_command_sender);
735
736        state.event_queue.clear();
737        state.event_queue.push_back(usb_functionfs_event {
738            type_: usb_functionfs_event_type_FUNCTIONFS_BIND as u8,
739            ..Default::default()
740        });
741        state.record_event("BIND");
742        state.waiters.notify_fd_events(FdEvents::POLLIN);
743        state.waiters.notify_all();
744        state.has_input_output_endpoints = true;
745        state.record_event("ENDPOINTS_CREATED");
746
747        let state_copy = Arc::clone(&self.state);
748        let stats_copy = Arc::clone(&self.stats);
749        // Spawn background worker to watch for USB ADB hardware service and manage connection lifecycle.
750        kernel.kthreads.spawn_future(
751            move || async move {
752                manage_adb_connection(
753                    read_command_receiver,
754                    write_command_receiver,
755                    state_copy,
756                    stats_copy,
757                    session_id,
758                )
759                .await
760            },
761            "functionfs_adb_worker",
762        );
763
764        Ok(())
765    }
766
767    fn from_fs(fs: &FileSystem) -> &Self {
768        fs.root()
769            .node
770            .downcast_ops::<FunctionFsRootDir>()
771            .expect("failed to downcast functionfs root dir")
772    }
773
774    fn from_file(file: &FileObject) -> &Self {
775        Self::from_fs(&file.fs)
776    }
777
778    fn on_control_opened(&self) {
779        let mut state = self.state.lock();
780        state.num_open_file_objects += 1;
781        state.record_event("CONTROL_OPENED");
782    }
783
784    fn on_endpoint_opened(&self) {
785        let mut state = self.state.lock();
786        state.num_open_file_objects += 1;
787        state.record_event("ENDPOINT_OPENED");
788    }
789
790    fn on_control_closed(&self) {
791        self.on_file_closed("CONTROL_CLOSED");
792    }
793
794    fn on_endpoint_closed(&self) {
795        self.on_file_closed("ENDPOINT_CLOSED");
796    }
797
798    fn on_file_closed(&self, event: &'static str) {
799        let mut state = self.state.lock();
800        state.num_open_file_objects -= 1;
801        state.record_event(event);
802        if state.num_open_file_objects == 0 {
803            state.session_id = state.session_id.wrapping_add(1);
804            // When all open endpoint file objects are closed (`ffs->opened == 0`), the filesystem
805            // resets to its initial state.
806            if let Some(device_proxy) = state.device_proxy.take() {
807                // Use a bounded 5-second deadline to prevent close_control_file / adbd exit
808                // from hanging indefinitely if the ADB driver daemon or FIDL channel is hung
809                // or unbinding during a role switch.
810                let deadline = zx::MonotonicInstant::after(zx::Duration::from_seconds(5));
811                match device_proxy.stop_adb(deadline) {
812                    Ok(Ok(())) => {}
813                    Ok(Err(status)) => {
814                        log_warn!(
815                            "Failed to stop adb driver on control reset: {}",
816                            zx::Status::err_from_raw(status)
817                        );
818                    }
819                    Err(err) => {
820                        log_warn!("FIDL error calling StopAdb on control reset: {err}");
821                    }
822                }
823            }
824
825            state.has_input_output_endpoints = false;
826            state.is_online = false;
827            state.adb_read_channel = None;
828            state.adb_write_channel = None;
829            state.event_queue.clear();
830            state.control_resets += 1;
831            state.record_event("CONTROL_RESET");
832            state.waiters.notify_all();
833        }
834    }
835
836    fn wait_until_online(
837        &self,
838        current_task: &CurrentTask,
839        file: &FileObject,
840    ) -> Result<(), Errno> {
841        let initial_session_id = self.state.lock().session_id;
842        loop {
843            let waiter = {
844                let state = self.state.lock();
845                if !state.has_input_output_endpoints || state.session_id != initial_session_id {
846                    return error!(ESHUTDOWN);
847                }
848                if state.is_online {
849                    return Ok(());
850                }
851                if file.flags().contains(OpenFlags::NONBLOCK) {
852                    return error!(EAGAIN);
853                }
854                let waiter = Waiter::new();
855                state.waiters.wait_async(&waiter);
856                waiter
857            };
858            waiter.wait(current_task)?;
859        }
860    }
861
862    fn available(&self) -> usize {
863        let state = self.state.lock();
864        state.event_queue.len()
865    }
866
867    fn write(
868        &self,
869        current_task: &CurrentTask,
870        file: &FileObject,
871        data: Vec<u8>,
872    ) -> Result<usize, Errno> {
873        self.wait_until_online(current_task, file)?;
874
875        let pending = Arc::<PendingResult<usize>>::default();
876        let guard = pending.event.begin_wait();
877
878        if let Some(channel) = self.state.lock().adb_write_channel.as_ref() {
879            channel.send_blocking(WriteCommand { data, pending: pending.clone() }).map_err(
880                |err| {
881                    log_warn!(
882                        "FunctionFsRootDir::write (ep2 bulk IN) failed to send command for task {} (pid {}): {err}",
883                        current_task.command(),
884                        current_task.get_pid()
885                    );
886                    errno!(EINVAL)
887                },
888            )?;
889        } else {
890            log_warn!(
891                "FunctionFsRootDir::write (ep2 bulk IN) called by task {} (pid {}) with {} bytes, but adb_write_channel is None (ENODEV)",
892                current_task.command(),
893                current_task.get_pid(),
894                data.len()
895            );
896            return error!(ENODEV);
897        }
898
899        current_task.block_until(guard, zx::MonotonicInstant::INFINITE)?;
900
901        let mut result = pending.result.lock();
902        result.take().ok_or_else(|| errno!(EINTR))?
903    }
904
905    fn read(&self, current_task: &CurrentTask, file: &FileObject) -> Result<Vec<u8>, Errno> {
906        self.wait_until_online(current_task, file)?;
907
908        let pending = Arc::<PendingResult<Vec<u8>>>::default();
909        let guard = pending.event.begin_wait();
910        if let Some(channel) = self.state.lock().adb_read_channel.as_ref() {
911            channel.send_blocking(ReadCommand { pending: pending.clone() }).map_err(|err| {
912                log_warn!(
913                    "FunctionFsRootDir::read (ep1 bulk OUT) failed to send command for task {} (pid {}): {err}",
914                    current_task.command(),
915                    current_task.get_pid()
916                );
917                errno!(EINVAL)
918            })?;
919        } else {
920            log_warn!(
921                "FunctionFsRootDir::read (ep1 bulk OUT) called by task {} (pid {}), but adb_read_channel is None (ENODEV)",
922                current_task.command(),
923                current_task.get_pid()
924            );
925            return error!(ENODEV);
926        }
927
928        current_task.block_until(guard, zx::MonotonicInstant::INFINITE)?;
929
930        let mut result = pending.result.lock();
931        result.take().ok_or_else(|| errno!(EINTR))?
932    }
933}
934
935impl FsNodeOps for FunctionFsRootDir {
936    fs_node_impl_dir_readonly!();
937
938    fn create_file_ops(
939        &self,
940        _node: &FsNode,
941        _current_task: &CurrentTask,
942        _flags: OpenFlags,
943    ) -> Result<Box<dyn FileOps>, Errno> {
944        let mut entries = vec![];
945        entries.push(VecDirectoryEntry {
946            entry_type: DirectoryEntryType::REG,
947            name: CONTROL_ENDPOINT.into(),
948            inode: Some(CONTROL_ENDPOINT_NODE_ID),
949        });
950
951        let state = self.state.lock();
952        if state.has_input_output_endpoints {
953            entries.push(VecDirectoryEntry {
954                entry_type: DirectoryEntryType::REG,
955                name: INPUT_ENDPOINT.into(),
956                inode: Some(INPUT_ENDPOINT_NODE_ID),
957            });
958            entries.push(VecDirectoryEntry {
959                entry_type: DirectoryEntryType::REG,
960                name: OUTPUT_ENDPOINT.into(),
961                inode: Some(OUTPUT_ENDPOINT_NODE_ID),
962            });
963        }
964
965        Ok(VecDirectory::new_file(entries))
966    }
967
968    fn lookup(
969        &self,
970        entry: &DirEntry,
971        _current_task: &CurrentTask,
972        name: &FsStr,
973    ) -> Result<starnix_core::vfs::FsNodeHandle, Errno> {
974        let name = std::str::from_utf8(name).map_err(|_| errno!(ENOENT))?;
975        let cred = entry.node.info().cred();
976        match name {
977            CONTROL_ENDPOINT => Ok(entry.node.fs().create_node(
978                CONTROL_ENDPOINT_NODE_ID,
979                FunctionFsControlEndpoint,
980                FsNodeInfo::new(mode!(IFREG, 0o600), cred),
981            )),
982            OUTPUT_ENDPOINT => Ok(entry.node.fs().create_node(
983                OUTPUT_ENDPOINT_NODE_ID,
984                FunctionFsOutputEndpoint,
985                FsNodeInfo::new(mode!(IFREG, 0o600), cred),
986            )),
987            INPUT_ENDPOINT => Ok(entry.node.fs().create_node(
988                INPUT_ENDPOINT_NODE_ID,
989                FunctionFsInputEndpoint,
990                FsNodeInfo::new(mode!(IFREG, 0o600), cred),
991            )),
992            _ => error!(ENOENT),
993        }
994    }
995}
996
997// FunctionFS Control Endpoint is both readable and writable.
998// Clients should write USB descriptors to the endpoint to setup the USB connection.
999// Clients can read `usb_functionfs_event`s to know about the USB connection state.
1000struct FunctionFsControlEndpoint;
1001impl FsNodeOps for FunctionFsControlEndpoint {
1002    fs_node_impl_not_dir!();
1003
1004    fn create_file_ops(
1005        &self,
1006        node: &FsNode,
1007        _current_task: &CurrentTask,
1008        _flags: OpenFlags,
1009    ) -> Result<Box<dyn FileOps>, Errno> {
1010        let fs = node.fs();
1011        let rootdir = fs
1012            .root()
1013            .node
1014            .downcast_ops::<FunctionFsRootDir>()
1015            .expect("failed to downcast functionfs root dir");
1016        rootdir.on_control_opened();
1017        Ok(Box::new(FunctionFsControlEndpoint))
1018    }
1019}
1020
1021impl FileOps for FunctionFsControlEndpoint {
1022    fileops_impl_seekless!();
1023    fileops_impl_noop_sync!();
1024
1025    fn close(self: Box<Self>, file: &FileObjectState, _current_task: &CurrentTask) {
1026        let rootdir = FunctionFsRootDir::from_fs(&file.fs);
1027        rootdir.on_control_closed();
1028    }
1029
1030    fn read(
1031        &self,
1032        file: &FileObject,
1033        _current_task: &CurrentTask,
1034        _offset: usize,
1035        data: &mut dyn OutputBuffer,
1036    ) -> Result<usize, Errno> {
1037        // The control endpoint does not currently implement blocking read.
1038        // ADB would only read from this endpoint after polling it.
1039        track_stub!(
1040            TODO("https://fxbug.dev/329699340"),
1041            "FunctionFS blocking read on control endpoint"
1042        );
1043
1044        let rootdir = FunctionFsRootDir::from_file(file);
1045
1046        let mut state = rootdir.state.lock();
1047        if !state.event_queue.is_empty() {
1048            if data.available() < std::mem::size_of::<usb_functionfs_event>() {
1049                return error!(EINVAL);
1050            }
1051        } else {
1052            return error!(EAGAIN);
1053        }
1054        let front = state.event_queue.pop_front().expect("pop from non-empty event queue");
1055        data.write(front.as_bytes())
1056    }
1057
1058    fn write(
1059        &self,
1060        file: &FileObject,
1061        current_task: &CurrentTask,
1062        _offset: usize,
1063        data: &mut dyn InputBuffer,
1064    ) -> Result<usize, Errno> {
1065        // The ADB driver creates and passes its own descriptors to the host system over the wire,
1066        // and so, Starnix does not need to parse the descriptors that Android sends.
1067        // Here we directly attempt to connect to the driver via FIDL, and create endpoints for data transfer.
1068        track_stub!(TODO("https://fxbug.dev/329699340"), "FunctionFS should parse descriptors");
1069
1070        let rootdir = FunctionFsRootDir::from_file(file);
1071        rootdir.create_endpoints(current_task.kernel().deref())?;
1072
1073        Ok(data.drain())
1074    }
1075
1076    fn wait_async(
1077        &self,
1078        file: &FileObject,
1079        _current_task: &CurrentTask,
1080        waiter: &Waiter,
1081        events: FdEvents,
1082        handler: EventHandler,
1083    ) -> Option<WaitCanceler> {
1084        let rootdir = FunctionFsRootDir::from_file(file);
1085        let state = rootdir.state.lock();
1086        Some(state.waiters.wait_async_fd_events(waiter, events, handler))
1087    }
1088
1089    fn query_events(
1090        &self,
1091        file: &FileObject,
1092        _current_task: &CurrentTask,
1093    ) -> Result<FdEvents, Errno> {
1094        let rootdir = FunctionFsRootDir::from_file(file);
1095        if rootdir.available() > 0 { Ok(FdEvents::POLLIN) } else { Ok(FdEvents::empty()) }
1096    }
1097}
1098
1099// FunctionFSInputEndpoint is device to host communication, a.k.a. the "IN" USB direction.
1100// This endpoint is writable, and not readable.
1101struct FunctionFsInputEndpoint;
1102impl FsNodeOps for FunctionFsInputEndpoint {
1103    fs_node_impl_not_dir!();
1104
1105    fn create_file_ops(
1106        &self,
1107        node: &FsNode,
1108        _current_task: &CurrentTask,
1109        _flags: OpenFlags,
1110    ) -> Result<Box<dyn FileOps>, Errno> {
1111        FunctionFsRootDir::from_fs(&node.fs()).on_endpoint_opened();
1112        Ok(Box::new(FunctionFsInputEndpoint))
1113    }
1114}
1115
1116impl FileOps for FunctionFsInputEndpoint {
1117    fileops_impl_seekless!();
1118    fileops_impl_noop_sync!();
1119
1120    fn close(self: Box<Self>, file: &FileObjectState, _current_task: &CurrentTask) {
1121        FunctionFsRootDir::from_fs(&file.fs).on_endpoint_closed();
1122    }
1123
1124    fn read(
1125        &self,
1126        _file: &FileObject,
1127        _current_task: &CurrentTask,
1128        _offset: usize,
1129        _data: &mut dyn OutputBuffer,
1130    ) -> Result<usize, Errno> {
1131        error!(EINVAL)
1132    }
1133
1134    fn write(
1135        &self,
1136        file: &FileObject,
1137        current_task: &CurrentTask,
1138        _offset: usize,
1139        data: &mut dyn InputBuffer,
1140    ) -> Result<usize, Errno> {
1141        let bytes = data.read_all().map_err(|err| {
1142            log_warn!(
1143                "FunctionFsInputEndpoint (ep2 bulk IN) input buffer read failed for task {} (pid {}): {err}",
1144                current_task.command(),
1145                current_task.get_pid()
1146            );
1147            err
1148        })?;
1149        let rootdir = FunctionFsRootDir::from_file(file);
1150        rootdir.write(current_task, file, bytes)
1151    }
1152}
1153
1154// FunctionFSOutputEndpoint is host to device communication, a.k.a. the "OUT" USB direction.
1155// This endpoint is readable, and not writable.
1156struct FunctionFsOutputEndpoint;
1157impl FsNodeOps for FunctionFsOutputEndpoint {
1158    fs_node_impl_not_dir!();
1159
1160    fn create_file_ops(
1161        &self,
1162        node: &FsNode,
1163        _current_task: &CurrentTask,
1164        _flags: OpenFlags,
1165    ) -> Result<Box<dyn FileOps>, Errno> {
1166        FunctionFsRootDir::from_fs(&node.fs()).on_endpoint_opened();
1167        Ok(Box::new(FunctionFsOutputFileObject))
1168    }
1169}
1170
1171struct FunctionFsOutputFileObject;
1172
1173impl FileOps for FunctionFsOutputFileObject {
1174    fileops_impl_seekless!();
1175    fileops_impl_noop_sync!();
1176
1177    fn close(self: Box<Self>, file: &FileObjectState, _current_task: &CurrentTask) {
1178        FunctionFsRootDir::from_fs(&file.fs).on_endpoint_closed();
1179    }
1180
1181    fn read(
1182        &self,
1183        file: &FileObject,
1184        current_task: &CurrentTask,
1185        _offset: usize,
1186        data: &mut dyn OutputBuffer,
1187    ) -> Result<usize, Errno> {
1188        let rootdir = FunctionFsRootDir::from_file(file);
1189        let payload = rootdir.read(current_task, file)?;
1190        if payload.len() > data.available() {
1191            // This means the data will only be partially written, with the rest discarded.
1192            // Instead of attempting this, we'll instead return error to the client.
1193            log_warn!(
1194                "FunctionFsOutputFileObject (ep1 bulk OUT) buffer overflow for task {} (pid {}): payload len {} > buffer avail {}",
1195                current_task.command(),
1196                current_task.get_pid(),
1197                payload.len(),
1198                data.available()
1199            );
1200            rootdir.stats.ep1_buffer_overflow_errors.fetch_add(1, Ordering::Relaxed);
1201            return error!(EINVAL);
1202        }
1203
1204        data.write(&payload).map_err(|err| {
1205            log_warn!(
1206                "FunctionFsOutputFileObject (ep1 bulk OUT) output buffer write failed for task {} (pid {}): {err}",
1207                current_task.command(),
1208                current_task.get_pid()
1209            );
1210            err
1211        })
1212    }
1213
1214    fn write(
1215        &self,
1216        _file: &FileObject,
1217        _current_task: &CurrentTask,
1218        _offset: usize,
1219        _data: &mut dyn InputBuffer,
1220    ) -> Result<usize, Errno> {
1221        error!(EINVAL)
1222    }
1223}
1224
1225#[cfg(test)]
1226mod tests {
1227    use super::*;
1228    use diagnostics_assertions::{AnyProperty, TreeAssertion, assert_data_tree};
1229    use fidl::endpoints::RequestStream;
1230    use std::pin::pin;
1231
1232    #[fuchsia::test]
1233    async fn test_inspect_initial_hierarchy() {
1234        let inspector = inspect::Inspector::default();
1235        let _rootdir = FunctionFsRootDir::new(inspector.root());
1236
1237        assert_data_tree!(inspector, root: {
1238            "usb-functionfs": {
1239                is_online: false,
1240                num_open_file_objects: 0u64,
1241                event_queue_depth: 0u64,
1242                has_input_output_endpoints: false,
1243                control_resets: 0u64,
1244                event_history: {},
1245                ep1_bulk_out: {
1246                    total_bytes_read: 0u64,
1247                    read_count: 0u64,
1248                    read_errors: 0u64,
1249                    buffer_overflow_errors: 0u64,
1250                },
1251                ep2_bulk_in: {
1252                    total_bytes_written: 0u64,
1253                    write_count: 0u64,
1254                    write_errors: 0u64,
1255                },
1256            }
1257        });
1258    }
1259
1260    #[fuchsia::test]
1261    async fn test_control_endpoint_lifecycle_and_reset() {
1262        let inspector = inspect::Inspector::default();
1263        let rootdir = FunctionFsRootDir::new(inspector.root());
1264
1265        // Open two control file objects.
1266        rootdir.on_control_opened();
1267        rootdir.on_control_opened();
1268
1269        // Simulate active endpoints, online state, and a queued event before closing.
1270        {
1271            let mut state_locked = rootdir.state.lock();
1272            state_locked.has_input_output_endpoints = true;
1273            state_locked.is_online = true;
1274            state_locked.event_queue.push_back(usb_functionfs_event::default());
1275        }
1276
1277        assert_data_tree!(inspector, root: {
1278            "usb-functionfs": contains {
1279                num_open_file_objects: 2u64,
1280                has_input_output_endpoints: true,
1281                is_online: true,
1282                event_queue_depth: 1u64,
1283                control_resets: 0u64,
1284                event_history: {
1285                    "0": {
1286                        "@time": AnyProperty,
1287                        event: "CONTROL_OPENED",
1288                    },
1289                    "1": {
1290                        "@time": AnyProperty,
1291                        event: "CONTROL_OPENED",
1292                    },
1293                },
1294            }
1295        });
1296
1297        // Closing the first control file object decrements the count to 1 without resetting state.
1298        rootdir.on_control_closed();
1299        {
1300            let state_locked = rootdir.state.lock();
1301            assert_eq!(state_locked.num_open_file_objects, 1);
1302            assert!(state_locked.has_input_output_endpoints);
1303            assert!(state_locked.is_online);
1304            assert_eq!(state_locked.event_queue.len(), 1);
1305        }
1306
1307        assert_data_tree!(inspector, root: {
1308            "usb-functionfs": contains {
1309                num_open_file_objects: 1u64,
1310                has_input_output_endpoints: true,
1311                is_online: true,
1312                event_queue_depth: 1u64,
1313                control_resets: 0u64,
1314                event_history: {
1315                    "0": {
1316                        "@time": AnyProperty,
1317                        event: "CONTROL_OPENED",
1318                    },
1319                    "1": {
1320                        "@time": AnyProperty,
1321                        event: "CONTROL_OPENED",
1322                    },
1323                    "2": {
1324                        "@time": AnyProperty,
1325                        event: "CONTROL_CLOSED",
1326                    },
1327                },
1328            }
1329        });
1330
1331        // Closing the last control file object resets state and logs CONTROL_RESET.
1332        rootdir.on_control_closed();
1333        {
1334            let state_locked = rootdir.state.lock();
1335            assert_eq!(state_locked.num_open_file_objects, 0);
1336            assert!(!state_locked.has_input_output_endpoints);
1337            assert!(!state_locked.is_online);
1338            assert!(state_locked.event_queue.is_empty());
1339        }
1340
1341        assert_data_tree!(inspector, root: {
1342            "usb-functionfs": contains {
1343                num_open_file_objects: 0u64,
1344                has_input_output_endpoints: false,
1345                is_online: false,
1346                event_queue_depth: 0u64,
1347                control_resets: 1u64,
1348                event_history: {
1349                    "0": {
1350                        "@time": AnyProperty,
1351                        event: "CONTROL_OPENED",
1352                    },
1353                    "1": {
1354                        "@time": AnyProperty,
1355                        event: "CONTROL_OPENED",
1356                    },
1357                    "2": {
1358                        "@time": AnyProperty,
1359                        event: "CONTROL_CLOSED",
1360                    },
1361                    "3": {
1362                        "@time": AnyProperty,
1363                        event: "CONTROL_CLOSED",
1364                    },
1365                    "4": {
1366                        "@time": AnyProperty,
1367                        event: "CONTROL_RESET",
1368                    },
1369                },
1370            }
1371        });
1372    }
1373
1374    #[fuchsia::test]
1375    async fn test_event_history_ring_buffer_eviction() {
1376        let inspector = inspect::Inspector::default();
1377        let rootdir = FunctionFsRootDir::new(inspector.root());
1378
1379        {
1380            let mut state = rootdir.state.lock();
1381            for i in 0..35 {
1382                state.record_event(format!("event_{i}"));
1383            }
1384        }
1385
1386        let mut event_history_assertion = TreeAssertion::new("event_history", true);
1387        for i in 3..35 {
1388            let mut child = TreeAssertion::new(&i.to_string(), true);
1389            child.add_property_assertion("@time", Arc::new(AnyProperty));
1390            child.add_property_assertion("event", Arc::new(format!("event_{i}")));
1391            event_history_assertion.add_child_assertion(child);
1392        }
1393
1394        assert_data_tree!(inspector, root: {
1395            "usb-functionfs": contains {
1396                event_history_assertion,
1397            }
1398        });
1399    }
1400
1401    #[fuchsia::test]
1402    fn test_handle_adb_inspect_updates_and_error_filtering() {
1403        let mut exec = fasync::TestExecutor::new_with_fake_time();
1404
1405        let mut test_fut = pin!(async {
1406            let inspector = inspect::Inspector::default();
1407            let rootdir = FunctionFsRootDir::new(inspector.root());
1408
1409            let (proxy, mut stream) =
1410                fidl::endpoints::create_proxy_and_stream::<fadb::UsbAdbImpl_Marker>();
1411            let (read_sender, read_receiver) = async_channel::unbounded();
1412            let (write_sender, write_receiver) = async_channel::unbounded();
1413
1414            // Simulate the BIND event and endpoints established by create_endpoints().
1415            {
1416                let mut state = rootdir.state.lock();
1417                state.has_input_output_endpoints = true;
1418                state.event_queue.push_back(usb_functionfs_event {
1419                    type_: usb_functionfs_event_type_FUNCTIONFS_BIND as u8,
1420                    ..Default::default()
1421                });
1422                state.record_event("BIND");
1423            }
1424
1425            let adb_fut = handle_adb(
1426                proxy,
1427                None,
1428                read_receiver,
1429                write_receiver,
1430                Arc::clone(&rootdir.state),
1431                Arc::clone(&rootdir.stats),
1432                0,
1433            );
1434
1435            let driver_fut = async move {
1436                // Transition to ONLINE.
1437                stream
1438                    .control_handle()
1439                    .send_on_status_changed(fadb::StatusFlags::ONLINE)
1440                    .expect("send ONLINE status");
1441
1442                // 1. Successful Receive (4 bytes).
1443                let read_ok = Arc::<PendingResult<Vec<u8>>>::default();
1444                read_sender
1445                    .send(ReadCommand { pending: read_ok.clone() })
1446                    .await
1447                    .expect("send read command");
1448                match stream.next().await.expect("stream item").expect("fidl request") {
1449                    fadb::UsbAdbImpl_Request::Receive { responder } => {
1450                        responder.send(Ok(&[1, 2, 3, 4])).expect("send Receive Ok");
1451                    }
1452                    other => panic!("Unexpected request: {other:?}"),
1453                }
1454
1455                // 2. Genuine driver error on Receive (ZX_ERR_IO) -> increments read_errors.
1456                let read_io_err = Arc::<PendingResult<Vec<u8>>>::default();
1457                read_sender
1458                    .send(ReadCommand { pending: read_io_err.clone() })
1459                    .await
1460                    .expect("send read command");
1461                match stream.next().await.expect("stream item").expect("fidl request") {
1462                    fadb::UsbAdbImpl_Request::Receive { responder } => {
1463                        responder
1464                            .send(Err(zx::Status::IO.into_raw()))
1465                            .expect("send Receive IO error");
1466                    }
1467                    other => panic!("Unexpected request: {other:?}"),
1468                }
1469
1470                // 3. Expected shutdown errors on Receive -> must return ESHUTDOWN and NOT
1471                // increment read_errors.
1472                let mut read_shutdown_errs = Vec::new();
1473                for status in [zx::Status::BAD_STATE, zx::Status::CANCELED, zx::Status::PEER_CLOSED]
1474                {
1475                    let pending = Arc::<PendingResult<Vec<u8>>>::default();
1476                    read_sender
1477                        .send(ReadCommand { pending: pending.clone() })
1478                        .await
1479                        .expect("send read command");
1480                    read_shutdown_errs.push(pending);
1481                    match stream.next().await.expect("stream item").expect("fidl request") {
1482                        fadb::UsbAdbImpl_Request::Receive { responder } => {
1483                            responder
1484                                .send(Err(status.into_raw()))
1485                                .expect("send Receive shutdown error");
1486                        }
1487                        other => panic!("Unexpected request: {other:?}"),
1488                    }
1489                }
1490
1491                // 4. Successful QueueTx (5 bytes).
1492                let write_ok = Arc::<PendingResult<usize>>::default();
1493                write_sender
1494                    .send(WriteCommand {
1495                        data: vec![10, 20, 30, 40, 50],
1496                        pending: write_ok.clone(),
1497                    })
1498                    .await
1499                    .expect("send write command");
1500                match stream.next().await.expect("stream item").expect("fidl request") {
1501                    fadb::UsbAdbImpl_Request::QueueTx { data, responder } => {
1502                        assert_eq!(data, vec![10, 20, 30, 40, 50]);
1503                        responder.send(Ok(())).expect("send QueueTx Ok");
1504                    }
1505                    other => panic!("Unexpected request: {other:?}"),
1506                }
1507
1508                // 5. Genuine driver error on QueueTx (ZX_ERR_IO) -> increments write_errors.
1509                let write_io_err = Arc::<PendingResult<usize>>::default();
1510                write_sender
1511                    .send(WriteCommand { data: vec![1, 2], pending: write_io_err.clone() })
1512                    .await
1513                    .expect("send write command");
1514                match stream.next().await.expect("stream item").expect("fidl request") {
1515                    fadb::UsbAdbImpl_Request::QueueTx { responder, .. } => {
1516                        responder
1517                            .send(Err(zx::Status::IO.into_raw()))
1518                            .expect("send QueueTx IO error");
1519                    }
1520                    other => panic!("Unexpected request: {other:?}"),
1521                }
1522
1523                // 6. Expected shutdown errors on QueueTx -> must return ESHUTDOWN and NOT
1524                // increment write_errors.
1525                let mut write_shutdown_errs = Vec::new();
1526                for status in [zx::Status::BAD_STATE, zx::Status::CANCELED, zx::Status::PEER_CLOSED]
1527                {
1528                    let pending = Arc::<PendingResult<usize>>::default();
1529                    write_sender
1530                        .send(WriteCommand { data: vec![1], pending: pending.clone() })
1531                        .await
1532                        .expect("send write command");
1533                    write_shutdown_errs.push(pending);
1534                    match stream.next().await.expect("stream item").expect("fidl request") {
1535                        fadb::UsbAdbImpl_Request::QueueTx { responder, .. } => {
1536                            responder
1537                                .send(Err(status.into_raw()))
1538                                .expect("send QueueTx shutdown error");
1539                        }
1540                        other => panic!("Unexpected request: {other:?}"),
1541                    }
1542                }
1543
1544                // Transition to offline before closing the FIDL channel.
1545                stream
1546                    .control_handle()
1547                    .send_on_status_changed(fadb::StatusFlags::empty())
1548                    .expect("send offline status");
1549
1550                // Drop the FIDL request stream to close the server channel.
1551                drop(stream);
1552
1553                // 7. Closed FIDL channel on Receive and QueueTx -> must return ESHUTDOWN and NOT
1554                // increment error counters.
1555                let read_closed = Arc::<PendingResult<Vec<u8>>>::default();
1556                read_sender
1557                    .send(ReadCommand { pending: read_closed.clone() })
1558                    .await
1559                    .expect("send read command on closed channel");
1560                let write_closed = Arc::<PendingResult<usize>>::default();
1561                write_sender
1562                    .send(WriteCommand { data: vec![9], pending: write_closed.clone() })
1563                    .await
1564                    .expect("send write command on closed channel");
1565
1566                drop(read_sender);
1567                drop(write_sender);
1568
1569                (
1570                    read_ok,
1571                    read_io_err,
1572                    read_shutdown_errs,
1573                    write_ok,
1574                    write_io_err,
1575                    write_shutdown_errs,
1576                    read_closed,
1577                    write_closed,
1578                )
1579            };
1580
1581            let (
1582                (),
1583                (
1584                    read_ok,
1585                    read_io_err,
1586                    read_shutdown_errs,
1587                    write_ok,
1588                    write_io_err,
1589                    write_shutdown_errs,
1590                    read_closed,
1591                    write_closed,
1592                ),
1593            ) = futures::join!(adb_fut, driver_fut);
1594
1595            assert_eq!(read_ok.result.lock().take(), Some(Ok(vec![1, 2, 3, 4])));
1596            assert_eq!(read_io_err.result.lock().take(), Some(error!(EINVAL)));
1597            for pending in read_shutdown_errs {
1598                assert_eq!(pending.result.lock().take(), Some(error!(ESHUTDOWN)));
1599            }
1600            assert_eq!(write_ok.result.lock().take(), Some(Ok(5)));
1601            assert_eq!(write_io_err.result.lock().take(), Some(error!(EINVAL)));
1602            for pending in write_shutdown_errs {
1603                assert_eq!(pending.result.lock().take(), Some(error!(ESHUTDOWN)));
1604            }
1605            assert_eq!(read_closed.result.lock().take(), Some(error!(ESHUTDOWN)));
1606            assert_eq!(write_closed.result.lock().take(), Some(error!(ESHUTDOWN)));
1607
1608            assert_data_tree!(inspector, root: {
1609                "usb-functionfs": {
1610                    is_online: false,
1611                    num_open_file_objects: 0u64,
1612                    event_queue_depth: 4u64,
1613                    has_input_output_endpoints: false,
1614                    control_resets: 0u64,
1615                    event_history: {
1616                        "0": {
1617                            "@time": AnyProperty,
1618                            event: "BIND",
1619                        },
1620                        "1": {
1621                            "@time": AnyProperty,
1622                            event: "ENABLE",
1623                        },
1624                        "2": {
1625                            "@time": AnyProperty,
1626                            event: "DISABLE",
1627                        },
1628                        "3": {
1629                            "@time": AnyProperty,
1630                            event: "UNBIND",
1631                        },
1632                    },
1633                    ep1_bulk_out: {
1634                        total_bytes_read: 4u64,
1635                        read_count: 1u64,
1636                        read_errors: 1u64,
1637                        buffer_overflow_errors: 0u64,
1638                    },
1639                    ep2_bulk_in: {
1640                        total_bytes_written: 5u64,
1641                        write_count: 1u64,
1642                        write_errors: 1u64,
1643                    },
1644                }
1645            });
1646        });
1647
1648        while exec.run_until_stalled(&mut test_fut).is_pending() {
1649            assert!(exec.wake_next_timer().is_some(), "Executor stalled with no pending timers");
1650        }
1651    }
1652
1653    #[fuchsia::test]
1654    fn test_stale_session_stream_closure_does_not_inject_unbind() {
1655        let mut exec = fasync::TestExecutor::new_with_fake_time();
1656
1657        let mut test_fut = pin!(async {
1658            let inspector = inspect::Inspector::default();
1659            let rootdir = FunctionFsRootDir::new(inspector.root());
1660
1661            let (proxy1, stream1) =
1662                fidl::endpoints::create_proxy_and_stream::<fadb::UsbAdbImpl_Marker>();
1663            let (read_sender1, read_receiver1) = async_channel::unbounded();
1664            let (write_sender1, write_receiver1) = async_channel::unbounded();
1665
1666            // Start session 1.
1667            rootdir.state.lock().session_id = 1;
1668            rootdir.state.lock().is_online = true;
1669
1670            let adb_fut1 = handle_adb(
1671                proxy1,
1672                None,
1673                read_receiver1,
1674                write_receiver1,
1675                Arc::clone(&rootdir.state),
1676                Arc::clone(&rootdir.stats),
1677                1,
1678            );
1679
1680            // Simulate opening session 2 while session 1's async task is still running.
1681            // The session ID is incremented and active state is updated.
1682            let (client_channel2, _server_channel2) = zx::Channel::create();
1683            {
1684                let mut state = rootdir.state.lock();
1685                state.session_id = 2;
1686                state.is_online = true;
1687                state.device_proxy = Some(fadb::DeviceSynchronousProxy::new(client_channel2));
1688                state.event_queue.clear();
1689            }
1690
1691            // Drop command senders and stream1 to allow adb_fut1 to complete.
1692            drop(read_sender1);
1693            drop(write_sender1);
1694            drop(stream1);
1695
1696            // Run adb_fut1 to completion.
1697            adb_fut1.await;
1698
1699            // Verify that the stale stream1 did NOT set is_online to false, steal session 2's
1700            // device_proxy, or push UNBIND into session 2's event queue.
1701            let state = rootdir.state.lock();
1702            assert!(state.is_online, "Stale session termination must not set is_online to false");
1703            assert!(
1704                state.device_proxy.is_some(),
1705                "Stale session termination must not take the active session's device_proxy"
1706            );
1707            assert!(
1708                state.event_queue.is_empty(),
1709                "Stale session termination must not push UNBIND into the active session queue"
1710            );
1711        });
1712
1713        while exec.run_until_stalled(&mut test_fut).is_pending() {
1714            assert!(exec.wake_next_timer().is_some(), "Executor stalled with no pending timers");
1715        }
1716    }
1717
1718    #[fuchsia::test]
1719    fn test_stale_session_status_changed_is_ignored() {
1720        let mut exec = fasync::TestExecutor::new_with_fake_time();
1721
1722        let mut test_fut = pin!(async {
1723            let inspector = inspect::Inspector::default();
1724            let rootdir = FunctionFsRootDir::new(inspector.root());
1725
1726            let (proxy1, stream1) =
1727                fidl::endpoints::create_proxy_and_stream::<fadb::UsbAdbImpl_Marker>();
1728            let (read_sender1, read_receiver1) = async_channel::unbounded();
1729            let (write_sender1, write_receiver1) = async_channel::unbounded();
1730
1731            rootdir.state.lock().session_id = 1;
1732
1733            let adb_fut1 = handle_adb(
1734                proxy1,
1735                None,
1736                read_receiver1,
1737                write_receiver1,
1738                Arc::clone(&rootdir.state),
1739                Arc::clone(&rootdir.stats),
1740                1,
1741            );
1742
1743            let driver_state = Arc::clone(&rootdir.state);
1744            let driver_fut = async move {
1745                // Advance session_id to 2 before sending event from stream1.
1746                {
1747                    let mut state = driver_state.lock();
1748                    state.session_id = 2;
1749                    state.is_online = true;
1750                    state.event_queue.clear();
1751                }
1752
1753                // Send OnStatusChanged on the stale stream1.
1754                stream1
1755                    .control_handle()
1756                    .send_on_status_changed(fadb::StatusFlags::empty())
1757                    .expect("send offline status");
1758
1759                drop(read_sender1);
1760                drop(write_sender1);
1761                drop(stream1);
1762            };
1763
1764            futures::join!(adb_fut1, driver_fut);
1765
1766            let state = rootdir.state.lock();
1767            assert!(state.is_online, "Stale status change must not overwrite is_online");
1768            assert!(
1769                state.event_queue.is_empty(),
1770                "Stale status change must not push events to active session"
1771            );
1772        });
1773
1774        while exec.run_until_stalled(&mut test_fut).is_pending() {
1775            assert!(exec.wake_next_timer().is_some(), "Executor stalled with no pending timers");
1776        }
1777    }
1778
1779    #[fuchsia::test]
1780    fn test_unbind_clears_endpoints_and_channels() {
1781        let mut exec = fasync::TestExecutor::new_with_fake_time();
1782
1783        let mut test_fut = pin!(async {
1784            let inspector = inspect::Inspector::default();
1785            let rootdir = FunctionFsRootDir::new(inspector.root());
1786
1787            let (proxy, stream) =
1788                fidl::endpoints::create_proxy_and_stream::<fadb::UsbAdbImpl_Marker>();
1789            let (read_sender, read_receiver) = async_channel::unbounded();
1790            let (write_sender, write_receiver) = async_channel::unbounded();
1791
1792            // Simulate state established by create_endpoints() for session 1.
1793            let (client_channel, _server_channel) = zx::Channel::create();
1794            {
1795                let mut state = rootdir.state.lock();
1796                state.session_id = 1;
1797                state.has_input_output_endpoints = true;
1798                state.device_proxy = Some(fadb::DeviceSynchronousProxy::new(client_channel));
1799                state.adb_read_channel = Some(read_sender);
1800                state.adb_write_channel = Some(write_sender);
1801                state.event_queue.push_back(usb_functionfs_event {
1802                    type_: usb_functionfs_event_type_FUNCTIONFS_BIND as u8,
1803                    ..Default::default()
1804                });
1805                state.record_event("BIND");
1806            }
1807
1808            // Drop the driver stream to simulate USB driver unbind (e.g. role switch to Host mode).
1809            drop(stream);
1810
1811            handle_adb(
1812                proxy,
1813                None,
1814                read_receiver,
1815                write_receiver,
1816                Arc::clone(&rootdir.state),
1817                Arc::clone(&rootdir.stats),
1818                1,
1819            )
1820            .await;
1821
1822            let state = rootdir.state.lock();
1823            assert!(!state.is_online);
1824            assert!(
1825                state.device_proxy.is_none(),
1826                "UNBIND must clear device_proxy for the active session"
1827            );
1828            assert!(
1829                !state.has_input_output_endpoints,
1830                "UNBIND must clear has_input_output_endpoints so wait_until_online aborts"
1831            );
1832            assert!(
1833                state.adb_read_channel.is_none(),
1834                "UNBIND must clear adb_read_channel so ep1 reads do not block on dead session"
1835            );
1836            assert!(
1837                state.adb_write_channel.is_none(),
1838                "UNBIND must clear adb_write_channel so ep2 writes do not block on dead session"
1839            );
1840            let events: Vec<u8> = state.event_queue.iter().map(|e| e.type_).collect();
1841            assert_eq!(
1842                events,
1843                vec![
1844                    usb_functionfs_event_type_FUNCTIONFS_BIND as u8,
1845                    usb_functionfs_event_type_FUNCTIONFS_UNBIND as u8,
1846                ],
1847                "Exactly one BIND and one UNBIND event should be queued"
1848            );
1849        });
1850
1851        while exec.run_until_stalled(&mut test_fut).is_pending() {
1852            assert!(exec.wake_next_timer().is_some(), "Executor stalled with no pending timers");
1853        }
1854    }
1855
1856    #[fuchsia::test]
1857    async fn test_create_endpoints_without_hardware_service() {
1858        starnix_core::testing::spawn_kernel_and_run_sync(|current_task| {
1859            let inspector = inspect::Inspector::default();
1860            let rootdir = FunctionFsRootDir::new(inspector.root());
1861
1862            let result = rootdir.create_endpoints(current_task.kernel());
1863            assert_eq!(result, Ok(()));
1864            assert!(rootdir.state.lock().has_input_output_endpoints);
1865            assert!(rootdir.state.lock().adb_read_channel.is_some());
1866            assert!(rootdir.state.lock().adb_write_channel.is_some());
1867            assert_eq!(
1868                rootdir.state.lock().event_queue.front().map(|e| e.type_),
1869                Some(usb_functionfs_event_type_FUNCTIONFS_BIND as u8)
1870            );
1871        })
1872        .await;
1873    }
1874
1875    #[fuchsia::test]
1876    async fn test_connect_to_device_without_hardware_service() {
1877        let result = connect_to_device(AdbProxyMode::None).await;
1878        assert!(result.is_err());
1879    }
1880
1881    #[fuchsia::test]
1882    async fn test_on_control_closed_clears_device_proxy() {
1883        let inspector = inspect::Inspector::default();
1884        let rootdir = FunctionFsRootDir::new(inspector.root());
1885
1886        let (client_channel, _server_channel) = zx::Channel::create();
1887        let device_proxy = fadb::DeviceSynchronousProxy::new(client_channel);
1888        {
1889            let mut state = rootdir.state.lock();
1890            state.num_open_file_objects = 1;
1891            state.device_proxy = Some(device_proxy);
1892        }
1893
1894        rootdir.on_control_closed();
1895
1896        let state = rootdir.state.lock();
1897        assert_eq!(state.num_open_file_objects, 0);
1898        assert!(state.device_proxy.is_none(), "device_proxy should be taken by on_control_closed");
1899    }
1900
1901    #[fuchsia::test]
1902    async fn test_session_change_stops_device_proxy() {
1903        let inspector = inspect::Inspector::default();
1904        let rootdir = FunctionFsRootDir::new(inspector.root());
1905
1906        let (client_channel, server_channel) = zx::Channel::create();
1907        let device_proxy = fadb::DeviceSynchronousProxy::new(client_channel);
1908
1909        // Simulate session_id changed while connecting
1910        let session_id = 1;
1911        {
1912            let mut state = rootdir.state.lock();
1913            state.session_id = 2; // session_id changed!
1914            state.has_input_output_endpoints = true;
1915        }
1916
1917        // When checking session_id after connect_to_device:
1918        {
1919            let mut state_locked = rootdir.state.lock();
1920            if state_locked.session_id != session_id || !state_locked.has_input_output_endpoints {
1921                let deadline = zx::MonotonicInstant::after(zx::Duration::from_seconds(1));
1922                let _ = device_proxy.stop_adb(deadline);
1923            } else {
1924                state_locked.device_proxy = Some(device_proxy);
1925            }
1926        }
1927
1928        // Verify stop_adb request was sent on server_channel
1929        let mut stream = fadb::DeviceRequestStream::from_channel(fidl::AsyncChannel::from_channel(
1930            server_channel,
1931        ));
1932        match stream.next().await {
1933            Some(Ok(fadb::DeviceRequest::StopAdb { responder })) => {
1934                responder.send(Ok(())).expect("send StopAdb response");
1935            }
1936            other => panic!("Expected StopAdb request, got {other:?}"),
1937        }
1938        assert!(rootdir.state.lock().device_proxy.is_none());
1939    }
1940
1941    #[::fuchsia::test]
1942    fn test_functionfs_reset_waits_for_all_endpoint_file_objects() {
1943        let inspector = inspect::Inspector::default();
1944        let rootdir = FunctionFsRootDir::new(inspector.root());
1945
1946        // Open ep0, ep1, and ep2 as adbd does after writing descriptors.
1947        rootdir.on_control_opened();
1948        {
1949            let mut state = rootdir.state.lock();
1950            state.has_input_output_endpoints = true;
1951            state.is_online = true;
1952        }
1953        rootdir.on_endpoint_opened(); // ep1 (bulk OUT)
1954        rootdir.on_endpoint_opened(); // ep2 (bulk IN)
1955
1956        // Closing ep0 while ep1 and ep2 remain open must NOT tear down active endpoints
1957        // (matching Linux drivers/usb/gadget/function/f_fs.c `ffs->opened`).
1958        rootdir.on_control_closed();
1959        assert!(rootdir.state.lock().has_input_output_endpoints);
1960        assert_eq!(rootdir.state.lock().control_resets, 0);
1961
1962        // Closing ep1 still leaves ep2 open (e.g. an in-flight AIO write holding FileHandle).
1963        rootdir.on_endpoint_closed();
1964        assert!(rootdir.state.lock().has_input_output_endpoints);
1965        assert_eq!(rootdir.state.lock().control_resets, 0);
1966
1967        // Closing the final open FileObject (`ffs->opened == 0`) resets FunctionFS.
1968        rootdir.on_endpoint_closed();
1969        assert!(!rootdir.state.lock().has_input_output_endpoints);
1970        assert!(!rootdir.state.lock().is_online);
1971        assert_eq!(rootdir.state.lock().control_resets, 1);
1972    }
1973}