Introduction
This document explains the architectural invariants and design principles that guide IronRDP development. These are not merely suggestions - they are enforced rules that ensure the codebase remains maintainable, secure, and performant.An architectural invariant is a property that must hold true at all times. Violating these invariants can compromise security, portability, or the ability to maintain the codebase.
Core Principles
I/O Separation
Why No I/O in Core?
Separating protocol logic from I/O operations provides several critical benefits:- Testability: Pure protocol logic can be tested deterministically without mocking sockets, files, or system calls
- Fuzzing: Fuzz targets can feed arbitrary byte sequences directly to protocol parsers without I/O overhead
- Portability: Core libraries work in any environment - embedded systems, WebAssembly, operating system kernels
- Security: Smaller attack surface by eliminating I/O-related vulnerabilities from protocol code
Dependency Injection Pattern
When runtime information is needed (hostname, current time, random values), use dependency injection:No Platform-Dependent Code
The RDP protocol itself is platform-agnostic. Platform-specific implementations belong in extra tier crates:ironrdp-cliprdr-native- native clipboard integrationironrdp-rdpdr-native- native device redirectionironrdp-rdpsnd-native- native audio output
no_std Compatibility
Feature Flag Pattern
Quality Invariants
Mandatory Fuzzing
RDP is a network protocol exposed to potentially malicious input. Fuzzing is not optional for security-critical parsing code. Fuzz targets location:fuzz/
Running fuzzing:
Test at Boundaries
Architecture Invariant: Tests focus on API boundaries (public APIs of libraries) rather than implementation details.
ironrdp-testsuite-core- for core tier cratesironrdp-testsuite-extra- for extra tier crates
No External Dependencies in Tests
Tests must:- Not require internet connectivity
- Not depend on specific file system structures
- Not rely on system services or daemons
- Be deterministic and repeatable
- Use
include_bytes!()for test data - Use
expect-testfor snapshot testing - Use
proptestfor property-based testing with controlled randomness
Dependency Management
Minimal Dependencies in Core
Examples of acceptable dependencies:bitflags- efficient bit flag handlingder-parser- X.509 certificate parsing (required for RDP security)- Cryptographic primitives (
md5,sha1)
- HTTP clients
- Logging frameworks (use
tracingwith dependency injection) - Serialization frameworks (implement
Encode/Decodedirectly)
No Proc-Macro Dependencies
Rationale: Compilation time is a multiplier for everything. Proc-macros are a major compilation bottleneck. Research by Google engineers shows build latency significantly impacts developer productivity. Exceptions:thiserror may be used for error types, but only if the ergonomic benefit clearly outweighs the compilation cost.
No [workspace.dependencies] for External Crates
Rationale: release-plz cannot detect that a dependency has been updated in a way warranting a version bump if no commit touches a file associated with the crate.
Phasing Out Legacy Dependencies
Performance Principles
Avoid Monomorphization Bloat
The Problem with Generic Code
Rust uses monomorphization - generating separate machine code for each concrete type a generic function is called with. This provides excellent runtime performance but has costs:- Compilation time: Each instantiation is compiled separately
- Binary size: Code is duplicated for each type
- Parallelism: More units to compile can saturate the CPU
Solution: Delegate to Dynamic Dispatch
Avoid Premature Polymorphism
AsRef polymorphism may be worth it for widely-used libraries where ergonomics matter significantly.
Allocation Awareness
Avoid unnecessary allocations, especially in hot paths:Zero-Copy Parsing
UseReadCursor and WriteCursor for efficient, no_std-compatible I/O:
CI/CD Invariants
Local CI Equivalence
Rationale: Developers should be able to validate their changes locally with confidence before pushing. Usage:Test Suite Isolation
Rationale: Keeps iteration time short when working on core protocol logic.Error Handling Standards
Error Type Requirements
Following Rust conventions:- Libraries: Use concrete error types (hand-crafted or
thiserror) - Binaries: Use
anyhow::Errorfor catch-all error handling
Error Message Formatting
Error messages must be:
- Short and concise
- Lowercase (no capital letter at start)
- No trailing punctuation
API Boundary Rules
Certain crates are designated as API Boundaries - public interfaces that external code depends on:Core Tier (All are API Boundaries)
ironrdp(meta crate)ironrdp-coreironrdp-pduironrdp-connectorironrdp-session- And others (see Crate Tiers)
Extra Tier (Selected)
ironrdp-blockingironrdp-asyncironrdp-tokioironrdp-futuresironrdp-web(WASM module)
Special Considerations at Boundaries
Remember: Rules at the boundary are different
- Breaking changes require careful consideration and major version bumps
- Documentation must be comprehensive
- Performance characteristics become public contract
- Error types become part of the API
Internal Tier Boundaries
Internal crates (ironrdp-testsuite-*, ironrdp-fuzzing, xtask) are free to change as needed for project maintenance.
Summary
These design principles ensure IronRDP remains: ✅ Secure - through mandatory fuzzing and small attack surface✅ Portable - via
no_std support and I/O separation✅ Maintainable - with clear boundaries and minimal dependencies
✅ Fast to compile - by avoiding monomorphization and proc-macros
✅ Testable - through deterministic, reproducible tests When in doubt, refer back to these principles and the ARCHITECTURE.md document in the repository.
Further Reading
Architecture Overview
High-level architecture and design philosophy
Crate Tiers
Detailed breakdown of each tier and its crates

