| // Copyright 2026 The Fuchsia Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| //! Shared data schema for Starnix `usbfs` declarative scenarios, recorded golden traces, | |
| //! gadget control RPCs, and documented Starnix-vs-Linux divergences. | |
| pub mod scenarios; | |
| use serde::{Deserialize, Serialize}; | |
| use serde_json::Value; | |
| /// Default loopback TCP address used by `usb_test` and `usbfs_trace_recorder` to control | |
| /// either `fake_usb_device` (on Starnix) or `linux_ffs_daemon` (on Linux). | |
| pub const DEFAULT_CONTROL_ADDR: &str = "127.0.0.1:28765"; | |
| /// Per-scenario USB device descriptor and configuration topology. | |
| #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] | |
| pub struct DeviceSpec { | |
| pub vendor_id: u16, | |
| pub product_id: u16, | |
| pub bcd_usb: u16, | |
| pub bcd_device: u16, | |
| pub device_class: u8, | |
| pub device_subclass: u8, | |
| pub device_protocol: u8, | |
| pub max_packet_size0: u8, | |
| pub speed: String, | |
| #[serde(default)] | |
| pub configs: Vec<ConfigSpec>, | |
| } | |
| /// USB configuration descriptor specification. | |
| #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] | |
| pub struct ConfigSpec { | |
| pub config_value: u8, | |
| pub bm_attributes: u8, | |
| pub max_power_ma: u16, | |
| #[serde(default)] | |
| pub interfaces: Vec<InterfaceSpec>, | |
| } | |
| /// USB interface alternate-setting specification. | |
| #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] | |
| pub struct InterfaceSpec { | |
| pub interface_number: u8, | |
| pub alt_setting: u8, | |
| pub interface_class: u8, | |
| pub interface_subclass: u8, | |
| pub interface_protocol: u8, | |
| #[serde(default)] | |
| pub endpoints: Vec<EndpointSpec>, | |
| } | |
| /// USB endpoint descriptor specification. | |
| #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] | |
| pub struct EndpointSpec { | |
| pub address: u8, | |
| pub xfer_type: String, | |
| pub max_packet_size: u16, | |
| pub interval: u8, | |
| } | |
| /// Declarative input scenario (`scenarios/*.json`). | |
| #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] | |
| pub struct Scenario { | |
| pub name: String, | |
| pub device: DeviceSpec, | |
| #[serde(default)] | |
| pub steps: Vec<Step>, | |
| } | |
| /// A single declarative host or gadget action in a [`Scenario`]. | |
| #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] | |
| pub struct Step { | |
| #[serde(rename = "type")] | |
| pub op: String, | |
| #[serde(flatten)] | |
| pub params: Value, | |
| } | |
| /// Recorded golden trace (`generated/*.json`) produced by executing a [`Scenario`] on Linux. | |
| #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] | |
| pub struct RecordedTrace { | |
| pub name: String, | |
| pub device: DeviceSpec, | |
| #[serde(default)] | |
| pub steps: Vec<RecordedStep>, | |
| } | |
| /// A single step paired with its recorded Linux outcome. | |
| #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] | |
| pub struct RecordedStep { | |
| pub step: Step, | |
| pub outcome: StepOutcome, | |
| } | |
| /// Recorded outcome of executing a [`Step`]. | |
| #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] | |
| pub struct StepOutcome { | |
| pub status: String, | |
| #[serde(default, skip_serializing_if = "Option::is_none")] | |
| pub errno: Option<String>, | |
| #[serde(default, skip_serializing_if = "Value::is_null")] | |
| pub result: Value, | |
| } | |
| /// Gadget-side RPC command sent over [`DEFAULT_CONTROL_ADDR`]. | |
| #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] | |
| #[serde(tag = "cmd", rename_all = "snake_case")] | |
| pub enum DeviceCommand { | |
| ConfigureDevice { | |
| device: DeviceSpec, | |
| }, | |
| ScriptControl { | |
| bm_request_type: u8, | |
| b_request: u8, | |
| #[serde(default, skip_serializing_if = "Option::is_none")] | |
| w_value: Option<u16>, | |
| #[serde(default, skip_serializing_if = "Option::is_none")] | |
| w_index: Option<u16>, | |
| #[serde(default, skip_serializing_if = "Option::is_none")] | |
| w_length: Option<u16>, | |
| action: Value, | |
| }, | |
| ReleaseControl { | |
| hold: String, | |
| action: Value, | |
| }, | |
| PushIn { | |
| ep: u8, | |
| data: Vec<u8>, | |
| }, | |
| PopOut { | |
| ep: u8, | |
| timeout_ms: u64, | |
| }, | |
| AssertEmpty { | |
| ep: u8, | |
| }, | |
| Disconnect, | |
| Reconnect, | |
| } | |
| /// Gadget-side RPC response returned over [`DEFAULT_CONTROL_ADDR`]. | |
| #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] | |
| pub struct DeviceResponse { | |
| pub ok: bool, | |
| #[serde(default, skip_serializing_if = "Option::is_none")] | |
| pub error: Option<String>, | |
| #[serde(default, skip_serializing_if = "Option::is_none")] | |
| pub data: Option<Vec<u8>>, | |
| } | |
| /// Scoped rule allowing a documented Starnix-vs-Linux difference (`divergences.json`). | |
| #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] | |
| pub struct DivergenceRule { | |
| pub scenario_glob: String, | |
| pub step_op: String, | |
| pub field: String, | |
| pub linux_expected: Value, | |
| pub starnix_expected: Value, | |
| pub bug_url: String, | |
| pub reason: String, | |
| } | |
| /// Collection of documented Starnix-vs-Linux divergence rules. | |
| #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] | |
| pub struct DivergenceManifest { | |
| #[serde(default)] | |
| pub divergences: Vec<DivergenceRule>, | |
| } |