From 1d18eb72cff35166ad44159844f641d91ffbaa09 Mon Sep 17 00:00:00 2001 From: Keenan Tims Date: Sun, 19 Apr 2026 00:58:39 -0700 Subject: [PATCH] initial commit: beginnings of descriptor definition --- .gitignore | 1 + Cargo.lock | 219 +++++++ Cargo.toml | 17 + src/constants.rs | 490 +++++++++++++++ src/cursor.rs | 169 ++++++ src/descriptors.rs | 1422 ++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 285 +++++++++ 7 files changed, 2603 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/constants.rs create mode 100644 src/cursor.rs create mode 100644 src/descriptors.rs create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..062fc16 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,219 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "byteorder-embedded-io" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed6bb9472871706c9b1f648ca527031e33d647a95706d6ab5659f22ca28d419" +dependencies = [ + "byteorder", + "embedded-io", +] + +[[package]] +name = "defmt" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78" +dependencies = [ + "bitflags", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d4fc12a85bcf441cfe44344c4b72d58493178ce635338a3f3b78943aceb258e" +dependencies = [ + "defmt-parser", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror", +] + +[[package]] +name = "embedded-io" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eb1aa714776b75c7e67e1da744b81a129b3ff919c8712b5e1b32252c1f07cc7" + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "modular-bitfield" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2956e537fc68236d2aa048f55704f231cc93f1c4de42fe1ecb5bd7938061fc4a" +dependencies = [ + "modular-bitfield-impl", + "static_assertions", +] + +[[package]] +name = "modular-bitfield-impl" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b43b4fd69e3437618106f7754f34021b831a514f9e1a98ae863cabcd8d8dad" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "usb-device" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6" +dependencies = [ + "heapless", + "portable-atomic", +] + +[[package]] +name = "usbd-uac2" +version = "0.1.0" +dependencies = [ + "byteorder-embedded-io", + "defmt", + "embedded-io", + "modular-bitfield", + "usb-device", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a43f73f --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "usbd-uac2" +description = "USB Audio Class 2.0 for usb-device" +authors = ["Keenan Tims "] +version = "0.1.0" +edition = "2024" +keywords = ["no-std", "usb-device"] + +[features] +defmt = ["dep:defmt"] + +[dependencies] +byteorder-embedded-io = { version = "0.1.1", features = ["embedded-io"] } +defmt = { version = "1.0.1", optional = true } +embedded-io = "0.7.1" +modular-bitfield = "0.13.1" +usb-device = "0.3" diff --git a/src/constants.rs b/src/constants.rs new file mode 100644 index 0000000..32e9f9e --- /dev/null +++ b/src/constants.rs @@ -0,0 +1,490 @@ +pub const AUDIO: u8 = 0x1; +pub const HEADER: u8 = 0x1; + +/// A.2 Audio Function Subclass Codes +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum FunctionSubclass { + Undefined = 0, +} + +/// A.3 Audio Function Protocol Codes +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum FunctionProtocol { + Undefined = 0, + Version2 = 0x20, +} + +/// A.5 Audio Interface Subclass Codes +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum InterfaceSubclass { + Undefined = 0, + AudioControl = 1, + AudioStreaming = 2, + MidiStreaming = 3, +} + +/// A.6 Audio Interface Protocol Codes +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum InterfaceProtocol { + Undefined = 0, + Version2 = 0x20, +} + +/// A.7 Audio Function Category Codes +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum FunctionCode { + Undefined = 0, + DesktopSpeaker = 1, + HomeTheater = 2, + Microphone = 3, + Headset = 4, + Telephone = 5, + Converter = 6, + SoundRecorder = 7, + IoBox = 8, + MusicalInstrument = 9, + ProAudio = 0xa, + AudioVideo = 0xb, + ControlPanel = 0xc, + Other = 0xff, +} + +/// A.8 Audio Class-Specific Descriptor Types +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum ClassSpecificDescriptorType { + Undefined = 0x20, + Device = 0x21, + Configuration = 0x22, + String = 0x23, + Interface = 0x24, + Endpoint = 0x25, +} + +/// A.9 Audio Class-Specific AC Interface Descriptor Subtypes +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum ClassSpecificACInterfaceDescriptorSubtype { + Undefined = 0, + Header = 0x01, + InputTerminal = 0x02, + OutputTerminal = 0x03, + MixerUnit = 0x04, + SelectorUnit = 0x05, + FeatureUnit = 0x06, + EffectUnit = 0x07, + ProcessingUnit = 0x08, + ExtensionUnit = 0x09, + ClockSource = 0x0A, + ClockSelector = 0x0B, + ClockMultiplier = 0x0C, + SampleRateConverter = 0x0D, +} + +/// A.10 Audio Class-Specific AS Interface Descriptor Subtypes +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum ClassSpecificASInterfaceDescriptorSubtype { + Undefined = 0, + General = 1, + FormatType = 2, + Encoder = 3, + Decoder = 4, +} + +/// A.11 Effect Unit Effect Types +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum EffectUnitEffectType { + Undefined = 0, + ParamEqSection = 1, + Reverb = 2, + ModDelay = 3, + DynRangeComp = 4, +} + +/// A.12 Processing Unit Process Types +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum ProcessingUnitProcessType { + Undefined = 0, + UpDownMix = 1, + DolbyPrologic = 2, + StereoExtender = 3, +} + +/// A.13 Audio Class-Specific Endpoint Descriptor Subtypes +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum ClassSpecificEndpointDescriptorSubtype { + Undefined = 0, + General = 1, +} + +/// A.14 Audio Class-Specific Request Codes +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum ClassSpecificRequest { + Undefined = 0, + Cur = 1, + Range = 2, + Mem = 3, +} + +/// A.15 Encoder Type Codes +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum EncoderType { + Undefined = 0, + Other = 1, + Mpeg = 2, + Ac3 = 3, + Wma = 4, + Dts = 5, +} +/// A.16 Decoder Type Codes +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum DecoderType { + Undefined = 0, + Other = 1, + Mpeg = 2, + Ac3 = 3, + Wma = 4, + Dts = 5, +} + +/// A.17.1 Clock Source Control Selectors +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum ClockSourceControlSelector { + Undefined = 0, + SamFreqControl = 1, + ClockValidControl = 2, +} +/// A.17.2 Clock Selector Control Selectors +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum ClockSelectorControlSelector { + ControlUndefined = 0, + ClockSelectorControl = 1, +} + +/// A.17.3 Clock Multiplier Control Selectors +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum ClockMultiplierControlSelector { + Undefined = 0, + NumeratorControl = 1, + DenominatorControl = 2, +} + +/// A.17.4 Terminal Control Selectors +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum TerminalControlSelector { + Undefined = 0, + CopyProtect = 1, + Connector = 2, + Overload = 3, + Cluster = 4, + Underflow = 5, + Overflow = 6, + Latency = 7, + PhantomPower = 8, +} + +/// A.17.5 Mixer Control Selectors +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum MixerControlSelector { + Undefined = 0, + Mixer = 1, + Cluster = 2, + Underflow = 3, + Overflow = 4, + Latency = 5, +} + +/// A.17.6 Selector Control Selectors +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum SelectorControlSelector { + Undefined = 0, + Selector = 1, + Latency = 2, +} + +/// A.17.7 Feature Unit Control Selectors +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum FeatureUnitControlSelector { + Undefined = 0, + Mute = 0x01, + Volume = 0x02, + Bass = 0x03, + Mid = 0x04, + Treble = 0x05, + GraphicEqualizer = 0x06, + AutomaticGain = 0x07, + Delay = 0x08, + BassBoost = 0x09, + Loudness = 0x0A, + InputGain = 0x0B, + InputGainPad = 0x0C, + PhaseInverter = 0x0D, + Underflow = 0x0E, + Overflow = 0x0F, + Latency = 0x10, + HighpassFilter = 0x11, +} +/// A.17.8.1 Parametric Equalizer Section Effect Unit Control Selectors +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum ParametricEqSectionEffectUnitControlSelector { + Undefined = 0, + Enable = 1, + CenterFreq = 2, + QFactor = 3, + Gain = 4, + Underflow = 5, + Overflow = 6, + Latency = 7, +} + +/// A.17.8.2 Reverberation Effect Unit Control Selectors +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum ReverbEffectUnitControlSelector { + Undefined = 0, + Enable = 1, + Type = 2, + Level = 3, + Time = 4, + Feedback = 5, + PreDelay = 6, + Density = 7, + HiFreqRolloff = 8, + Underflow = 9, + Overflow = 0xa, + Latency = 0xb, +} + +/// A.17.8.3 Modulation Delay Effect Unit Control Selectors +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum ModDelayEffectUnitControlSelector { + Undefined = 0, + Enable = 1, + Balance = 2, + Rate = 3, + Depth = 4, + Time = 5, + Feedback = 6, + Underflow = 7, + Overflow = 8, + Latency = 9, +} + +/// A.17.8.4 Dynamic Range Compressor Effect Unit Control Selectors +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum DynamicRangeCompressorEffectUnitControlSelector { + Undefined = 0, + Enable = 1, + CompressionRate = 2, + MaxAmplitude = 3, + Threshold = 4, + AttackTime = 5, + ReleaseTime = 6, + Underflow = 7, + Overflow = 8, + Latency = 9, +} + +/// A.17.9.1 Up/Down-mix Processing Unit Control Selectors +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum UpDownMixProcessingUnitControlSelector { + Undefined = 0, + Enable = 1, + ModeSelect = 2, + Cluster = 3, + Underflow = 4, + Overflow = 5, + Latency = 6, +} + +/// A.17.9.2 Dolby Prologic Processing Unit Control Selectors +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum DolbyProcessingUnitControlSelectors { + Undefined = 0, + Enable = 1, + ModeSelect = 2, + Cluster = 3, + Underflow = 4, + Overflow = 5, + Latency = 6, +} + +/// A.17.9.3 Stereo Extender Processing Unit Control Selectors +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum StereoExtenderProcessingUnitControlSelector { + Undefined = 0, + Enable = 1, + Width = 2, + Underflow = 3, + Overflow = 4, + Latency = 5, +} + +/// A.17.10 Extension Unit Control Selectors +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum ExtensionUnitControlSelector { + Undefined = 0, + Enable = 1, + Cluster = 2, + Underflow = 3, + Overflow = 4, + Latency = 5, +} + +/// A.17.11 AudioStreaming Interface Control Selectors +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum AudioStreamingInterfaceControlSelector { + Undefined = 0, + ActAltSetting = 1, + ValAltSetting = 2, + AudioDataFormat = 3, +} + +/// A.17.12 Encoder Control Selectors +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum EncoderControlSelector { + Undefined = 0, + BitRate = 0x01, + Quality = 0x02, + Vbr = 0x03, + Type = 0x04, + Underflow = 0x05, + Overflow = 0x06, + EncoderError = 0x07, + Param1 = 0x08, + Param2 = 0x09, + Param3 = 0x0A, + Param4 = 0x0B, + Param5 = 0x0C, + Param6 = 0x0D, + Param7 = 0x0E, + Param8 = 0x0F, +} + +/// A.17.13.1 MPEG Decoder Control Selectors +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum MpegDecoderControlSelector { + Undefined = 0, + DualChannel = 1, + SecondStereo = 2, + Multilingual = 3, + DynRange = 4, + Scaling = 5, + HiloScaling = 6, + Underflow = 7, + Overflow = 8, + DecoderError = 9, +} + +/// A.17.13.2 AC-3 Decoder Control Selectors +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum Ac3DecoderControlSelector { + Undefined = 0, + Mode = 1, + DynRange = 2, + Scaling = 3, + HiloScaling = 4, + Underflow = 5, + Overflow = 6, + DecoderError = 7, +} + +/// A.17.13.3 WMA Decoder Control Selectors +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum WmaDecoderControlSelector { + Undefined = 0, + Underflow = 1, + Overflow = 2, + DecoderError = 3, +} + +/// A.17.13.4 DTS Decoder Control Selectors +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum DtsDecoderControlSelector { + Undefined = 0, + Underflow = 1, + Overflow = 2, + DecoderError = 3, +} + +/// A.17.14 Endpoint Control Selectors +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum EndpointControlSelector { + Undefined = 0, + Pitch = 1, + DataOverrun = 2, + DataUnderrun = 3, +} + +/// Universal Serial Bus Device Class Definition for Terminal Types +#[repr(u16)] +#[non_exhaustive] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum TerminalType { + // USB Terminal Types + UsbUndefined = 0x0100, + UsbStreaming = 0x0101, + UsbVendor = 0x01ff, + + // Input Terminal Types + InUndefined = 0x0200, + InMicrophone = 0x0201, + InDesktopMicrophone = 0x0202, + InPersonalMicrophone = 0x0203, + InOmniDirectionalMicrophone = 0x0204, + InMicrophoneArray = 0x0205, + InProcessingMicrophoneArray = 0x0206, + + // Output Terminal Types + OutUndefined = 0x0300, + OutSpeaker = 0x0301, + OutHeadphones = 0x0302, + OutHeadMountedDisplayAudio = 0x0303, + OutDesktopSpeaker = 0x0304, + OutRoomSpeaker = 0x0305, + OutCommunicationSpeaker = 0x0306, + OutLowFrequencyEffectsSpeaker = 0x0307, + + // External Terminal Types + ExtUndefined = 0x0600, + ExtAnalogConnector = 0x0601, + ExtDigitalAudioInterface = 0x0602, + ExtLineConnector = 0x0603, + ExtLegacyAudioConnector = 0x0604, + ExtSpdifConnector = 0x0605, + Ext1394DaStream = 0x0606, + Ext1394DvStreamSoundtrack = 0x0607, +} diff --git a/src/cursor.rs b/src/cursor.rs new file mode 100644 index 0000000..7007161 --- /dev/null +++ b/src/cursor.rs @@ -0,0 +1,169 @@ +// Copy most of embedded_io_cursor here to avoid multiple embedded-io versions in dep tree + +use core::cmp; +use embedded_io::{BufRead, Error, ErrorKind, ErrorType, Read, Seek, SeekFrom, Write}; + +#[derive(Debug, Default, Eq, PartialEq)] +pub struct Cursor { + inner: T, + pos: usize, +} + +impl Cursor { + /// Creates a new cursor wrapping the provided underlying in-memory buffer. + /// + /// Cursor initial position is `0` even if underlying buffer (e.g., `Vec`) + /// is not empty. So writing to cursor starts with overwriting `Vec` + /// content, not with appending to it. + pub const fn new(inner: T) -> Cursor { + Cursor { pos: 0, inner } + } + + /// Consumes this cursor, returning the underlying value. + pub fn into_inner(self) -> T { + self.inner + } + + /// Gets a reference to the underlying value in this cursor. + pub const fn get_ref(&self) -> &T { + &self.inner + } + + /// Gets a mutable reference to the underlying value in this cursor. + /// + /// Care should be taken to avoid modifying the internal I/O state of the + /// underlying value as it may corrupt this cursor's position. + pub fn get_mut(&mut self) -> &mut T { + &mut self.inner + } + + /// Returns the current position of this cursor. + pub const fn position(&self) -> usize { + self.pos + } + + /// Sets the position of this cursor. + pub fn set_position(&mut self, pos: usize) { + self.pos = pos; + } +} + +impl Cursor +where + T: AsRef<[u8]>, +{ + /// Returns the remaining slice from the current position. + /// + /// This method returns the portion of the underlying buffer that + /// can still be read from the current cursor position. + pub fn remaining_slice(&self) -> &[u8] { + let pos = cmp::min(self.pos, self.inner.as_ref().len()); + &self.inner.as_ref()[pos..] + } + + /// Returns `true` if there are no more bytes to read from the cursor. + /// + /// This is equivalent to checking if `remaining_slice().is_empty()`. + pub fn is_empty(&self) -> bool { + self.pos >= self.inner.as_ref().len() + } +} + +impl Clone for Cursor +where + T: Clone, +{ + #[inline] + fn clone(&self) -> Self { + Cursor { + inner: self.inner.clone(), + pos: self.pos, + } + } + + #[inline] + fn clone_from(&mut self, other: &Self) { + self.inner.clone_from(&other.inner); + self.pos = other.pos; + } +} + +impl ErrorType for Cursor { + type Error = ErrorKind; +} + +// Read implementation for AsRef<[u8]> types +impl Read for Cursor +where + T: AsRef<[u8]>, +{ + fn read(&mut self, buf: &mut [u8]) -> Result { + let remaining = self.remaining_slice(); + let n = cmp::min(buf.len(), remaining.len()); + + if n > 0 { + buf[..n].copy_from_slice(&remaining[..n]); + } + + self.pos += n; + Ok(n) + } +} + +// BufRead implementation for AsRef<[u8]> types +impl BufRead for Cursor +where + T: AsRef<[u8]>, +{ + fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + Ok(self.remaining_slice()) + } + + fn consume(&mut self, amt: usize) { + self.pos += amt; + } +} + +// Seek implementation for AsRef<[u8]> types +impl Seek for Cursor +where + T: AsRef<[u8]>, +{ + fn seek(&mut self, style: SeekFrom) -> Result { + let (base_pos, offset) = match style { + SeekFrom::Start(n) => { + self.pos = n as usize; + return Ok(n); + } + SeekFrom::End(n) => (self.inner.as_ref().len() as u64, n), + SeekFrom::Current(n) => (self.pos as u64, n), + }; + + match base_pos.checked_add_signed(offset) { + Some(n) => { + self.pos = n as usize; + Ok(self.pos as u64) + } + None => Err(ErrorKind::InvalidInput), + } + } +} + +/// Helper function for writing to fixed-size slices +fn slice_write(pos_mut: &mut usize, slice: &mut [u8], buf: &[u8]) -> Result { + let pos = cmp::min(*pos_mut, slice.len()) as usize; + let amt = (&mut slice[pos..]).write(buf).map_err(|err| err.kind())?; + *pos_mut += amt; + Ok(amt) +} + +// Write implementation for &mut [u8] +impl Write for Cursor<&mut [u8]> { + fn write(&mut self, buf: &[u8]) -> Result { + slice_write(&mut self.pos, self.inner, buf) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } +} diff --git a/src/descriptors.rs b/src/descriptors.rs new file mode 100644 index 0000000..14dad87 --- /dev/null +++ b/src/descriptors.rs @@ -0,0 +1,1422 @@ +use core::fmt::{Display, Formatter}; + +use crate::constants::ClassSpecificACInterfaceDescriptorSubtype; +use crate::constants::*; + +use byteorder_embedded_io::{LittleEndian, WriteBytesExt}; +use embedded_io::ErrorType; +use modular_bitfield::prelude::*; +use usb_device::{UsbError, descriptor::DescriptorWriter}; + +#[derive(Debug)] +pub struct DescriptorWriterError { + error: UsbError, +} + +impl Display for DescriptorWriterError { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + write!( + f, + "UsbError: {}", + match self.error { + UsbError::InvalidState => "InvalidState", + UsbError::WouldBlock => "WouldBlock", + UsbError::Unsupported => "Unsupported", + UsbError::BufferOverflow => "BufferOverflow", + UsbError::EndpointMemoryOverflow => "EndpointMemoryOverflow", + UsbError::EndpointOverflow => "EndpointOverflow", + UsbError::InvalidEndpoint => "InvalidEndpoint", + UsbError::ParseError => "ParseError", + } + ) + } +} + +impl core::error::Error for DescriptorWriterError {} + +impl embedded_io::Error for DescriptorWriterError { + fn kind(&self) -> embedded_io::ErrorKind { + embedded_io::ErrorKind::Other + } +} + +impl From for DescriptorWriterError { + fn from(error: UsbError) -> Self { + Self { error } + } +} + +impl From for UsbError { + fn from(error: DescriptorWriterError) -> Self { + error.error + } +} + +struct DescriptorWriterAdapter<'w, 'd> { + writer: &'w mut DescriptorWriter<'d>, + descriptor_type: ClassSpecificDescriptorType, + written: usize, +} + +impl<'w, 'd> embedded_io::Write for DescriptorWriterAdapter<'w, 'd> { + fn write(&mut self, buf: &[u8]) -> Result { + self.writer.write(self.descriptor_type as u8, buf)?; + self.written += buf.len(); + Ok(buf.len()) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } +} + +impl ErrorType for DescriptorWriterAdapter<'_, '_> { + type Error = DescriptorWriterError; +} + +impl<'w, 'd> DescriptorWriterAdapter<'w, 'd> { + fn new( + writer: &'w mut DescriptorWriter<'d>, + descriptor_type: ClassSpecificDescriptorType, + ) -> Self { + Self { + writer, + descriptor_type, + written: 0, + } + } +} + +#[repr(u8)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum ClockType { + External = 0, + InternalFixed = 1, + InternalVariable = 2, + InternalProgrammable = 3, +} + +#[repr(u8)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Specifier)] +#[bits = 2] +pub enum AccessControl { + NotPresent = 0, + ReadOnly = 1, + Programmable = 3, +} + +#[bitfield(bits = 32)] +#[repr(u32)] +#[derive(Copy, Clone, Debug)] +pub struct ChannelConfig { + pub front_left: bool, + pub front_right: bool, + pub front_center: bool, + pub low_frequency_effects: bool, + pub back_left: bool, + pub back_right: bool, + pub front_left_of_center: bool, + pub front_right_of_center: bool, + pub back_center: bool, + pub side_left: bool, + pub side_right: bool, + pub top_center: bool, + pub top_front_left: bool, + pub top_front_center: bool, + pub top_front_right: bool, + pub top_back_left: bool, + pub top_back_center: bool, + pub top_back_right: bool, + pub top_front_left_of_center: bool, + pub top_front_right_of_center: bool, + pub left_row_low_frequency_effects: bool, + pub right_row_low_frequency_effects: bool, + pub top_side_left: bool, + pub top_side_right: bool, + pub bottom_center: bool, + pub back_left_of_center: bool, + pub back_right_of_center: bool, + #[skip] + __: B4, + raw_data: bool, +} + +impl ChannelConfig { + /// Creates a new ChannelConfig with a default channel configuration based on the number of channels. + /// + /// Supports: + /// - 1 channel: Front Left + /// - 2 channels: Front Left, Front Right + /// - 4 channels: Front Left, Front Right, Back Left, Back Right + /// - 6 channels: Front Left, Front Right, Back Left, Back Right, Front Center, LFE + /// - 8 channels: Front Left, Front Right, Back Left, Back Right, Front Center, Side Left, Side Right, LFE + pub fn default_chans(n: u8) -> Self { + match n { + 1 => ChannelConfig::new().with_front_left(true), + 2 => ChannelConfig::new() + .with_front_left(true) + .with_front_right(true), + 4 => ChannelConfig::new() + .with_front_left(true) + .with_front_right(true) + .with_back_left(true) + .with_back_right(true), + 6 => ChannelConfig::new() + .with_front_left(true) + .with_front_center(true) + .with_front_right(true) + .with_back_left(true) + .with_back_right(true) + .with_low_frequency_effects(true), + 8 => ChannelConfig::new() + .with_front_left(true) + .with_front_center(true) + .with_front_right(true) + .with_back_left(true) + .with_back_right(true) + .with_front_center(true) + .with_side_left(true) + .with_side_right(true) + .with_low_frequency_effects(true), + _ => panic!("Unsupported number of channels"), + } + } +} + +#[derive(Copy, Clone)] +#[bitfield(bits = 32)] +pub struct FeatureControls { + mute: AccessControl, + volume: AccessControl, + bass: AccessControl, + mid: AccessControl, + treble: AccessControl, + graphic_eq: AccessControl, + agc: AccessControl, + delay: AccessControl, + bass_boost: AccessControl, + loudness: AccessControl, + input_gain: AccessControl, + input_gain_pad: AccessControl, + phase_inverter: AccessControl, + underflow: AccessControl, + overflow: AccessControl, + hpf: AccessControl, +} + +pub trait Descriptor { + /// The maximum possible size of the descriptor, including the length and descriptor type bytes. For creation of Sized buffers. + const MAX_SIZE: usize; + /// The actual size of the descriptor, including the length and descriptor type bytes. + fn size(&self) -> u8; + /// Write the descriptor using the provided usb_device DescriptorWriter. + fn write_descriptor<'w, 'd>( + &self, + writer: &'w mut DescriptorWriter<'d>, + ) -> Result<(), DescriptorWriterError>; + /// Write the descriptor to the provided buffer. Includes the length and descriptor type bytes. + fn write(&self, writer: &mut T) -> Result<(), T::Error>; +} + +#[derive(Clone, Debug)] +pub struct ClockSource { + pub id: u8, + pub clock_type: ClockType, + pub sof_sync: bool, + pub frequency_access: AccessControl, + pub validity_access: AccessControl, + pub assoc_terminal: u8, + pub string: u8, +} + +impl ClockSource { + fn bm_attributes(&self) -> u8 { + self.clock_type as u8 | if self.sof_sync { 4 } else { 0 } + } + fn bm_controls(&self) -> u8 { + ((self.validity_access as u8) << 2) | self.frequency_access as u8 + } + + fn write_payload(&self, writer: &mut T) -> Result<(), T::Error> { + writer.write_u8(ClassSpecificACInterfaceDescriptorSubtype::ClockSource as u8)?; // bDescriptorSubtype + writer.write_u8(self.id)?; // bClockId + writer.write_u8(self.bm_attributes())?; // bmAttributes + writer.write_u8(self.bm_controls())?; // bmControls + writer.write_u8(self.assoc_terminal)?; // bAssocTerminal + writer.write_u8(self.string)?; // iClockSource + + Ok(()) + } +} + +impl Descriptor for ClockSource { + const MAX_SIZE: usize = 8; + fn size(&self) -> u8 { + Self::MAX_SIZE as u8 + } + + fn write(&self, writer: &mut T) -> Result<(), T::Error> { + writer.write_u8(self.size())?; + writer.write_u8(ClassSpecificDescriptorType::Interface as u8)?; + self.write_payload(writer) + } + + fn write_descriptor<'w, 'd>( + &self, + writer: &'w mut DescriptorWriter<'d>, + ) -> Result<(), DescriptorWriterError> { + let mut writer = + DescriptorWriterAdapter::new(writer, ClassSpecificDescriptorType::Interface); + self.write_payload(&mut writer) + } +} + +#[derive(Clone, Debug)] +pub struct InputTerminal { + pub id: u8, + pub terminal_type: TerminalType, + pub assoc_terminal: u8, + pub clock_source: u8, + pub num_channels: u8, + pub channel_config: ChannelConfig, + pub channel_names: u8, + pub copy_protect_control: AccessControl, + pub connector_control: AccessControl, + pub overload_control: AccessControl, + pub cluster_control: AccessControl, + pub underflow_control: AccessControl, + pub overflow_control: AccessControl, + pub phantom_power_control: AccessControl, + pub string: u8, +} + +impl InputTerminal { + fn write_payload(&self, writer: &mut T) -> Result<(), T::Error> { + writer.write_u8(ClassSpecificACInterfaceDescriptorSubtype::InputTerminal as u8)?; // bDescriptorSubtype + writer.write_u8(self.id)?; // bTerminalID + writer.write_u16::(self.terminal_type as u16)?; // wTerminalType + writer.write_u8(self.assoc_terminal)?; // bAssocTerminal + writer.write_u8(self.clock_source)?; // bCSourceID + writer.write_u8(self.num_channels)?; // bNrChannels + writer.write(&self.channel_config.into_bytes())?; + writer.write_u8(self.channel_names)?; + writer.write_u8( + self.copy_protect_control as u8 + | ((self.connector_control as u8) << 2) + | ((self.overload_control as u8) << 4) + | ((self.cluster_control as u8) << 6), + )?; + writer.write_u8( + self.underflow_control as u8 + | ((self.overflow_control as u8) << 2) + | ((self.phantom_power_control as u8) << 4), + )?; + writer.write_u8(self.string)?; + Ok(()) + } +} + +impl Descriptor for InputTerminal { + const MAX_SIZE: usize = 17; + fn size(&self) -> u8 { + Self::MAX_SIZE as u8 + } + fn write(&self, writer: &mut T) -> Result<(), T::Error> { + writer.write_u8(self.size())?; + writer.write_u8(ClassSpecificDescriptorType::Interface as u8)?; + self.write_payload(writer) + } + fn write_descriptor<'w, 'd>( + &self, + writer: &'w mut DescriptorWriter<'d>, + ) -> Result<(), DescriptorWriterError> { + let mut writer = + DescriptorWriterAdapter::new(writer, ClassSpecificDescriptorType::Interface); + + self.write_payload(&mut writer) + } +} + +#[derive(Clone, Debug)] +pub struct OutputTerminal { + pub id: u8, + pub terminal_type: TerminalType, + pub assoc_terminal: u8, + pub source_id: u8, + pub clock_source: u8, + pub copy_protect_control: AccessControl, + pub connector_control: AccessControl, + pub overload_control: AccessControl, + pub underflow_control: AccessControl, + pub overflow_control: AccessControl, + pub string: u8, +} + +impl OutputTerminal { + fn write_payload(&self, writer: &mut T) -> Result<(), T::Error> { + writer.write_u8(ClassSpecificACInterfaceDescriptorSubtype::OutputTerminal as u8)?; // bDescriptorSubtype + writer.write_u8(self.id)?; // bTerminalID + writer.write_u16::(self.terminal_type as u16)?; // wTerminalType + writer.write_u8(self.assoc_terminal)?; // bAssocTerminal + writer.write_u8(self.source_id)?; // bSourceID + writer.write_u8(self.clock_source)?; // bCSourceID + writer.write_u8( + self.copy_protect_control as u8 + | ((self.connector_control as u8) << 2) + | ((self.overload_control as u8) << 4) + | ((self.underflow_control as u8) << 6), + )?; + writer.write_u8(self.overflow_control as u8)?; + writer.write_u8(self.string)?; // iTerminal + Ok(()) + } +} + +impl Descriptor for OutputTerminal { + const MAX_SIZE: usize = 12; + fn size(&self) -> u8 { + Self::MAX_SIZE as u8 + } + fn write(&self, writer: &mut T) -> Result<(), T::Error> { + writer.write_u8(self.size())?; + writer.write_u8(ClassSpecificDescriptorType::Interface as u8)?; + self.write_payload(writer) + } + fn write_descriptor<'w, 'd>( + &self, + writer: &'w mut DescriptorWriter<'d>, + ) -> Result<(), DescriptorWriterError> { + let mut writer = + DescriptorWriterAdapter::new(writer, ClassSpecificDescriptorType::Interface); + + self.write_payload(&mut writer) + } +} + +#[derive(Clone, Debug)] +pub enum Terminal { + Input(InputTerminal), + Output(OutputTerminal), +} + +impl Descriptor for Terminal { + const MAX_SIZE: usize = if InputTerminal::MAX_SIZE > OutputTerminal::MAX_SIZE { + InputTerminal::MAX_SIZE + } else { + OutputTerminal::MAX_SIZE + }; + fn size(&self) -> u8 { + match self { + Self::Input(t) => t.size(), + Self::Output(t) => t.size(), + } + } + fn write(&self, writer: &mut T) -> Result<(), T::Error> { + match self { + Self::Input(t) => t.write(writer), + Self::Output(t) => t.write(writer), + } + } + fn write_descriptor<'w, 'd>( + &self, + writer: &'w mut DescriptorWriter<'d>, + ) -> Result<(), DescriptorWriterError> { + match self { + Self::Input(t) => t.write_descriptor(writer), + Self::Output(t) => t.write_descriptor(writer), + } + } +} + +#[derive(Clone, Debug)] +pub struct ClockSelector { + pub id: u8, + pub n_sources: u8, + pub sources: [u8; MAX_SOURCES], // baCSourceID[] + pub selector_access: AccessControl, + pub string: u8, // iClockSelector +} + +impl ClockSelector { + fn write_payload(&self, writer: &mut T) -> Result<(), T::Error> { + writer.write_u8(ClassSpecificACInterfaceDescriptorSubtype::ClockSelector as u8)?; // bDescriptorSubtype + writer.write_u8(self.id)?; // bClockID + writer.write_u8(self.n_sources)?; // bNrInPins + writer.write(&self.sources[0..(self.n_sources as usize)])?; + writer.write_u8(self.selector_access as u8)?; // bmControls (CX_CLOCK_SELECTOR) + writer.write_u8(self.string)?; // iClockSelector (last byte) + Ok(()) + } +} + +impl Descriptor for ClockSelector { + const MAX_SIZE: usize = 7 + N; + fn size(&self) -> u8 { + 7 + self.n_sources + } + fn write(&self, writer: &mut T) -> Result<(), T::Error> { + writer.write_u8(self.size())?; + writer.write_u8(ClassSpecificDescriptorType::Interface as u8)?; + self.write_payload(writer) + } + fn write_descriptor<'w, 'd>( + &self, + writer: &'w mut DescriptorWriter<'d>, + ) -> Result<(), DescriptorWriterError> { + let mut writer = + DescriptorWriterAdapter::new(writer, ClassSpecificDescriptorType::Interface); + self.write_payload(&mut writer) + } +} + +#[derive(Clone, Debug)] +pub struct ClockMultiplier { + pub id: u8, + pub source_id: u8, + pub numerator_access: AccessControl, + pub denominator_access: AccessControl, + pub string: u8, // iClockMultiplier +} + +impl ClockMultiplier { + fn bm_controls(&self) -> u8 { + (self.numerator_access as u8) | ((self.denominator_access as u8) << 2) + } + fn write_payload(&self, writer: &mut T) -> Result<(), T::Error> { + writer.write_u8(ClassSpecificACInterfaceDescriptorSubtype::ClockMultiplier as u8)?; // bDescriptorSubtype + writer.write_u8(self.id)?; // bClockID + writer.write_u8(self.source_id)?; // bCSourceID + writer.write_u8(self.bm_controls())?; // bmControls + writer.write_u8(self.string)?; // iClockMultiplier + Ok(()) + } +} + +impl Descriptor for ClockMultiplier { + const MAX_SIZE: usize = 7; + fn size(&self) -> u8 { + Self::MAX_SIZE as u8 + } + fn write(&self, writer: &mut T) -> Result<(), T::Error> { + writer.write_u8(self.size())?; + writer.write_u8(ClassSpecificDescriptorType::Interface as u8)?; + self.write_payload(writer) + } + fn write_descriptor<'w, 'd>( + &self, + writer: &'w mut DescriptorWriter<'d>, + ) -> Result<(), DescriptorWriterError> { + let mut writer = + DescriptorWriterAdapter::new(writer, ClassSpecificDescriptorType::Interface); + + self.write_payload(&mut writer) + } +} +// Feature Unit's size depends on the number of channels in its source node, which we don't have a way +// to look up with the current API. Ignore and leave unimplemented for now. +// +// pub struct FeatureUnit { +// pub id: u8, +// pub source_id: u8, +// pub controls: [FeatureControls; MAX_CONTROLS], // bmaControls[] (N == channels + 1) +// pub n_controls: u8, +// pub string: u8, // iFeature +// } + +// impl Descriptor for FeatureUnit { +// const MAX_SIZE: usize = 6 + 4 * N; +// fn size(&self) -> u8 { +// 6 + 4 * (self.n_controls as u8) +// } + +// fn write(&self, buf: &mut [u8]) -> Result { +// let mut cur = Cursor::new(buf); +// cur.write_u8(self.size())?; // bLength +// cur.write_u8(ClassSpecificDescriptorType::Interface as u8)?; // bDescriptorType +// cur.write_u8(ClassSpecificACInterfaceDescriptorSubtype::FeatureUnit as u8)?; // bDescriptorSubtype +// cur.write_u8(self.id)?; // bUnitID +// cur.write_u8(self.source_id)?; // bSourceID + +// // bmaControls[i] as LE u32 +// for v in self.controls.iter() { +// cur.write(&v.bytes)?; +// } + +// cur.write_u8(self.string)?; // iFeature +// assert_eq!(cur.position(), self.size() as usize); +// Ok(cur.position()) +// } +// } + +// This implementation is not correct due to how pins/channels/mixer channels are computed +// it needs to know more about the rest of the devices +// pub struct MixerUnit { +// pub id: u8, +// pub sources: [u8; MAX_SOURCES], // baSourceID[] +// pub n_sources: u8, +// pub num_channels: u8, // bNrChannels +// pub channel_config: ChannelConfig, // bmChannelConfig (u32) +// pub channel_names: u8, // iChannelNames +// pub mixer_controls: [u8; MAX_CONTROLS], // bmMixerControls[] +// pub n_controls: u8, +// pub cluster_control: AccessControl, +// pub underflow_control: AccessControl, +// pub overflow_control: AccessControl, +// pub latency_control: AccessControl, +// pub string: u8, // iMixer +// } + +// impl MixerUnit { +// fn bm_controls(&self) -> u8 { +// (self.cluster_control as u8) +// | ((self.underflow_control as u8) << 2) +// | ((self.overflow_control as u8) << 4) +// | ((self.latency_control as u8) << 6) +// } +// } + +// impl Descriptor for MixerUnit { +// const MAX_SIZE: usize = 13 + MS + MC; +// fn size(&self) -> u8 { +// 13 + self.n_sources as u8 + self.n_controls as u8 +// } + +// fn write(&self, buf: &mut [u8]) -> Result { +// let mut cur = Cursor::new(buf); +// cur.write_u8(self.size())?; // bLength +// cur.write_u8(ClassSpecificDescriptorType::Interface as u8)?; // bDescriptorType +// cur.write_u8(ClassSpecificACInterfaceDescriptorSubtype::MixerUnit as u8)?; // bDescriptorSubtype +// cur.write_u8(self.id)?; // bUnitID +// cur.write_u8(self.n_sources as u8)?; // bNrInPins +// cur.write(&self.sources[0..(self.n_sources as usize)])?; // baSourceID[] + +// cur.write_u8(self.num_channels)?; // bNrChannels +// cur.write(&self.channel_config.bytes)?; // bmChannelConfig (already u32 LE) + +// cur.write_u8(self.channel_names)?; // iChannelNames + +// cur.write(&self.mixer_controls[0..(self.n_controls as usize)])?; // bmMixerControls[] + +// cur.write_u8(self.bm_controls())?; // bmControls +// cur.write_u8(self.string)?; // iMixer +// assert_eq!(cur.position(), self.size() as usize); +// Ok(cur.position()) +// } +// } + +#[derive(Clone, Debug)] +pub struct SelectorUnit { + pub id: u8, + pub sources: [u8; MAX_SOURCES], // baSourceID[] + pub n_sources: u8, + pub selector_control: AccessControl, + pub string: u8, // iSelector +} + +impl SelectorUnit { + fn bm_controls(&self) -> u8 { + self.selector_control as u8 + } + fn write_payload(&self, writer: &mut T) -> Result<(), T::Error> { + writer.write_u8(ClassSpecificACInterfaceDescriptorSubtype::SelectorUnit as u8)?; // bDescriptorSubtype + writer.write_u8(self.id)?; // bUnitID + writer.write_u8(self.n_sources)?; // bNrInPins + writer.write(&self.sources[0..(self.n_sources as usize)])?; + writer.write_u8(self.bm_controls())?; // bmControls + writer.write_u8(self.string)?; // iSelector + Ok(()) + } +} + +impl Descriptor for SelectorUnit { + const MAX_SIZE: usize = 7 + N; + fn size(&self) -> u8 { + 7 + self.n_sources + } + fn write(&self, writer: &mut T) -> Result<(), T::Error> { + writer.write_u8(self.size())?; + writer.write_u8(ClassSpecificDescriptorType::Interface as u8)?; + self.write_payload(writer) + } + fn write_descriptor<'w, 'd>( + &self, + writer: &'w mut DescriptorWriter<'d>, + ) -> Result<(), DescriptorWriterError> { + let mut writer = + DescriptorWriterAdapter::new(writer, ClassSpecificDescriptorType::Interface); + + self.write_payload(&mut writer) + } +} + +#[derive(Clone, Debug)] +pub struct ProcessingUnit { + pub id: u8, + pub process_type: u16, // wProcessType + pub sources: [u8; MAX_SOURCES], // baSourceID[] + pub num_channels: u8, // bNrChannels + pub n_sources: u8, + pub channel_config: ChannelConfig, // bmChannelConfig + pub channel_names: u8, // iChannelNames + pub enable_control: AccessControl, + pub mode_select_control: AccessControl, + pub cluster_control: AccessControl, + pub underflow_control: AccessControl, + pub overflow_control: AccessControl, + pub latency_control: AccessControl, + pub string: u8, // iProcessing +} + +impl ProcessingUnit { + fn bm_controls(&self) -> u16 { + (self.enable_control as u16) + | ((self.mode_select_control as u16) << 2) + | ((self.cluster_control as u16) << 4) + | ((self.underflow_control as u16) << 6) + | ((self.overflow_control as u16) << 8) + | ((self.latency_control as u16) << 10) + } + fn write_payload(&self, writer: &mut T) -> Result<(), T::Error> { + writer.write_u8(ClassSpecificACInterfaceDescriptorSubtype::ProcessingUnit as u8)?; // bDescriptorSubtype + writer.write_u8(self.id)?; // bUnitID + writer.write_u16::(self.process_type)?; // wProcessType + writer.write_u8(self.n_sources)?; // bNrInPins + writer.write(&self.sources[0..(self.n_sources as usize)])?; // baSourceID[] + writer.write_u8(self.num_channels)?; // bNrChannels + writer.write(&self.channel_config.into_bytes())?; // bmChannelConfig (already u32 LE) + writer.write_u8(self.channel_names)?; // iChannelNames + writer.write_u16::(self.bm_controls())?; // bmControls (PU is 2 bytes in UAC2) + writer.write_u8(self.string)?; // iProcessing + Ok(()) + } +} + +impl Descriptor for ProcessingUnit { + const MAX_SIZE: usize = 17 + N; + fn size(&self) -> u8 { + 17 + self.n_sources + } + fn write(&self, writer: &mut T) -> Result<(), T::Error> { + writer.write_u8(self.size())?; + writer.write_u8(ClassSpecificDescriptorType::Interface as u8)?; + self.write_payload(writer) + } + fn write_descriptor<'w, 'd>( + &self, + writer: &'w mut DescriptorWriter<'d>, + ) -> Result<(), DescriptorWriterError> { + let mut writer = + DescriptorWriterAdapter::new(writer, ClassSpecificDescriptorType::Interface); + + self.write_payload(&mut writer) + } +} + +#[derive(Clone, Debug)] +pub struct ExtensionUnit { + pub id: u8, + pub extension_code: u16, // wExtensionCode + pub sources: [u8; MAX_SOURCES], // baSourceID[] + pub n_sources: u8, + pub num_channels: u8, // bNrChannels + pub channel_config: ChannelConfig, // bmChannelConfig + pub channel_names: u8, // iChannelNames + pub enable_control: AccessControl, + pub cluster_control: AccessControl, + pub underflow_control: AccessControl, + pub overflow_control: AccessControl, + pub string: u8, // iExtension +} + +impl ExtensionUnit { + fn bm_controls(&self) -> u8 { + (self.enable_control as u8) + | ((self.cluster_control as u8) << 2) + | ((self.underflow_control as u8) << 4) + | ((self.overflow_control as u8) << 6) + } + fn write_payload(&self, writer: &mut T) -> Result<(), T::Error> { + writer.write_u8(ClassSpecificACInterfaceDescriptorSubtype::ExtensionUnit as u8)?; // bDescriptorSubtype + writer.write_u8(self.id)?; // bUnitID + writer.write_u16::(self.extension_code)?; // wExtensionCode + writer.write_u8(self.n_sources)?; // bNrInPins + writer.write(&self.sources[0..(self.n_sources as usize)])?; // baSourceID[] + writer.write_u8(self.num_channels)?; // bNrChannels + writer.write(&self.channel_config.into_bytes())?; // bmChannelConfig + writer.write_u8(self.channel_names)?; // iChannelNames + writer.write_u8(self.bm_controls())?; // bmControls (XU is 1 byte in UAC2) + writer.write_u8(self.string)?; // iExtension + Ok(()) + } +} + +impl Descriptor for ExtensionUnit { + const MAX_SIZE: usize = 16 + N; + fn size(&self) -> u8 { + 16 + self.n_sources + } + fn write(&self, writer: &mut T) -> Result<(), T::Error> { + writer.write_u8(self.size())?; + writer.write_u8(ClassSpecificDescriptorType::Interface as u8)?; + self.write_payload(writer) + } + fn write_descriptor<'w, 'd>( + &self, + writer: &'w mut DescriptorWriter<'d>, + ) -> Result<(), DescriptorWriterError> { + let mut writer = + DescriptorWriterAdapter::new(writer, ClassSpecificDescriptorType::Interface); + self.write_payload(&mut writer) + } +} +// Effect unit is also variable based on its source node, leave unimplemented for now. +// +// pub struct EffectUnit { +// pub id: u8, +// pub effect_type: u16, // wEffectType +// pub source_id: u8, // bSourceID +// pub controls: [u32; MAX_CONTROLS], // bmaControls[] (N == channels + 1) +// pub n_controls: u8, +// pub string: u8, // iEffect +// } + +// impl Descriptor for EffectUnit { +// const MAX_SIZE: usize = 16 + 4 * N; +// fn size(&self) -> u8 { +// 16 + 4 * self.n_controls +// } + +// fn write(&self, buf: &mut [u8]) -> Result { +// let mut cur = Cursor::new(buf); +// cur.write_u8(self.size())?; // bLength +// cur.write_u8(ClassSpecificDescriptorType::Interface as u8)?; // bDescriptorType +// cur.write_u8(ClassSpecificACInterfaceDescriptorSubtype::EffectUnit as u8)?; // bDescriptorSubtype +// cur.write_u8(self.id)?; // bUnitID +// cur.write_u16::(self.effect_type)?; // wEffectType +// cur.write_u8(self.source_id)?; // bSourceID +// for v in self.controls.iter() { +// cur.write_u32::(*v)?; +// } +// cur.write_u8(self.string)?; // iEffect +// assert_eq!(cur.position(), self.size() as usize); +// Ok(cur.position()) +// } +// } + +#[derive(Clone, Debug)] +/// Enum covering basic sized audio class descriptors for building the Class-Specific +/// AC Interface descriptor. Dynamically sized descriptors are not supported yet. +pub enum AudioClassDescriptor { + ClockSource(ClockSource), + ClockMultiplier(ClockMultiplier), + InputTerminal(InputTerminal), + OutputTerminal(OutputTerminal), +} + +impl AudioClassDescriptor { + pub fn size(&self) -> u8 { + match self { + AudioClassDescriptor::ClockSource(cs) => cs.size(), + AudioClassDescriptor::ClockMultiplier(cm) => cm.size(), + AudioClassDescriptor::InputTerminal(it) => it.size(), + AudioClassDescriptor::OutputTerminal(ot) => ot.size(), + } + } + pub fn write(&self, writer: &mut T) -> Result<(), T::Error> { + match self { + AudioClassDescriptor::ClockSource(cs) => cs.write(writer), + AudioClassDescriptor::ClockMultiplier(cm) => cm.write(writer), + AudioClassDescriptor::InputTerminal(it) => it.write(writer), + AudioClassDescriptor::OutputTerminal(ot) => ot.write(writer), + } + } +} + +impl From for AudioClassDescriptor { + fn from(cs: ClockSource) -> Self { + AudioClassDescriptor::ClockSource(cs) + } +} + +impl From for AudioClassDescriptor { + fn from(cm: ClockMultiplier) -> Self { + AudioClassDescriptor::ClockMultiplier(cm) + } +} + +impl From for AudioClassDescriptor { + fn from(it: InputTerminal) -> Self { + AudioClassDescriptor::InputTerminal(it) + } +} +impl From for AudioClassDescriptor { + fn from(ot: OutputTerminal) -> Self { + AudioClassDescriptor::OutputTerminal(ot) + } +} +impl From for AudioClassDescriptor { + fn from(t: Terminal) -> Self { + match t { + Terminal::Input(it) => AudioClassDescriptor::InputTerminal(it), + Terminal::Output(ot) => AudioClassDescriptor::OutputTerminal(ot), + } + } +} + +pub struct AudioClassInterfaceDescriptor { + inner: [AudioClassDescriptor; NUM_DESCRIPTORS], + category: FunctionCode, +} + +impl AudioClassInterfaceDescriptor { + pub fn new(inner: [AudioClassDescriptor; N], category: FunctionCode) -> Self { + Self { inner, category } + } + /// Total length of the interface descriptor and all its associated descriptors. + /// wTotalLength in the Class-Specific AC Interface Header + pub fn total_length(&self) -> u16 { + 9 + self + .inner + .iter() + .map(|desc| desc.size() as u16) + .sum::() + } + fn write_header(&self, writer: &mut T) -> Result<(), T::Error> { + let total_length = self.total_length(); + writer.write_u8(9)?; // bLength + writer.write_u8(ClassSpecificDescriptorType::Interface as u8)?; // bDescriptorType + writer.write_u8(ClassSpecificACInterfaceDescriptorSubtype::Header as u8)?; // bDescriptorSubtype + writer.write_u8(0)?; // bcdADC msd + writer.write_u8(2)?; // bcdADC lsd + writer.write_u8(self.category as u8)?; // bCategory + writer.write_u16::(total_length)?; // wTotalLength + writer.write_u8(0)?; // bmControls + Ok(()) + } + pub fn write(&self, writer: &mut T) -> Result<(), T::Error> { + self.write_header(writer)?; + for desc in &self.inner { + desc.write(writer)?; + } + Ok(()) + } +} + +#[cfg(test)] +extern crate std; +#[cfg(test)] +mod tests { + use super::*; + use crate::cursor::Cursor; + use std::{print, println}; + #[test] + fn test_clock_source() { + let cs = ClockSource { + id: 8, + clock_type: ClockType::InternalFixed, + sof_sync: false, + frequency_access: AccessControl::ReadOnly, + validity_access: AccessControl::ReadOnly, + assoc_terminal: 6, + string: 10, + }; + let mut buf = [0u8; ClockSource::MAX_SIZE]; + let mut cur = Cursor::new(&mut buf[..]); + let len = cs.size(); + cs.write(&mut cur).unwrap(); + let descriptor = &buf[..len as usize]; + assert_eq!( + descriptor, + &[ + 8, // bLength + 0x24, // CS_INTERFACE + 0x0a, // CLOCK_SOURCE + 8, // bClockId + 1, // bmAttributes + 5, // bmControls + 6, // bAssocTerminal + 10, // iClockSource + ] + ) + } + #[test] + fn test_clock_selector() { + let cs = ClockSelector { + id: 8, + n_sources: 3, + sources: [2, 3, 4, 0], + selector_access: AccessControl::Programmable, + string: 10, + }; + let mut buf = [0u8; ClockSelector::<4>::MAX_SIZE]; + let mut cur = Cursor::new(&mut buf[..]); + let len = cs.size(); + cs.write(&mut cur).unwrap(); + let descriptor = &buf[..len as usize]; + assert_eq!( + descriptor, + &[ + 10, // bLength + 0x24, // CS_INTERFACE + 0x0b, // CLOCK_SELECTOR + 8, // bClockId + 3, // bNrInPins + 2, // baCSourceId + 3, 4, 3, // bmControls + 10, // iClockSelector + ] + ) + } + #[test] + fn test_clock_multiplier() { + let cm = ClockMultiplier { + id: 8, + source_id: 10, + numerator_access: AccessControl::Programmable, + denominator_access: AccessControl::ReadOnly, + string: 20, + }; + let mut buf = [0u8; ClockMultiplier::MAX_SIZE]; + let mut cur = Cursor::new(&mut buf[..]); + let len = cm.size(); + + cm.write(&mut cur).unwrap(); + let descriptor = &buf[0..len as usize]; + assert_eq!( + descriptor, + &[ + 7, // bLength + 0x24, // CS_INTERFACE + 0x0c, // CLOCK_MULTIPLIER + 8, // bClockId + 10, // bCSourceId + 7, // bmControls + 20, // iClockMultiplier + ] + ) + } + #[test] + fn test_input_terminal() { + let it = InputTerminal { + id: 8, + terminal_type: TerminalType::InMicrophone, + assoc_terminal: 10, + clock_source: 12, + num_channels: 2, + channel_config: ChannelConfig::new() + .with_front_left(true) + .with_front_right(true), + channel_names: 14, + copy_protect_control: AccessControl::NotPresent, + connector_control: AccessControl::Programmable, + overload_control: AccessControl::ReadOnly, + cluster_control: AccessControl::Programmable, + underflow_control: AccessControl::ReadOnly, + overflow_control: AccessControl::ReadOnly, + phantom_power_control: AccessControl::NotPresent, + string: 20, + }; + let mut buf = [0u8; InputTerminal::MAX_SIZE]; + let mut cur = Cursor::new(&mut buf[..]); + let len = it.size(); + + it.write(&mut cur).unwrap(); + let descriptor = &buf[..len as usize]; + assert_eq!( + descriptor, + &[ + 17, // bLength + 0x24, // CS_INTERFACE + 0x02, // INPUT_TERMINAL + 8, + 0x01, // wTerminalType (u16) + 0x02, + 10, // bAssocTerminal + 12, // bCSourceId + 2, // bNrChannels + 3, // bmChannelConfig (u32) + 0, + 0, + 0, + 14, // iChannelNames + (3 << 2) | (1 << 4) | (3 << 6), // bmControls (u16) + (1 << 0) | (1 << 2), + 20, // iTerminal + ] + ) + } + #[test] + fn test_output_terminal() { + let ot = OutputTerminal { + id: 8, + terminal_type: TerminalType::OutSpeaker, + assoc_terminal: 10, + source_id: 11, + clock_source: 12, + copy_protect_control: AccessControl::NotPresent, + connector_control: AccessControl::Programmable, + overload_control: AccessControl::ReadOnly, + underflow_control: AccessControl::ReadOnly, + overflow_control: AccessControl::ReadOnly, + string: 20, + }; + let mut buf = [0u8; OutputTerminal::MAX_SIZE]; + let mut cur = Cursor::new(&mut buf[..]); + let len = ot.size(); + + ot.write(&mut cur).unwrap(); + let descriptor = &buf[..len as usize]; + assert_eq!( + descriptor, + &[ + 12, // bLength + 0x24, // CS_INTERFACE + 0x03, // OUTPUT_TERMINAL + 8, // bTerminalId + 01, // wTerminalType (u16) + 03, + 10, // bAssocTerminal + 11, // bSourceId + 12, // bCSourceId + (3 << 2) | (1 << 4) | (1 << 6), // bmControls (u16) + 1, + 20, // iTerminal + ] + ) + } + #[test] + fn test_selector_unit() { + let su = SelectorUnit { + id: 8, + n_sources: 3, + sources: [2, 3, 4, 0], + selector_control: AccessControl::Programmable, + string: 20, + }; + let mut buf = [0u8; SelectorUnit::<4>::MAX_SIZE]; + let mut cur = Cursor::new(&mut buf[..]); + let len = su.size(); + + su.write(&mut cur).unwrap(); + let descriptor = &buf[..len as usize]; + assert_eq!( + descriptor, + &[ + 10, // bLength + 0x24, // CS_INTERFACE + 0x05, // SELECTOR_UNIT + 8, // bUnitId + 3, // bNrInPins + 2, // baSourceId[3] + 3, 4, 3, // bmControls + 20, // iSelector + ] + ) + } + + /// Write a minimal PCAP file containing a single synthetic USB control + /// transfer that returns the provided descriptor bytes. + /// + /// The resulting file can be opened in Wireshark and expanded under: + /// USB → URB → Descriptors + /// + /// Note: this is slop + pub fn write_usb_descriptor_pcap( + path: &str, + descriptor_type: u8, // e.g. 0x02 = Configuration + descriptor_index: u8, + descriptor_bytes: &[u8], + ) -> std::io::Result<()> { + use std::fs::File; + use std::io::{self, Write}; + use std::time::{SystemTime, UNIX_EPOCH}; + use std::vec::Vec; + + const DLT_USBPCAP: u32 = 249; + + // URB function codes (Windows). 0x0008 is URB_FUNCTION_CONTROL_TRANSFER. + const URB_FUNCTION_CONTROL_TRANSFER: u16 = 0x0008; + + // USBPcap transfer types + const USBPCAP_TRANSFER_CONTROL: u8 = 2; + + // USBPcap control stages + const USBPCAP_CONTROL_STAGE_SETUP: u8 = 0; + const USBPCAP_CONTROL_STAGE_COMPLETE: u8 = 3; + + let mut f = File::create(path)?; + + // PCAP GLOBAL HEADER (little endian) + f.write_all(&[ + 0xd4, + 0xc3, + 0xb2, + 0xa1, // magic + 0x02, + 0x00, // version major + 0x04, + 0x00, // version minor + 0x00, + 0x00, + 0x00, + 0x00, // thiszone + 0x00, + 0x00, + 0x00, + 0x00, // sigfigs + 0xff, + 0xff, + 0x00, + 0x00, // snaplen + (DLT_USBPCAP & 0xff) as u8, + ((DLT_USBPCAP >> 8) & 0xff) as u8, + ((DLT_USBPCAP >> 16) & 0xff) as u8, + ((DLT_USBPCAP >> 24) & 0xff) as u8, + ])?; + + let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); + let ts_sec = now.as_secs() as u32; + let ts_usec = now.subsec_micros(); + + // USB SETUP packet for GET_DESCRIPTOR + let w_value = ((descriptor_type as u16) << 8) | (descriptor_index as u16); + let w_length = descriptor_bytes.len() as u16; + + let setup_pkt = [ + 0x80, // bmRequestType: device->host, standard, device + 0x06, // bRequest: GET_DESCRIPTOR + (w_value & 0xff) as u8, + (w_value >> 8) as u8, + 0x00, + 0x00, // wIndex + (w_length & 0xff) as u8, + (w_length >> 8) as u8, + ]; + + // Helper to write one USBPcap packet record + fn write_usbpcap_record( + f: &mut File, + ts_sec: u32, + ts_usec: u32, + // USBPcap base header fields + irp_id: u64, + status: u32, + function: u16, + info: u8, + bus: u16, + device: u16, + endpoint: u8, + transfer: u8, + stage: u8, + payload: &[u8], + ) -> io::Result<()> { + // USBPCAP_BUFFER_PACKET_HEADER is packed(1) and begins with USHORT headerLen. + // For control transfers, header is: base header + stage byte. + // base header size = 2+8+4+2+1+2+2+1+1+4 = 27 bytes + // + stage = 1 => 28 bytes total headerLen + let header_len: u16 = 28; + let data_len: u32 = payload.len() as u32; + + let mut pkt = Vec::with_capacity(header_len as usize + payload.len()); + + // --- USBPCAP_BUFFER_PACKET_HEADER --- + pkt.extend_from_slice(&header_len.to_le_bytes()); // USHORT headerLen + pkt.extend_from_slice(&irp_id.to_le_bytes()); // UINT64 irpId + pkt.extend_from_slice(&status.to_le_bytes()); // USBD_STATUS status (UINT32) + pkt.extend_from_slice(&function.to_le_bytes()); // USHORT function + pkt.push(info); // UCHAR info + pkt.extend_from_slice(&bus.to_le_bytes()); // USHORT bus + pkt.extend_from_slice(&device.to_le_bytes()); // USHORT device + pkt.push(endpoint); // UCHAR endpoint (MSB = IN) + pkt.push(transfer); // UCHAR transfer (2 = control) + pkt.extend_from_slice(&data_len.to_le_bytes()); // UINT32 dataLength + + // --- USBPCAP_BUFFER_CONTROL_HEADER extra field --- + pkt.push(stage); // UCHAR stage + + debug_assert_eq!(pkt.len(), header_len as usize); + + // --- transfer data --- + pkt.extend_from_slice(payload); + + // PCAP per-record header + let incl_len = pkt.len() as u32; + f.write_all(&ts_sec.to_le_bytes())?; + f.write_all(&ts_usec.to_le_bytes())?; + f.write_all(&incl_len.to_le_bytes())?; + f.write_all(&incl_len.to_le_bytes())?; + f.write_all(&pkt)?; + Ok(()) + } + + // Use consistent IDs so Wireshark can correlate them. + let irp_id: u64 = 0x1111_2222_3333_4444; + let bus: u16 = 1; + let device: u16 = 1; + + // 1) SETUP stage (host -> device), carries SETUP bytes as payload. + write_usbpcap_record( + &mut f, + ts_sec, + ts_usec, + irp_id, + 0, // status + URB_FUNCTION_CONTROL_TRANSFER, + 0x00, // info: FDO->PDO (request) + bus, + device, + 0x00, // EP0 OUT + USBPCAP_TRANSFER_CONTROL, + USBPCAP_CONTROL_STAGE_SETUP, + &setup_pkt, // payload = 8-byte setup + )?; + + let mut config_descriptor = Vec::with_capacity(9 + 8 + 9 + descriptor_bytes.len()); + // Config Descriptor + config_descriptor.push(0x09); // bLength + config_descriptor.push(0x02); // bDescriptorType = CONFIGURATION + config_descriptor.write_all(&(9 + 8 + 9 + descriptor_bytes.len() as u16).to_le_bytes())?; + config_descriptor.push(0x01); // bNumInterfaces + config_descriptor.push(0x01); // bConfigurationValue + config_descriptor.push(0x00); // iConfiguration + config_descriptor.push(0xC0); // bmAttributes = self-powered + config_descriptor.push(0x32); // bMaxPower = 100mA + + // Interface Association Descriptor + config_descriptor.push(0x08); // bLength + config_descriptor.push(0x0B); // bDescriptorType = INTERFACE ASSOCIATION + config_descriptor.push(0x00); // bFirstInterface + config_descriptor.push(0x01); // bInterfaceCount + config_descriptor.push(0x01); // bFunctionClass = AUDIO + config_descriptor.push(0x03); // bFunctionSubClass = AUDIO_STREAMING + config_descriptor.push(0x00); // bFunctionProtocol = NONE + config_descriptor.push(0x00); // iFunction + + // 'Standard' Audio Class Interface Descriptor + config_descriptor.push(0x09); // bLength + config_descriptor.push(0x04); // bDescriptorType = INTERFACE + config_descriptor.push(0x00); // bInterfaceNumber + config_descriptor.push(0x00); // bAlternateSetting + config_descriptor.push(0x02); // bNumEndpoints + config_descriptor.push(0x01); // bInterfaceClass = AUDIO + config_descriptor.push(0x01); // bInterfaceSubClass = AUDIO_CONTROL + config_descriptor.push(0x20); // bInterfaceProtocol = NONE + config_descriptor.push(0x00); // iInterface + + config_descriptor.write_all(descriptor_bytes)?; + + // 2) COMPLETE stage (device -> host), carries descriptor bytes as payload. + write_usbpcap_record( + &mut f, + ts_sec, + ts_usec.wrapping_add(1), // tiny delta + irp_id, + 0, // status + URB_FUNCTION_CONTROL_TRANSFER, + 0x01, // info: PDO->FDO (response) + bus, + device, + 0x80, // EP0 IN + USBPCAP_TRANSFER_CONTROL, + USBPCAP_CONTROL_STAGE_COMPLETE, + &config_descriptor, // payload = IN data (descriptors) + )?; + Ok(()) + } + + #[test] + fn test_ac_interface() { + let descriptors: [AudioClassDescriptor; _] = [ + ClockSource { + id: 1, + clock_type: ClockType::InternalFixed, + sof_sync: false, + frequency_access: AccessControl::NotPresent, + validity_access: AccessControl::NotPresent, + assoc_terminal: 0, + string: 0, + } + .into(), + InputTerminal { + id: 2, + terminal_type: TerminalType::UsbStreaming, + assoc_terminal: 0, + clock_source: 1, + num_channels: 2, + channel_config: ChannelConfig::default_chans(2), + channel_names: 0, + copy_protect_control: AccessControl::NotPresent, + connector_control: AccessControl::NotPresent, + overload_control: AccessControl::NotPresent, + cluster_control: AccessControl::NotPresent, + underflow_control: AccessControl::NotPresent, + overflow_control: AccessControl::NotPresent, + phantom_power_control: AccessControl::NotPresent, + string: 0, + } + .into(), + OutputTerminal { + id: 3, + terminal_type: TerminalType::OutUndefined, + assoc_terminal: 0, + source_id: 2, + clock_source: 1, + copy_protect_control: AccessControl::NotPresent, + connector_control: AccessControl::NotPresent, + overload_control: AccessControl::NotPresent, + underflow_control: AccessControl::NotPresent, + overflow_control: AccessControl::NotPresent, + string: 0, + } + .into(), + OutputTerminal { + id: 4, + source_id: 5, + terminal_type: TerminalType::UsbStreaming, + assoc_terminal: 0, + clock_source: 1, + copy_protect_control: AccessControl::NotPresent, + connector_control: AccessControl::NotPresent, + overload_control: AccessControl::NotPresent, + underflow_control: AccessControl::NotPresent, + overflow_control: AccessControl::NotPresent, + string: 0, + } + .into(), + InputTerminal { + id: 5, + terminal_type: TerminalType::InUndefined, + assoc_terminal: 0, + clock_source: 1, + num_channels: 2, + channel_config: ChannelConfig::default_chans(2), + channel_names: 0, + copy_protect_control: AccessControl::NotPresent, + connector_control: AccessControl::NotPresent, + overload_control: AccessControl::NotPresent, + cluster_control: AccessControl::NotPresent, + underflow_control: AccessControl::NotPresent, + overflow_control: AccessControl::NotPresent, + phantom_power_control: AccessControl::NotPresent, + string: 0, + } + .into(), + ]; + let ac = AudioClassInterfaceDescriptor::new(descriptors, FunctionCode::Undefined); + let mut buf = [0u8; 1024]; + let len = { + let mut cur = Cursor::new(&mut buf[..]); + ac.write(&mut cur).unwrap(); + cur.position() + }; + let bytes = &buf[..len]; + for (i, b) in bytes.iter().enumerate() { + if i.is_multiple_of(16) { + println!(); + } + print!("{:02x} ", b); + } + println!(); + write_usb_descriptor_pcap("./uac2.pcap", 0x02, 0, bytes); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..ab025dd --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,285 @@ +#![no_std] +#![allow(dead_code)] + +mod constants; +mod cursor; +mod descriptors; + +use constants::*; +use descriptors::*; + +use usb_device::control::{Recipient, Request, RequestType}; +use usb_device::device::DEFAULT_ALTERNATE_SETTING; +use usb_device::endpoint::{self, Endpoint, EndpointDirection, In, Out}; +use usb_device::{UsbDirection, class_prelude::*}; + +#[derive(Clone, Copy, Debug)] +pub enum Format { + /// Signed, 16 bits per subframe, little endian + S16le, + /// Signed, 24 bits per subframe, little endian + S24le, + /// Signed, 32 bits per subframe, little endian + S32le, +} + +/// Sampling rates that shall be supported by an steaming endpoint +#[derive(Debug)] +pub enum Rates<'a> { + /// A continuous range of sampling rates in samples/second defined by a + /// tuple including a minimum value and a maximum value. The maximum value + /// must be greater than the minimum value. + Continuous(u32, u32), + /// A set of discrete sampling rates in samples/second + Discrete(&'a [u32]), +} + +#[derive(Debug)] +pub struct StreamConfig<'a> { + format: Format, + channels: u8, + rates: Rates<'a>, + terminal_type: TerminalType, + /// ISO endpoint size calculated from format, channels and rates (may be + /// removed in future) + ep_size: u16, +} + +impl StreamConfig<'_> { + /// Create a stream configuration with one or more discrete sampling rates + /// indicated in samples/second. An input stream or an output stream will + /// have an Input Terminal or Output Terminal of Terminal Type + /// `terminal_type`, respectively. + pub fn new_discrete( + format: Format, + channels: u8, + rates: &'_ [u32], + terminal_type: TerminalType, + ) -> Result> { + let max_rate = rates.iter().max().unwrap(); + let ep_size = Self::ep_size(format, channels, *max_rate)?; + let rates = Rates::Discrete(rates); + Ok(StreamConfig { + format, + channels, + rates, + terminal_type, + ep_size, + }) + } + + /// Create a stream configuration with a continuous range of supported + /// sampling rates indicated in samples/second. An input stream or an output + /// stream will have an Input Terminal or Output Terminal of Terminal Type + /// `terminal_type`, respectively. + pub fn new_continuous( + format: Format, + channels: u8, + min_rate: u32, + max_rate: u32, + terminal_type: TerminalType, + ) -> Result> { + if min_rate >= max_rate { + return Err(Error::InvalidValue); + } + let ep_size = Self::ep_size(format, channels, max_rate)?; + let rates = Rates::Continuous(min_rate, max_rate); + Ok(StreamConfig { + format, + channels, + rates, + terminal_type, + ep_size, + }) + } + + /// calculate ISO endpoint size from format, channels and rates + fn ep_size(format: Format, channels: u8, max_rate: u32) -> Result { + let octets_per_frame = channels as u32 + * match format { + Format::S16le => 2, + Format::S24le => 3, + Format::S32le => 4, + }; + let ep_size = octets_per_frame * max_rate / 1000; + // if ep_size > MAX_ISO_EP_SIZE { + // return Err(Error::BandwidthExceeded); + // } + Ok(ep_size as u16) + } +} + +/// USB audio errors, including possible USB Stack errors +#[derive(Debug)] +pub enum Error { + InvalidValue, + BandwidthExceeded, + StreamNotInitialized, + UsbError(usb_device::UsbError), +} + +impl From for Error { + fn from(err: UsbError) -> Self { + Error::UsbError(err) + } +} +type Result = core::result::Result; + +struct AudioStream<'a, B: UsbBus, D: EndpointDirection> { + stream_config: StreamConfig<'a>, + interface: InterfaceNumber, + endpoint: Endpoint<'a, B, D>, + alt_setting: u8, +} + +impl AudioStream<'_, B, endpoint::In> { + fn input_terminal_desc(&self, id: u8, clock_source: u8) -> InputTerminal { + let channel_config = ChannelConfig::default_chans(self.stream_config.channels); + InputTerminal { + id, + terminal_type: TerminalType::UsbStreaming, + assoc_terminal: 0, + clock_source, + num_channels: self.stream_config.channels, + channel_config, + channel_names: 0, + copy_protect_control: AccessControl::NotPresent, + connector_control: AccessControl::NotPresent, + overload_control: AccessControl::NotPresent, + cluster_control: AccessControl::NotPresent, + underflow_control: AccessControl::NotPresent, + overflow_control: AccessControl::NotPresent, + phantom_power_control: AccessControl::NotPresent, + string: 0, + } + } +} + +impl AudioStream<'_, B, endpoint::Out> { + fn output_terminal_desc(&self, id: u8, source_id: u8, clock_source: u8) -> OutputTerminal { + OutputTerminal { + id, + terminal_type: TerminalType::UsbStreaming, + assoc_terminal: 0, + source_id, + clock_source, + copy_protect_control: AccessControl::NotPresent, + connector_control: AccessControl::NotPresent, + overload_control: AccessControl::NotPresent, + underflow_control: AccessControl::NotPresent, + overflow_control: AccessControl::NotPresent, + string: 0, + } + } +} + +pub struct AudioClass<'a, B: UsbBus> { + control_iface: InterfaceNumber, + input: Option>, + output: Option>, + function: FunctionCode, + clock_type: ClockType, + input_type: Option, + output_type: Option, +} + +impl AudioClass<'_, B> {} + +impl UsbClass for AudioClass<'_, B> { + fn get_configuration_descriptors( + &self, + writer: &mut DescriptorWriter, + ) -> usb_device::Result<()> { + // Build the necessary descriptors + // Clock Source - id 1 + // USB Input Terminal - id 2 + // Audio Output Terminal - id 3 + // USB Output Terminal - id 4 + // Audio Input Terminal - id 5 + let clock_source = ClockSource { + id: 1, + clock_type: self.clock_type, + sof_sync: false, + frequency_access: if self.clock_type == ClockType::InternalProgrammable { + AccessControl::Programmable + } else { + AccessControl::NotPresent + }, + validity_access: AccessControl::ReadOnly, + assoc_terminal: 0, + string: 0, + }; + let in_terminals = match &self.input { + Some(i) => Some(( + i.input_terminal_desc(2, 1), + OutputTerminal { + id: 3, + terminal_type: self.output_type.unwrap_or(TerminalType::OutUndefined), + assoc_terminal: 0, + source_id: 2, + clock_source: 1, + copy_protect_control: AccessControl::NotPresent, + connector_control: AccessControl::NotPresent, + overload_control: AccessControl::NotPresent, + underflow_control: AccessControl::NotPresent, + overflow_control: AccessControl::NotPresent, + string: 0, + }, + )), + None => None, + }; + let out_terminals = match &self.output { + Some(i) => Some(( + i.output_terminal_desc(4, 5, 1), + InputTerminal { + id: 5, + terminal_type: self.input_type.unwrap_or(TerminalType::InUndefined), + assoc_terminal: 0, + clock_source: 1, + num_channels: i.stream_config.channels, + channel_config: ChannelConfig::default_chans(i.stream_config.channels), + channel_names: 0, + copy_protect_control: AccessControl::NotPresent, + connector_control: AccessControl::NotPresent, + overload_control: AccessControl::NotPresent, + cluster_control: AccessControl::NotPresent, + underflow_control: AccessControl::NotPresent, + overflow_control: AccessControl::NotPresent, + phantom_power_control: AccessControl::NotPresent, + string: 0, + }, + )), + None => None, + }; + let n_interfaces = match (&self.input, &self.output) { + (Some(_), Some(_)) => 3, // two audio, one control + (Some(_), None) | (None, Some(_)) => 2, // one audio, one control + (None, None) => 1, // no audio (?!), one control + }; + writer.iad( + self.control_iface, + n_interfaces, + AUDIO, + FunctionSubclass::Undefined as u8, + FunctionProtocol::Version2 as u8, + None, + )?; + writer.interface( + self.control_iface, + AUDIO, + InterfaceSubclass::AudioControl as u8, + InterfaceProtocol::Version2 as u8, + )?; + + if let Some(terminals) = in_terminals { + terminals.0.write_descriptor(writer)?; + terminals.1.write_descriptor(writer)?; + } + if let Some(terminals) = out_terminals { + terminals.0.write_descriptor(writer)?; + terminals.1.write_descriptor(writer)?; + } + + Ok(()) + } +}