|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Build, Test & Lint Commands |
| 6 | + |
| 7 | +```bash |
| 8 | +# Restore dependencies |
| 9 | +dotnet restore |
| 10 | + |
| 11 | +# Build (Debug or Release) |
| 12 | +dotnet build --configuration Release |
| 13 | +dotnet build --configuration Debug |
| 14 | + |
| 15 | +# Run all tests |
| 16 | +dotnet test --configuration Release --verbosity detailed |
| 17 | + |
| 18 | +# Run a single test class |
| 19 | +dotnet test --configuration Release --filter "ClassName=ChatSessionTests" |
| 20 | + |
| 21 | +# Run a single test method |
| 22 | +dotnet test --configuration Release --filter "FullyQualifiedName~ChatSessionTests.SomeTestMethod" |
| 23 | + |
| 24 | +# Run tests with coverage |
| 25 | +dotnet test --configuration Release --collect:"XPlat Code Coverage" |
| 26 | + |
| 27 | +# Pack NuGet package |
| 28 | +dotnet pack --configuration Release --output ./packages |
| 29 | +``` |
| 30 | + |
| 31 | +Build settings enforce `TreatWarningsAsErrors=true` and `EnforceCodeStyleInBuild=true`, so all analyzer warnings must be resolved before a build succeeds. CS1591 (missing XML doc comments) is suppressed. |
| 32 | + |
| 33 | +## Architecture Overview |
| 34 | + |
| 35 | +LibEmiddle is a .NET 8 end-to-end encryption library implementing X3DH + Double Ratchet protocols via libsodium. The solution has four projects: |
| 36 | + |
| 37 | +| Project | Role | |
| 38 | +|---------|------| |
| 39 | +| **LibEmiddle** | Main library — all cryptographic logic, sessions, transport | |
| 40 | +| **LibEmiddle.Abstractions** | Interface contracts only; no implementations | |
| 41 | +| **LibEmiddle.Domain** | Domain models, DTOs, enums, constants — no external dependencies | |
| 42 | +| **LibEmiddle.Tests.Unit** | MSTest test suite with Moq for mocking | |
| 43 | + |
| 44 | +### Layered dependency flow |
| 45 | +``` |
| 46 | +LibEmiddle → LibEmiddle.Abstractions → LibEmiddle.Domain |
| 47 | +``` |
| 48 | +`LibEmiddle.Tests.Unit` references only `LibEmiddle` (internals are exposed via `InternalsVisibleTo`). |
| 49 | + |
| 50 | +### Main library layout (`LibEmiddle/`) |
| 51 | + |
| 52 | +- **API/** — `LibEmiddleClient` is the single public entry point. `LibEmiddleClientOptions` controls all configuration. Client implements `IAsyncDisposable`. |
| 53 | +- **Protocol/** — `X3DHProtocol` (initial key agreement) and `DoubleRatchetProtocol` (continuous ratcheting). `ProtocolAdapter` bridges them. |
| 54 | +- **Crypto/** — `CryptoProvider` wraps libsodium via `Sodium.cs` P/Invoke bindings. `AES.cs` handles AES-256-GCM. `SecureMemory.cs` manages zeroing of sensitive data. |
| 55 | +- **KeyManagement/** — `KeyManager` handles key lifecycle; `KeyStorage` persists keys encrypted with AES-GCM. |
| 56 | +- **Sessions/** — `SessionManager` manages session lifecycle; `SessionPersistenceManager` handles bundle caching and recovery (Argon2id KDF for password-derived keys since v2.6.0). |
| 57 | +- **Messaging/Chat/** — `ChatSession` handles 1-to-1 encrypted sessions with 500-entry replay-protection FIFO buffer. |
| 58 | +- **Messaging/Group/** — `GroupSession` manages multi-party sessions with per-sender message IDs for replay protection. |
| 59 | +- **Messaging/Transport/** — `MailboxManager` encrypts/decrypts; `HttpMailboxTransport` and `InMemoryMailboxTransport` implement `IMailboxTransport`. `SecureWebSocketClient` wraps TLS WebSocket. |
| 60 | +- **MultiDevice/** — `DeviceManager`, `DeviceLinkingService`, and `SyncMessageValidator` implement device linking and revocation. |
| 61 | + |
| 62 | +### Domain layer (`LibEmiddle.Domain/`) |
| 63 | + |
| 64 | +- **Constants/ProtocolVersion.cs** — `MAJOR_VERSION`/`MINOR_VERSION` constants; updated only on major releases. |
| 65 | +- **Enums/** — `KeyRotationStrategy` (Aggressive/Standard/Conservative/Adaptive), `SessionState`, `MemberRole`, `MessageType`, `SessionType`. |
| 66 | +- **DTO/** — Serializable snapshots for all session types (used for persistence, never for wire format directly). |
| 67 | +- **`LibEmiddleException`** — Typed exception with `LibEmiddleErrorCode` enum; always throw this instead of raw exceptions in library code. |
| 68 | + |
| 69 | +### Abstractions layer (`LibEmiddle.Abstractions/`) |
| 70 | + |
| 71 | +All public-facing interfaces live here: `IChatSession`, `IGroupSession`, `IDoubleRatchetProtocol`, `IX3DHProtocol`, `IMailboxTransport`, `IKeyManager`, `ISessionManager`, `IStorageProvider`, `IDeviceManager`, `ICryptoProvider`, etc. Implement these interfaces in `LibEmiddle`; never let implementations leak into Abstractions. |
| 72 | + |
| 73 | +## Patterns & Anti-Patterns |
| 74 | + |
| 75 | +**Core patterns — always apply:** |
| 76 | +- Clone session state before mutating: `DeepCloneSession()` at entry of every encrypt/decrypt, return the clone, never modify the input object |
| 77 | +- Semaphore over lock: `SemaphoreSlim(1,1)` for all async-safe state mutations; never use `lock()` in async paths |
| 78 | +- Volatile + Interlocked disposal: `private volatile bool _disposed` checked via `ThrowIfDisposed()` on every public entry; set with `Interlocked.Exchange(ref _disposedFlag, 1)` |
| 79 | +- Null-before-return for owned keys: when returning key material to a caller, set the local variable to `null` before the `finally` block so the finally doesn't clear what the caller now owns |
| 80 | +- Secure-clear in finally: every method that produces intermediate key bytes wraps the work in try/finally and calls `SecureMemory.SecureClear()` on all temporaries — even on the success path |
| 81 | +- Tuple returns for paired state: protocol methods return `(UpdatedSession?, ResultData?)` — never mutate session in-place and return a side-channel result |
| 82 | +- Bounded eviction collections: replay-protection sets use a parallel Queue for FIFO eviction when the HashSet exceeds its cap; do the same for any unbounded accumulator |
| 83 | +- Fire-and-forget events: raise domain events via `Task.Run(() => handler(...))` so event handlers can't hold the session lock |
| 84 | +- Boolean validators, not throwing validators: key-validation helpers return `false` on bad input; only the caller that has context throws |
| 85 | +- Constant-time ops: use `CryptographicOperations.FixedTimeEquals()` for key comparison, `Sodium.sodium_memcmp()` for raw byte comparison — never `==` or `SequenceEqual` on secrets |
| 86 | + |
| 87 | +**Anti-patterns — never do:** |
| 88 | +- Never call `Array.Clear()` on key material — use `SecureMemory.SecureClear()` which pins and calls `sodium_memzero` |
| 89 | +- Never use `System.Security.Cryptography` asymmetric primitives — all asymmetric ops go through `CryptoProvider` → `Sodium` |
| 90 | +- Never add unsafe blocks outside `Core/SecureMemory.cs` and `Core/Sodium.cs` |
| 91 | +- Never swallow exceptions in catch — log and rethrow with context; `(null, null)` tuple returns are the only silent failure allowed and only in protocol decrypt paths |
| 92 | +- Never share the cached key array — `GetKeyCopy()` returns a fresh copy; the cache retains the original |
| 93 | +- Never use `Task.FromResult` to wrap genuinely async work — only for operations that are provably synchronous |
| 94 | +- Never skip timestamp validation on incoming messages — reject anything negative or more than 1 hour in the future before touching crypto |
| 95 | + |
| 96 | +## Key Conventions |
| 97 | + |
| 98 | +- **Cryptography**: All crypto operations go through `CryptoProvider` → `Sodium` P/Invoke. Never use `System.Security.Cryptography` directly for asymmetric operations; libsodium is the source of truth. |
| 99 | +- **Unsafe code**: Enabled in `LibEmiddle.csproj` solely for libsodium P/Invoke and `SecureMemory` zeroing. Do not add unsafe blocks elsewhere. |
| 100 | +- **Nullable**: Nullable reference types are enabled project-wide (`Nullable=enable`). All public APIs must be null-annotated. |
| 101 | +- **Async**: All I/O and session operations are async/await. Use `IAsyncDisposable` for resources that hold crypto state. |
| 102 | +- **Events**: State changes (message received, member added, key rotated, session state changed) are surfaced via typed `EventArgs` subclasses in Domain. |
| 103 | +- **Version bumps**: Update `Directory.Build.props` (`VersionPrefix`) and for major releases also `ProtocolVersion.cs`. |
| 104 | +- **Native binaries**: libsodium DLLs live in `runtimes/win-x64/native/` and `runtimes/win-x86/native/`. Linux/macOS builds rely on the system-installed libsodium. |
| 105 | + |
| 106 | +## CI/CD Notes |
| 107 | + |
| 108 | +- `dotnet.yml` runs matrix builds (Debug + Release) on push/PR to `main`, `legacy-1.0`, and `experimental`. Tests must pass in both configurations. |
| 109 | +- Version tags (`v*`) trigger a GitHub Release with attached `.nupkg` files and auto-extracted changelog notes. |
| 110 | +- `release.yml` is a manual workflow for triggering versioned releases; it validates branch/version compatibility before committing the version bump and creating the tag. |
| 111 | +- Branch → version mapping: `main` = v2.x.x, `legacy-1.0` = v1.x.x, `experimental` = v3.x.x-alpha. |
0 commit comments