blob: 7c9bc84ef82a6a14b8d4a239d1194383fae9708e [file]
// 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>,
}