A deterministic, host-side USB 2.0 composite-device simulation implementing a HID keyboard-style interface and CDC-ACM control interface, with explicit protocol validation, descriptor handling, endpoint management, a replaceable HAL, virtual enumeration, negative testing, and reproducible CMake/CTest validation.
This repository is a portfolio-grade embedded-systems project built to demonstrate how a firmware-oriented USB device stack can be structured as a set of testable engineering layers rather than as one monolithic implementation.
The simulated device exposes:
- USB device state management
- Standard control requests
- 8-byte SETUP packet encoding/decoding
- Device, configuration, string, HID and HID-report descriptors
- HID class requests and deterministic reports
- CDC-ACM line-coding and control-line-state requests
- Endpoint configuration and packet-size validation
- A replaceable hardware-abstraction layer
- A deterministic virtual USB host and bus
- Positive, integration and negative tests
- Python validation utilities
- CMake/CTest build and CI automation
- Architecture, protocol, testing and design documentation
This is deliberately a host-side protocol simulation.
It does not claim physical USB electrical compliance, PHY timing validation, controller-register integration, DMA/cache behavior, or USB-IF certification. Those concerns belong behind the HAL boundary in a hardware-backed implementation.
The engineering boundary demonstrated by the project is:
USB request bytes
│
▼
SETUP packet decoder
│
▼
Control-transfer validation
│
├───────────────┐
▼ ▼
Standard requests Class requests
│ │
▼ ┌────┴────┐
Device state HID CDC-ACM
│ │ │
└──────────┴──────────┘
│
▼
Descriptor / endpoint
validation
│
▼
HAL boundary
│
▼
Virtual USB backend
The project therefore exercises several areas relevant to embedded C, firmware, USB protocol development, device drivers, validation engineering and systems programming.
| Engineering area | Demonstrated implementation |
|---|---|
| Embedded C | C11, fixed-width types, explicit status codes |
| USB protocol | SETUP packets, standard requests, class requests |
| Device state | Default → Addressed → Configured |
| Descriptors | Device, configuration, string and HID descriptors |
| HID | Report descriptor, input/output report handling |
| CDC-ACM | Line coding and control-line-state requests |
| Endpoint management | Direction/type/packet-size validation |
| Defensive programming | Null, length, state and capability checks |
| HAL architecture | Transport operations isolated behind usb_hal_t |
| Virtual validation | Deterministic host/bus simulation |
| Testing | 17 CTest cases including negative paths |
| Automation | CMake + CTest + GitHub Actions |
| Tooling | Python descriptor, trace and test-report utilities |
| Documentation | Architecture, protocol, design and testing notes |
┌─────────────────────────┐
│ Examples / Tests │
└────────────┬────────────┘
│
┌────────────▼────────────┐
│ Virtual USB Host │
│ enumeration + requests │
└────────────┬────────────┘
│
┌────────────▼────────────┐
│ USB Device Core │
│ state + control transfer │
└────────────┬────────────┘
│
┌──────────────────┴──────────────────┐
│ │
┌──────────▼──────────┐ ┌──────────▼──────────┐
│ Standard Requests │ │ Class Request Layer │
│ descriptors/state │ │ HID + CDC-ACM │
└──────────┬──────────┘ └──────────┬──────────┘
│ │
└──────────────────┬──────────────────┘
│
┌────────────▼────────────┐
│ Descriptor / Endpoint │
│ validation + management │
└────────────┬────────────┘
│
┌────────────▼────────────┐
│ USB HAL boundary │
└────────────┬────────────┘
│
┌────────────▼────────────┐
│ Deterministic simulator │
└─────────────────────────┘
| Module | Responsibility |
|---|---|
src/usb |
Device state, control dispatch, standard requests and descriptors |
src/common |
Shared validation and status handling |
usb/protocol |
Byte-level SETUP and transfer validation |
hid |
HID semantics and report representation |
cdc |
CDC-ACM semantics and functional descriptors |
include/hal |
Transport abstraction contract |
hal/simulation |
In-memory endpoint transport |
simulation/usb_host |
Host-side enumeration and class transactions |
simulation/usb_bus |
Bus/HAL attachment boundary |
tests |
Unit, integration and negative coverage |
tools/python |
Host-side validation/reporting utilities |
The virtual host performs a deterministic control-transfer sequence:
RESET / DEFAULT
│
├── GET_DESCRIPTOR(Device)
│
├── SET_ADDRESS(1)
│
▼
ADDRESSED
│
├── GET_DESCRIPTOR(Configuration, first 9 bytes)
│
├── GET_DESCRIPTOR(Configuration, full descriptor)
│
├── Validate descriptor chain
│
├── SET_CONFIGURATION(1)
│
▼
CONFIGURED
│
├── GET_CONFIGURATION
├── HID class request
└── CDC-ACM class request
The host intentionally retrieves the configuration descriptor header first, obtains wTotalLength, then requests the complete descriptor. This makes the simulation closer to a real host enumeration flow than simply reading a static blob.
The device models:
DEFAULT
│
│ SET_ADDRESS(1..127)
▼
ADDRESSED
│
│ SET_CONFIGURATION(1)
▼
CONFIGURED
│
│ SET_CONFIGURATION(0)
▼
ADDRESSED
SET_ADDRESS(0) returns the device to DEFAULT.
Configuration changes reset non-control endpoints and interface alternate settings.
The implementation rejects invalid state transitions instead of silently mutating device state.
USB control transfers begin with an 8-byte SETUP packet.
The protocol layer provides explicit little-endian conversion:
usb_status_t usb_setup_decode(const uint8_t raw[8],
usb_setup_packet_t *setup);
usb_status_t usb_setup_encode(const usb_setup_packet_t *setup,
uint8_t raw[8]);The control layer then validates:
- request type
- direction
- recipient
- request length
- OUT payload presence
- supported request classes
- destination-buffer capacity
The stack implements deterministic handling for:
GET_STATUS
SET_FEATURE
CLEAR_FEATURE
SET_ADDRESS
GET_DESCRIPTOR
GET_CONFIGURATION
SET_CONFIGURATION
GET_INTERFACE
SET_INTERFACE
Unsupported vendor/reserved request types are rejected.
Remote-wakeup state is modeled for the device recipient.
The configuration contains three interfaces:
Interface 0
└── HID keyboard-style interface
├── EP1 IN interrupt, 8 bytes
└── EP1 OUT interrupt, 1 byte
Interface 1
└── CDC-ACM control interface
└── EP2 IN interrupt, 8 bytes
Interface 2
└── CDC data interface
├── EP3 IN bulk, 64 bytes
└── EP3 OUT bulk, 64 bytes
The configuration descriptor advertises a total length of 100 bytes and three interfaces.
Descriptor-chain validation checks every descriptor boundary before accepting a descriptor stream.
The HID layer provides:
GET_REPORT
SET_REPORT
The deterministic report model contains:
- 63-byte keyboard-style HID report descriptor
- 8-byte input report
- 1-byte output report
The simulation deliberately keeps report behavior deterministic so that tests are reproducible.
The CDC control interface implements:
SET_LINE_CODING
GET_LINE_CODING
SET_CONTROL_LINE_STATE
Line coding is represented as:
typedef struct {
uint32_t baud;
uint8_t stop_bits;
uint8_t parity;
uint8_t data_bits;
} cdc_line_coding_t;Validation rejects:
- null payloads
- incorrect seven-byte payloads
- zero baud rates
- unsupported stop-bit values
- unsupported parity values
- invalid data-bit widths
- invalid control-line-state bits
The endpoint manager maintains independent IN and OUT state for each endpoint number.
Each endpoint records:
configured
enabled
direction
transfer type
maximum packet size
Configuration is validated before state mutation.
Packet checks reject transfers larger than the endpoint's maximum packet size.
Hardware-dependent operations are represented by:
typedef struct {
int (*init)(void);
int (*ep_write)(uint8_t endpoint,
const uint8_t *data,
size_t length);
int (*ep_read)(uint8_t endpoint,
uint8_t *data,
size_t capacity,
size_t *length);
} usb_hal_t;The simulation backend provides an in-memory endpoint transport.
The separation means a hardware controller adapter can replace the simulation backend without changing the protocol semantics.
The host-side simulator exercises the same public device-control API that a hardware controller adapter would feed.
The virtual bus provides the explicit HAL attachment boundary.
This keeps:
protocol semantics
separate from:
transport implementation
which is an important embedded-systems architectural boundary.
The project uses CTest as the test execution layer.
Current validation contains:
17 tests
17 passed
0 failed
100% pass rate
| Category | Coverage |
|---|---|
| USB unit tests | setup packets, state machine, descriptors, endpoints, standard requests |
| HID tests | reports and class behavior |
| CDC tests | line coding and class requests |
| Integration tests | enumeration, HID, CDC and HAL transport |
| Negative tests | malformed descriptors, invalid transfers and invalid requests |
The suite explicitly exercises:
- null pointers
- missing OUT payloads
- incorrect OUT lengths
- undersized IN buffers
- invalid USB request types
- unsupported requests
- invalid addresses
- invalid configuration transitions
- invalid endpoint numbers
- invalid endpoint directions
- excessive packet sizes
- malformed descriptor lengths
- unsupported HID report IDs
- invalid CDC line coding
- C compiler with C11 support
- CMake 3.16 or newer
- CTest
- Python 3 for host utilities
On a Unix-like system:
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --parallel
ctest --test-dir build --output-on-failureThe build enables aggressive warnings and treats warnings as errors on GCC/Clang:
-Wall
-Wextra
-Wpedantic
-Wshadow
-Wconversion
-Wsign-conversion
-Werror
Debug assertions remain enabled for validation builds.
After building:
./build/hid_demo
./build/cdc_demo
./build/composite_demoExpected behavior:
HID demo: configured, input report 8 bytes
CDC demo: configured, line coding 7 bytes, baud 115200
Composite demo: HID + CDC device configured
On Windows/MSYS2 environments, the generated executable may carry .exe.
./scripts/build.sh
./scripts/run_tests.shThe scripts intentionally remain thin wrappers around the canonical CMake/CTest commands so that local and CI validation use the same build system.
The repository contains small standard-library-only host tools.
python3 tools/python/descriptor_validator.py fixtures/descriptors/composite_descriptor.hexprintf 'IN GET_DESCRIPTOR 0x0100 0x0000 18\n' \
| python3 tools/python/usb_trace_parser.pypython3 tools/python/test_report.py 17 17No third-party Python packages are required.
.
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ ├── workflows/
│ │ └── ci.yml
│ └── pull_request_template.md
│
├── docs/
│ ├── architecture/
│ │ ├── module_boundaries.md
│ │ └── system_architecture.md
│ ├── design/
│ │ ├── error_handling.md
│ │ └── state_machine.md
│ ├── protocol/
│ │ ├── cdc_acm.md
│ │ ├── descriptors.md
│ │ ├── hid.md
│ │ └── usb_control_transfers.md
│ └── testing/
│ ├── test_matrix.md
│ └── test_strategy.md
│
├── examples/
│ ├── cdc_demo/
│ ├── composite_demo/
│ └── hid_demo/
│
├── fixtures/
│ ├── descriptors/
│ ├── reports/
│ └── setup_packets/
│
├── hal/
│ └── simulation/
│
├── hid/
├── cdc/
├── include/
│ ├── cdc/
│ ├── hal/
│ ├── hid/
│ └── usb/
│
├── scripts/
├── simulation/
│ ├── scenarios/
│ ├── usb_bus/
│ └── usb_host/
│
├── src/
│ ├── common/
│ └── usb/
│
├── tests/
│ ├── cdc/
│ ├── hid/
│ ├── integration/
│ ├── negative/
│ ├── usb/
│ └── test_common.h
│
├── tools/
│ ├── descriptor_dump/
│ └── python/
│
├── CMakeLists.txt
├── CHANGELOG.md
├── CONTRIBUTING.md
├── Doxyfile
├── LICENSE
├── SECURITY.md
└── README.md
GitHub Actions validates the repository on pushes and pull requests to main.
The workflow performs:
- CMake configuration
- Release build
- CTest execution
- Python syntax validation
- Descriptor fixture validation
The repository also includes:
.clang-format
.editorconfig
.gitignore
CONTRIBUTING.md
CODE_OF_CONDUCT.md
SECURITY.md
CHANGELOG.md
Doxyfile
The goal is to keep source formatting, review expectations, security reporting and automated verification explicit.
Engineering documentation is organized by responsibility:
| Document area | Purpose |
|---|---|
docs/architecture |
module ownership and system layering |
docs/protocol |
USB control, descriptor, HID and CDC behavior |
docs/design |
state-machine and error-handling decisions |
docs/testing |
coverage strategy and test matrix |
Doxygen configuration is included for API documentation generation:
doxygen DoxyfileGenerated documentation is treated as a build artifact rather than source-controlled output.
The repository contains development screenshots under:
screenshots/
These are retained as historical development evidence. The authoritative release validation is always the reproducible command sequence in this README and the CI workflow, rather than a screenshot.
For a fresh verification:
rm -rf build
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --parallel
ctest --test-dir build --output-on-failure
./build/hid_demo
./build/cdc_demo
./build/composite_demo
python3 tools/python/descriptor_validator.py fixtures/descriptors/composite_descriptor.hexThis project is intentionally a protocol simulation.
It does not validate:
- physical USB signaling
- electrical timing
- USB PHY behavior
- controller registers
- DMA/cache coherency
- real host-driver installation
- USB-IF certification
- electrical interoperability
- real HID keyboard operation
- real serial-port transport over hardware USB
Those limitations are not hidden; they define the scope of the project.
A production hardware port would retain the protocol/class layers and replace the HAL and controller integration boundary.
A natural next-stage implementation would add:
USB controller driver
│
├── endpoint FIFO / DMA
├── interrupt handling
├── controller reset
├── PHY integration
└── cache / memory barriers
while preserving:
device state
control requests
descriptors
HID
CDC-ACM
validation
tests
This is the architectural reason for keeping transport-specific operations behind the HAL.
Status: Complete for the documented host-side simulation scope.
Validated locally:
- C11 compilation
- warnings-as-errors build
- USB SETUP encode/decode
- standard request handling
- device state transitions
- composite descriptor validation
- endpoint configuration
- HID report path
- CDC-ACM line coding path
- virtual host enumeration
- virtual HAL transport
- negative-path validation
- 17/17 CTest tests
- example applications
- Python utility validation
- CI workflow
- engineering documentation
- repository metadata and governance
This repository should be described as a host-side USB protocol/composite-device simulation, not as a physically validated USB firmware stack.
That distinction is deliberate and technically important.
Embedded Systems & Firmware Developer — Project Author / Lead Developer
B.Tech — Electronics and Communication Engineering (ECE)
Lendi Institute of Engineering and Technology
Vizianagaram, Andhra Pradesh, India
Expected Graduation: 2027
Embedded Systems
Embedded C
Firmware Development
Embedded Linux
Linux Device Drivers
RTOS
Systems Programming
Protocol Engineering
Hardware / Software Interfaces
This repository is part of a broader embedded-systems portfolio focused on demonstrating:
low-level programming
↓
hardware/software boundaries
↓
protocol implementation
↓
defensive systems engineering
↓
automated validation
↓
professional documentation
- GitHub:
github.com/prasanth-vedula - Email:
prasanthvedula2006@gmail.com
MIT License.
See LICENSE for the complete license text.
The purpose of this project is not merely to show that C code can compile.
It demonstrates an engineering workflow:
Requirements
↓
Architecture
↓
Protocol model
↓
Implementation
↓
Validation
↓
Negative testing
↓
Integration testing
↓
Automation
↓
Documentation
↓
Release packaging
The resulting repository is intentionally transparent about what has been implemented, what has been validated, and what remains outside the simulation boundary.
That is the standard expected from a serious embedded-systems portfolio: strong implementation, explicit assumptions, reproducible evidence, defensive interfaces, and honest engineering claims.