Each task's tests were run against a reference at its end state; the end states were replayed from master in order with the gate at each step (650 to 762 tests); each skeleton compiles against its tests and fails them. The reference is kept off this machine. Lessons T27 (every wait in a test has a limit) and T28 (mutate the reference before hand-over) come from this work. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
64 lines
3.0 KiB
Markdown
64 lines
3.0 KiB
Markdown
# M4a task 05: a connection, plain or TLS
|
|
|
|
**Branch:** `m4a` (run `git switch m4a`; `git status --short` must be empty, otherwise stop)
|
|
**Commit subject:** `gatewayd: net, TCP or verified TLS to the Mattermost server`
|
|
|
|
## Goal
|
|
|
|
Every REST call and the WebSocket run over one kind of stream: plain TCP for an `http` URL, or TCP
|
|
wrapped in TLS through `rustls` for `https`. TLS verifies the server's name against the host in the
|
|
URL and its chain against the host's trusted certificates (`rustls-native-certs`) plus `ca_file` if
|
|
given. **Nothing can turn verification off.** Spec section 5.
|
|
|
|
## Files
|
|
|
|
- Copy: `crates/gatewayd/tests/net.rs`, `crates/gatewayd/tests/support/tls_server.rs`, and the
|
|
skeleton `crates/gatewayd/src/net.rs`
|
|
- Modify: `crates/gatewayd/src/lib.rs` (`pub mod net;`), `crates/gatewayd/Cargo.toml` (below),
|
|
`docs/implementer-log.md`
|
|
|
|
The tests' TLS server needs `rustls` too. Add to the end of `crates/gatewayd/Cargo.toml`:
|
|
|
|
```toml
|
|
|
|
[dev-dependencies]
|
|
rustls.workspace = true
|
|
```
|
|
|
|
## The skeleton
|
|
|
|
Written, because they are mostly `rustls` API (checked against rustls 0.23.45):
|
|
`client_config(ca_file)`, which builds the root store and the `ClientConfig` with the `ring`
|
|
provider, and `Connector::connect`, which connects to each address in turn, sets the read and
|
|
write timeouts, and for TLS runs the handshake to its end so that a bad certificate is an error
|
|
from `connect` and not from a later read. Read both before you start.
|
|
|
|
Also written: `NetError` (`Roots`, `Connect`, `Tls`) and its `Display`, and the types `Stream`
|
|
(`Plain(TcpStream)` or `Tls(Box<StreamOwned<ClientConnection, TcpStream>>)`) and `Connector`.
|
|
|
|
To fill: `Stream::tcp`, `Stream::set_read_timeout`, `Read` and `Write` for `Stream` (forward to
|
|
the inner stream in each variant), `Connector::new` and `Connector::server`.
|
|
|
|
## Steps
|
|
|
|
- [ ] **1. Copy.** `git switch m4a`, then
|
|
`cp docs/plans/M4a/files/crates/gatewayd/tests/net.rs crates/gatewayd/tests/ && cp docs/plans/M4a/files/crates/gatewayd/tests/support/tls_server.rs crates/gatewayd/tests/support/ && cp docs/plans/M4a/files/crates/gatewayd/src/net.rs crates/gatewayd/src/`.
|
|
Add `pub mod net;` to `lib.rs` and the `[dev-dependencies]` above.
|
|
- [ ] **2. See it fail.** `cargo test -p gatewayd --test net`. Expected: it compiles and 7 tests
|
|
fail.
|
|
- [ ] **3. Fill the functions**, `cargo check -p gatewayd` after each.
|
|
- [ ] **4. See it pass.** `cargo test -p gatewayd --test net`. Expected: 7 passed: plain TCP; TLS
|
|
with the test CA; an unknown CA and a wrong name refused at connect; TLS to a plain server fails
|
|
without hanging; a bad `ca_file` refused before any connection; nobody listening.
|
|
- [ ] **5. Run the gate.** `cargo fmt --all`, then `make gate`. Expected last line: `gate: ok`.
|
|
- [ ] **6. Log and commit.** `git add crates/gatewayd docs/implementer-log.md Cargo.lock && git commit`
|
|
|
|
## Done when
|
|
|
|
- `cargo test -p gatewayd --test net` passes; `make gate` prints `gate: ok`.
|
|
|
|
## Stop and report if
|
|
|
|
- A test passes only with verification weakened in any way (a custom verifier, a skipped name
|
|
check). That is never the fix.
|