Wireframe handshake: install API, 5s timeout, bootstrap integration#234
Wireframe handshake: install API, 5s timeout, bootstrap integration#234
Conversation
…eout - Introduce handshake hooks module implementing Hotline handshake semantics. - Register preamble callbacks that send 8-byte Hotline reply on success or error. - Enforce a 5-second idle timeout for handshake phase on wireframe server. - Wire handshake hooks into WireframeBootstrap server setup pipeline. - Add tests and BDD scenarios validating handshake success, error, and timeout replies. - Update relevant docs and roadmap to reflect handshake handling improvements. This change improves the wireframe transport by integrating Hotline protocol handshake semantics directly, ensuring protocol compliance and improved error handling at the connection establishment phase. Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. Summary by CodeRabbitRelease Notes
✏️ Tip: You can customize this high-level summary in your review settings. WalkthroughInstall a Wireframe handshake layer that decodes 12‑byte Hotline preambles, maps decode outcomes to 8‑byte Hotline reply codes (0–3), enforces a configurable idle handshake timeout (default 5s), bump the wireframe dependency to v0.1.0, and add documentation, tests, and build/config updates. Changes
Sequence DiagramsequenceDiagram
autonumber
actor Client
participant TCP as TCP Connection
participant Server as Wireframe Server
participant Handshake as Handshake Layer
participant Decoder as Protocol Decoder
Client->>TCP: TCP connect
TCP->>Server: accept connection
Server->>Handshake: installed wrapper handles preamble & timeout
activate Handshake
alt Client sends valid preamble
Client->>Server: send 12‑byte Hotline preamble
Server->>Decoder: decode preamble
Decoder-->>Server: success
Server->>Handshake: on_preamble_decode_success
Handshake-->>Client: send 8‑byte reply (code 0)
Handshake->>Server: proceed to route
else Client sends invalid protocol
Client->>Server: send preamble (bad protocol)
Server->>Decoder: decode preamble
Decoder-->>Server: InvalidProtocol
Server->>Handshake: on_preamble_decode_failure
Handshake-->>Client: send 8‑byte reply (code 1)
Handshake->>Server: close connection
else Client sends unsupported version
Client->>Server: send preamble (unsupported version)
Server->>Decoder: decode preamble
Decoder-->>Server: UnsupportedVersion
Server->>Handshake: on_preamble_decode_failure
Handshake-->>Client: send 8‑byte reply (code 2)
Handshake->>Server: close connection
else Client idle (no preamble)
Note over Handshake: idle timer (configurable, default 5s)
Handshake-->>Client: send 8‑byte reply (code 3) on timeout
Handshake->>Server: close connection
end
deactivate Handshake
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25–35 minutes
Possibly related issues
Possibly related PRs
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
We failed to fetch the diff for pull request #234
You can try again by commenting this pull request with @sourcery-ai review, or contact us for help.
…ure imports - Replace custom inline error string matching with a dedicated helper function error_code_from_str for cleaner code. - Update imports from futures to futures_util to unify and simplify future-related imports. - Change return types to use BoxFuture alias for readability and consistency. - Minor test imports updated accordingly. These changes improve code clarity without altering existing functionality. Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
- Upgrade wireframe from 0.1.0-alpha1 to 0.1.0, removing vendored fork - Use upstream preamble hooks to send 8-byte Hotline reply codes - Enforce five-second handshake timeout, dropping idle sockets - Update .cargo/config.toml for development profile optimizations - Add derive_more crate dependencies and update Cargo.lock accordingly - Update design, roadmap, and user-guide docs to reflect handshake integration - Refactor handshake tests for synchronous server startup and cleaner fixtures This enables the Wireframe listener to implement the 12-byte Hotline handshake preamble with proper reply handling and timeout support, matching documented behavior and improving integration test coverage. Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
…tion Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
Reviewer's GuideImplements a reusable Wireframe handshake module that wires Hotline handshake semantics (including a 5s idle timeout) into the Wireframe server via preamble hooks, integrates it into the server bootstrap, exposes it as a public API, updates docs and roadmap, adds unit/BBD tests and feature scenarios, and bumps wireframe to v0.1.0 alongside minor dev-profile tuning. Sequence diagram for Hotline handshake with Wireframe preamble hooks and timeoutsequenceDiagram
actor Client
participant TcpStream
participant WireframeServer
participant HotlinePreamble
participant HandshakeModule
Client->>WireframeServer: connect()
WireframeServer->>TcpStream: accept()
activate WireframeServer
WireframeServer->>WireframeServer: read_preamble_with_timeout(timeout)
note over WireframeServer: WireframeServer configured with<br/>HotlinePreamble and HandshakeModule::install
alt Handshake bytes received before timeout
WireframeServer->>HotlinePreamble: decode(bytes)
alt Decode success
WireframeServer->>HandshakeModule: on_preamble_decode_success(preamble, stream)
HandshakeModule->>TcpStream: write_handshake_reply(HANDSHAKE_OK)
TcpStream-->>Client: 8 byte OK reply
WireframeServer->>WireframeServer: rewind_leftover_bytes()
WireframeServer->>WireframeServer: handle_connection()
Client->>WireframeServer: normal protocol traffic
else Decode failure (invalid protocol or version)
WireframeServer->>HandshakeModule: on_preamble_decode_failure(error, stream)
HandshakeModule->>HandshakeModule: error_code_for_decode(error)
alt Invalid protocol id
HandshakeModule->>TcpStream: write_handshake_reply(HANDSHAKE_ERR_INVALID)
else Unsupported version
HandshakeModule->>TcpStream: write_handshake_reply(HANDSHAKE_ERR_UNSUPPORTED_VERSION)
end
TcpStream-->>Client: 8 byte error reply
WireframeServer->>TcpStream: close()
end
else Handshake idle timeout
WireframeServer->>WireframeServer: timeout reached
WireframeServer->>HandshakeModule: on_preamble_decode_failure(DecodeError_TimedOut, stream)
HandshakeModule->>HandshakeModule: error_code_for_decode(timeout_error)
HandshakeModule->>TcpStream: write_handshake_reply(HANDSHAKE_ERR_TIMEOUT)
TcpStream-->>Client: 8 byte timeout reply
WireframeServer->>TcpStream: close()
end
deactivate WireframeServer
Class diagram for Wireframe handshake module and server bootstrap integrationclassDiagram
class WireframeServer {
+with_preamble_HotlinePreamble() WireframeServer
+on_preamble_decode_success(handler) WireframeServer
+on_preamble_decode_failure(handler) WireframeServer
+preamble_timeout(timeout_ms) WireframeServer
+accept_backoff(backoff) WireframeServer
+bind(addr) WireframeServer
+run_with_shutdown(shutdown_future) Result
}
class WireframeApp {
+default() WireframeApp
}
class HotlinePreamble {
+decode(bytes) Result
}
class ServerState {
<<trait>>
}
class HandshakeModule {
+install(server, timeout) WireframeServer
+success_handler() SuccessHandlerFn
+failure_handler() FailureHandlerFn
+error_code_for_decode(err) u32~Option~
+error_code_from_str(text) u32~Option~
}
class ProtocolConstants {
<<module>>
+HANDSHAKE_OK u32
+HANDSHAKE_ERR_INVALID u32
+HANDSHAKE_ERR_UNSUPPORTED_VERSION u32
+HANDSHAKE_ERR_TIMEOUT u32
+HANDSHAKE_TIMEOUT Duration
+write_handshake_reply(stream, code) io_Result
}
class TcpStream {
+read()
+write()
+shutdown()
}
class DecodeError {
<<enum>>
OtherString
Other
Io
}
HandshakeModule ..> WireframeServer : configures
HandshakeModule ..> ProtocolConstants : uses
HandshakeModule ..> TcpStream : writes_replies
HandshakeModule ..> DecodeError : maps_errors
WireframeServer --> HotlinePreamble : uses_for_preamble
WireframeServer --> WireframeApp : builds_per_connection
WireframeServer ..> ServerState : generic_over
class WireframeBootstrap {
+run(bind_addr, backoff) Result
}
WireframeBootstrap ..> WireframeServer : constructs
WireframeBootstrap ..> HotlinePreamble : with_preamble
WireframeBootstrap ..> HandshakeModule : calls_install
WireframeBootstrap ..> ProtocolConstants : HANDSHAKE_TIMEOUT
%% Rust generics shown as comments on associations
%% WireframeServer<F, HotlinePreamble, S> where F: Fn() -> WireframeApp, S: ServerState
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey there - I've reviewed your changes - here's some feedback:
- The
error_code_for_decodelogic relies on matchingDecodeErrorstring contents (e.g."invalid protocol id","unsupported version"), which is brittle against message wording changes; consider surfacing structured errors fromHotlinePreambleor wrapping decode failures so you can match on a stable enum/marker instead of parsing strings. - The
preamble_byteshelper is duplicated in bothtestsandbddmodules ofhandshake.rs; extracting it into a shared test utility (or reusing one of them) would reduce duplication and keep the handshake encoding logic in a single place.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The `error_code_for_decode` logic relies on matching `DecodeError` string contents (e.g. `"invalid protocol id"`, `"unsupported version"`), which is brittle against message wording changes; consider surfacing structured errors from `HotlinePreamble` or wrapping decode failures so you can match on a stable enum/marker instead of parsing strings.
- The `preamble_bytes` helper is duplicated in both `tests` and `bdd` modules of `handshake.rs`; extracting it into a shared test utility (or reusing one of them) would reduce duplication and keep the handshake encoding logic in a single place.
## Individual Comments
### Comment 1
<location> `src/wireframe/handshake.rs:80-85` </location>
<code_context>
+ }
+}
+
+fn error_code_from_str(text: &str) -> Option<u32> {
+ if text.contains("invalid protocol id") {
+ Some(HANDSHAKE_ERR_INVALID)
+ } else if text.contains("unsupported version") {
+ Some(HANDSHAKE_ERR_UNSUPPORTED_VERSION)
+ } else {
+ None
+ }
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Mapping decode errors via substring matching on error messages is brittle.
This couples protocol behavior to human-readable strings and will silently break if those messages change upstream. If we control the preamble decoder, it would be more robust to expose structured error variants or numeric codes and map those to Hotline error codes instead of matching on substrings.
Suggested implementation:
```rust
match err {
DecodeError::InvalidProtocolId => Some(HANDSHAKE_ERR_INVALID),
DecodeError::UnsupportedVersion => Some(HANDSHAKE_ERR_UNSUPPORTED_VERSION),
DecodeError::Io { inner, .. } if inner.kind() == io::ErrorKind::TimedOut => {
Some(HANDSHAKE_ERR_TIMEOUT)
}
_ => None,
}
}
fn success_handler()
```
```rust
fn success_handler()
-> impl for<'a> Fn(&'a HotlinePreamble, &'a mut TcpStream) -> BoxFuture<'a, io::Result<()>> + Send + Sync
{
```
To fully implement this approach and remove the brittle string matching, you will also need to:
1. **Extend `DecodeError`** (likely in the preamble/decoder module, e.g. `src/wireframe/preamble.rs` or wherever `DecodeError` is defined) to include structured variants:
```rust
pub enum DecodeError {
// existing variants...
InvalidProtocolId,
UnsupportedVersion,
// ...
}
```
2. **Update the preamble decoding logic** to emit these variants instead of `OtherString`/`Other` with textual messages. For example, wherever you currently do something like:
```rust
return Err(DecodeError::OtherString("invalid protocol id".to_string()));
```
change it to:
```rust
return Err(DecodeError::InvalidProtocolId);
```
and similarly for the unsupported version case:
```rust
return Err(DecodeError::UnsupportedVersion);
```
3. **Adjust any other code paths** that currently rely on those specific error strings (if any) to work with the new structured variants instead.
After these changes, handshake error mapping will be based on stable, structured error variants rather than brittle substring comparisons.
</issue_to_address>
### Comment 2
<location> `docs/wireframe-users-guide.md:37-38` </location>
<code_context>
+}
+```
+
+The snippet below wires the builder into a Tokio runtime, decodes inbound
+payloads, and emits a serialised response. It showcases the typical `main`
+function for a microservice that listens on localhost and responds to a `Ping`
+message with a `Pong` payload.[^2][^10][^15]
</code_context>
<issue_to_address>
**nitpick (typo):** Use a consistent spelling of “serialized” within the guide.
This occurrence uses the British spelling “serialised” while later sections use “serialized.” Please update this to “serialized” for consistency with the rest of the guide.
```suggestion
The snippet below wires the builder into a Tokio runtime, decodes inbound
payloads, and emits a serialized response. It showcases the typical `main`
```
</issue_to_address>
### Comment 3
<location> `src/wireframe/handshake.rs:106` </location>
<code_context>
+ use super::*;
+ use crate::protocol::{HANDSHAKE_LEN, HANDSHAKE_TIMEOUT, PROTOCOL_ID, REPLY_LEN, VERSION};
+
+ fn preamble_bytes(
+ protocol: [u8; 4],
+ sub_protocol: [u8; 4],
</code_context>
<issue_to_address>
**issue (complexity):** Consider extracting shared test helpers (like the handshake preamble and client/reply logic) into a common module and simplifying the BDD world to avoid custom runtimes and interior mutability.
You can keep all current functionality while reducing complexity and duplication in the test modules.
### 1. Reuse shared helpers instead of duplicating them
`preamble_bytes` is implemented twice; you can define it once in a small shared `#[cfg(test)]` helper module and use it from both `tests` and `bdd`:
```rust
#[cfg(test)]
mod test_support {
use crate::protocol::HANDSHAKE_LEN;
pub fn preamble_bytes(
protocol: [u8; 4],
sub_protocol: [u8; 4],
version: u16,
sub_version: u16,
) -> [u8; HANDSHAKE_LEN] {
let mut buf = [0u8; HANDSHAKE_LEN];
buf[0..4].copy_from_slice(&protocol);
buf[4..8].copy_from_slice(&sub_protocol);
buf[8..10].copy_from_slice(&version.to_be_bytes());
buf[10..12].copy_from_slice(&sub_version.to_be_bytes());
buf
}
}
```
Then:
```rust
mod tests {
use super::test_support::preamble_bytes;
// ...
}
mod bdd {
use super::test_support::preamble_bytes;
// ...
}
```
You can do the same with `recv_reply`/client logic by exposing a small helper that the BDD world calls instead of re‑implementing `read_exact` + `timeout`.
### 2. Drop the custom `Runtime` and most `RefCell`s in `HandshakeWorld`
You can still keep the BDD style and feature files, but simplify the world by running under Tokio’s test runtime and storing values directly instead of via `RefCell<Option<…>>`.
For example:
```rust
struct HandshakeWorld {
addr: SocketAddr,
shutdown: oneshot::Sender<()>,
reply: Option<Result<[u8; REPLY_LEN], String>>,
}
impl HandshakeWorld {
async fn start_server() -> Self {
let (addr, shutdown) = super::tests::start_server(Duration::from_millis(100));
Self { addr, shutdown, reply: None }
}
async fn connect_and_maybe_send(&mut self, bytes: Option<Vec<u8>>) {
let mut stream = TcpStream::connect(self.addr).await.expect("connect");
if let Some(data) = bytes {
stream.write_all(&data).await.expect("write handshake");
}
let mut buf = [0u8; REPLY_LEN];
let res = timeout(Duration::from_secs(1), stream.read_exact(&mut buf))
.await
.map(|r| { r.expect("read reply"); buf })
.map_err(|e| e.to_string());
self.reply = Some(res);
}
}
```
Then adapt your BDD steps to be async and run under `#[tokio::test]`/async `scenario` support (rstest_bdd supports async steps), avoiding `Runtime::block_on` entirely:
```rust
#[fixture]
#[tokio::main(flavor = "current_thread")]
async fn world() -> HandshakeWorld {
HandshakeWorld::start_server().await
}
#[when("I send a valid Hotline handshake")]
async fn when_valid(world: &mut HandshakeWorld) {
let bytes = preamble_bytes(*PROTOCOL_ID, *b"CHAT", VERSION, 0);
world.connect_and_maybe_send(Some(bytes.to_vec())).await;
}
```
This keeps the BDD feature coverage but removes the extra runtime management and interior mutability, and consolidates the handshake helpers into a single place.
</issue_to_address>
### Comment 4
<location> `src/wireframe/handshake.rs:106` </location>
<code_context>
+ use super::*;
+ use crate::protocol::{HANDSHAKE_LEN, HANDSHAKE_TIMEOUT, PROTOCOL_ID, REPLY_LEN, VERSION};
+
+ fn preamble_bytes(
+ protocol: [u8; 4],
+ sub_protocol: [u8; 4],
</code_context>
<issue_to_address>
**issue (review_instructions):** Deduplicate the `preamble_bytes` helper by sharing a single implementation between the `tests` and `bdd` modules to keep the test code DRY.
You already define an identical `preamble_bytes` helper in the unit-test module above. Extract a single shared version (for example, into the parent module or a common `tests` helper submodule) and reuse it from both `tests` and `bdd` to avoid duplication and keep the test code DRY while remaining readable.
<details>
<summary>Review instructions:</summary>
**Path patterns:** `**/*`
**Instructions:**
Keep code DRY, but readable. Use refactoring approaches best suited for the language in question.
</details>
</issue_to_address>
### Comment 5
<location> `src/wireframe/handshake.rs:298` </location>
<code_context>
+
+ // `rstest` reports this function as a fixture; rustc flags the required block
+ // as `unused_braces`, so suppress the false positive locally.
+ #[allow(unused_braces)]
+ #[fixture]
+ fn world() -> HandshakeWorld { HandshakeWorld::new() }
</code_context>
<issue_to_address>
**issue (review_instructions):** `#[allow]` is forbidden by the lint policy; this suppression should be removed or rewritten as a narrowly scoped `#[expect(...)]` with a reason.
The instructions explicitly forbid `#[allow]`. If you genuinely need to suppress this lint, please switch to a narrowly scoped `#[expect(unused_braces, reason = "FIXME: <link/explanation>")]` on the minimal item necessary, and include a FIXME and link if a future fix is expected.
If the lint is a false positive or not harmful, consider restructuring the code (e.g. by removing the braces or tweaking the fixture signature) so that no suppression is needed.
<details>
<summary>Review instructions:</summary>
**Path patterns:** `**/*.rs`
**Instructions:**
Lints must not be silenced except as a **last resort**.
* `#[allow]` is **forbidden**.
* Only **narrowly scoped** `#[expect(lint, reason = "...")]` is allowed.
* No lint groups, no blanket or file-wide suppression.
* Include `FIXME:` with link if a fix is expected.
</details>
</issue_to_address>
### Comment 6
<location> `src/wireframe/handshake.rs:125` </location>
<code_context>
+ .workers(1)
+ .with_preamble::<HotlinePreamble>();
+ let server = install(server, timeout);
+ let server = server.bind("127.0.0.1:0".parse().unwrap()).expect("bind");
+ let addr = server.local_addr().expect("addr");
+ let (shutdown_tx, shutdown_rx) = oneshot::channel::<()>();
</code_context>
<issue_to_address>
**issue (review_instructions):** `unwrap()` is used here; the guidelines require preferring `.expect()` instead.
To follow the guideline, please replace `.unwrap()` with `.expect("parse socket addr")` (or similar context string):
```rust
let server = server
.bind("127.0.0.1:0".parse().expect("parse socket addr"))
.expect("bind");
```
<details>
<summary>Review instructions:</summary>
**Path patterns:** `**/*.rs`
**Instructions:**
Prefer `.expect()` over `.unwrap()`
</details>
</issue_to_address>
### Comment 7
<location> `src/wireframe/handshake.rs:154` </location>
<code_context>
+
+ let reply = recv_reply(&mut stream).await;
+ assert_eq!(&reply[0..4], PROTOCOL_ID);
+ assert_eq!(
+ u32::from_be_bytes(reply[4..8].try_into().unwrap()),
+ HANDSHAKE_OK
</code_context>
<issue_to_address>
**issue (review_instructions):** This assertion relies on a `.unwrap()` inside `try_into()`, which should be changed to `.expect()` per the guideline.
A few lines below, `u32::from_be_bytes(reply[4..8].try_into().unwrap())` uses `unwrap()`. Please switch that to `expect("convert reply slice to array")` (or similar) to comply with the "prefer `.expect()`" rule:
```rust
u32::from_be_bytes(reply[4..8].try_into().expect("convert reply slice to array"))
```
<details>
<summary>Review instructions:</summary>
**Path patterns:** `**/*.rs`
**Instructions:**
Prefer `.expect()` over `.unwrap()`
</details>
</issue_to_address>
### Comment 8
<location> `src/wireframe/handshake.rs:154` </location>
<code_context>
+
+ let reply = recv_reply(&mut stream).await;
+ assert_eq!(&reply[0..4], PROTOCOL_ID);
+ assert_eq!(
+ u32::from_be_bytes(reply[4..8].try_into().unwrap()),
+ HANDSHAKE_OK
</code_context>
<issue_to_address>
**issue (review_instructions):** The second handshake test also uses `.unwrap()` inside `try_into()` and should be updated to `.expect()`.
Similarly in this test, the expression `u32::from_be_bytes(reply[4..8].try_into().unwrap())` should use `.expect("convert reply slice to array")` (or similar) instead of `unwrap()` to match the project guidelines.
<details>
<summary>Review instructions:</summary>
**Path patterns:** `**/*.rs`
**Instructions:**
Prefer `.expect()` over `.unwrap()`
</details>
</issue_to_address>
### Comment 9
<location> `src/wireframe/handshake.rs:283` </location>
<code_context>
+ };
+ reply
+ .as_ref()
+ .map(|buf| u32::from_be_bytes(buf[4..8].try_into().unwrap()))
+ .map_err(ToString::to_string)
+ }
</code_context>
<issue_to_address>
**issue (review_instructions):** `unwrap()` is used when converting the reply slice, and should be replaced with `.expect()` with a descriptive message.
Here as well, please replace `try_into().unwrap()` with `try_into().expect("convert reply slice to array")` (or a clearer context-specific message) to adhere to the "prefer `.expect()`" convention.
<details>
<summary>Review instructions:</summary>
**Path patterns:** `**/*.rs`
**Instructions:**
Prefer `.expect()` over `.unwrap()`
</details>
</issue_to_address>
### Comment 10
<location> `src/wireframe/handshake.rs:80` </location>
<code_context>
+ }
+}
+
+fn error_code_from_str(text: &str) -> Option<u32> {
+ if text.contains("invalid protocol id") {
+ Some(HANDSHAKE_ERR_INVALID)
</code_context>
<issue_to_address>
**suggestion (review_instructions):** `error_code_from_str` uses an if/else-if/else chain (>2 branches); per the guideline this logic should be factored into predicate helpers instead of a multi-branch conditional.
Inside `error_code_from_str` there is an `if ... else if ... else` chain that has three branches. The instructions ask that conditionals with more than two branches be moved into predicate functions.
One way to align with this would be to introduce small predicates, e.g.:
```rust
fn is_invalid_protocol(text: &str) -> bool { text.contains("invalid protocol id") }
fn is_unsupported_version(text: &str) -> bool { text.contains("unsupported version") }
fn error_code_from_str(text: &str) -> Option<u32> {
if is_invalid_protocol(text) {
Some(HANDSHAKE_ERR_INVALID)
} else if is_unsupported_version(text) {
Some(HANDSHAKE_ERR_UNSUPPORTED_VERSION)
} else {
None
}
}
```
or equivalently structure the checks so that no single conditional has more than two branches.
<details>
<summary>Review instructions:</summary>
**Path patterns:** `**/*.rs`
**Instructions:**
Move conditionals with >2 branches into a predicate function.
</details>
</issue_to_address>
### Comment 11
<location> `docs/wireframe-users-guide.md:38` </location>
<code_context>
+```
+
+The snippet below wires the builder into a Tokio runtime, decodes inbound
+payloads, and emits a serialised response. It showcases the typical `main`
+function for a microservice that listens on localhost and responds to a `Ping`
+message with a `Pong` payload.[^2][^10][^15]
</code_context>
<issue_to_address>
**suggestion (review_instructions):** "serialised" should use the -ize form under en-GB-oxendic conventions ("serialized").
En-GB-oxendic style prefers -ize/-yse endings, so this should read "emits a serialized response" rather than "serialised".
<details>
<summary>Review instructions:</summary>
**Path patterns:** `**/*.md`
**Instructions:**
Use en-GB-oxendic (-ize / -yse / -our) spelling and grammar.
</details>
</issue_to_address>
### Comment 12
<location> `docs/wireframe-users-guide.md:102` </location>
<code_context>
+```
+
+Route identifiers must be unique; the builder returns
+`WireframeError::DuplicateRoute` when you try to register a handler twice,
+keeping the dispatch table unambiguous.[^2][^5] New applications default to the
+bundled bincode serializer, a 1024-byte frame buffer, and a 100 ms read
</code_context>
<issue_to_address>
**suggestion (review_instructions):** This sentence uses the second-person pronoun "you", which the style guide forbids.
Consider rephrasing to avoid second person, for example: "`WireframeError::DuplicateRoute` when a handler is registered for the same route twice," or similar.
<details>
<summary>Review instructions:</summary>
**Path patterns:** `**/*.md`
**Instructions:**
Avoid 2nd person or 1st person pronouns ("I", "you", "we").
</details>
</issue_to_address>
### Comment 13
<location> `docs/wireframe-users-guide.md:106` </location>
<code_context>
+keeping the dispatch table unambiguous.[^2][^5] New applications default to the
+bundled bincode serializer, a 1024-byte frame buffer, and a 100 ms read
+timeout. Clamp these limits with `buffer_capacity` and `read_timeout_ms`, or
+swap the serializer with `with_serializer` when you need a different encoding
+strategy.[^3][^4]
+
</code_context>
<issue_to_address>
**suggestion (review_instructions):** This line contains "you", which is not permitted by the pronoun guideline.
A neutral alternative might be: "swap the serializer with `with_serializer` when a different encoding strategy is required".
<details>
<summary>Review instructions:</summary>
**Path patterns:** `**/*.md`
**Instructions:**
Avoid 2nd person or 1st person pronouns ("I", "you", "we").
</details>
</issue_to_address>
### Comment 14
<location> `docs/wireframe-users-guide.md:113` </location>
<code_context>
+`WireframeServer`—`handle_connection(stream)` builds (or reuses) the middleware
+chain, wraps the transport in a length-delimited codec, enforces per-frame read
+timeouts, and writes responses. Serialization helpers `send_response` and
+`send_response_framed` return typed `SendError` variants when encoding or I/O
+fails, and the connection closes after ten consecutive deserialization
+errors.[^6][^7]
</code_context>
<issue_to_address>
**suggestion (review_instructions):** The acronym "I/O" is used here without being expanded on first use.
To comply with the acronym rule, consider expanding on first mention, for example: "input/output (I/O)".
<details>
<summary>Review instructions:</summary>
**Path patterns:** `**/*.md`
**Instructions:**
Define uncommon acronyms on first use.
</details>
</issue_to_address>
### Comment 15
<location> `docs/wireframe-users-guide.md:359` </location>
<code_context>
+
+## Running servers
+
+`WireframeServer::new` clones the application factory per worker, defaults the
+worker count to the host CPU total (never below one), supports a readiness
+signal, and normalizes accept-loop backoff settings through
</code_context>
<issue_to_address>
**suggestion (review_instructions):** "CPU" later in this sentence is not expanded on first use.
Consider expanding the acronym on first occurrence, for example: "host central processing unit (CPU) total".
<details>
<summary>Review instructions:</summary>
**Path patterns:** `**/*.md`
**Instructions:**
Define uncommon acronyms on first use.
</details>
</issue_to_address>
### Comment 16
<location> `docs/wireframe-users-guide.md:576` </location>
<code_context>
+
+## Additional utilities
+
+- `read_preamble` decodes up to 1 KiB using bincode, returning the decoded
+ value plus any leftover bytes that must be replayed before normal frame
+ processing.[^37]
</code_context>
<issue_to_address>
**suggestion (review_instructions):** "KiB" is introduced without definition, which conflicts with the acronym rule.
Consider expanding this as "kibibyte (KiB)" on first use, for example: "decodes up to 1 kibibyte (KiB)".
<details>
<summary>Review instructions:</summary>
**Path patterns:** `**/*.md`
**Instructions:**
Define uncommon acronyms on first use.
</details>
</issue_to_address>
### Comment 17
<location> `docs/wireframe-users-guide.md:452` </location>
<code_context>
+`Response::MultiPacket` exposes a different surface: callers hand ownership of
+the receiving half of a `tokio::sync::mpsc` channel to the connection actor,
+retain the sender, and push frames whenever back-pressure allows. The
+`Response::with_channel` helper constructs the pair and returns the sender
+alongside a `Response::MultiPacket`, making the ergonomic tuple pattern
+documented in ADR 0001 trivial to adopt. The library awaits channel capacity
</code_context>
<issue_to_address>
**suggestion (review_instructions):** The reference to "ADR 0001" in the following line introduces the ADR acronym without expansion.
On its first occurrence, consider expanding ADR, for example: "Architecture Decision Record (ADR) 0001".
<details>
<summary>Review instructions:</summary>
**Path patterns:** `**/*.md`
**Instructions:**
Define uncommon acronyms on first use.
</details>
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
- Replace string matching of handshake errors with constants for clarity. - Extract test helper functions for handshake preamble and reply handling. - Update tests to use helper functions and better error code conversions. - Enhance decode_error_for_handshake to return specific error strings. - Minor cleanups in handshake and preamble modules to improve maintainability. Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/server/wireframe.rs (1)
1-8: Wire in handshake install and refresh module docsRegister the Hotline handshake as part of bootstrap, as done here, but also update the module-level doc comment so it no longer claims the handshake is “future work” now that
handshake::install(server, protocol::HANDSHAKE_TIMEOUT)is in therunpath.Use wording along these lines:
-//! The initial implementation keeps the runtime intentionally small: it -//! parses configuration, resolves the bind address, and starts an empty -//! [`WireframeServer`]. Future work will register the Hotline handshake, -//! serializer, and protocol routes described in the roadmap. +//! The current implementation keeps the runtime intentionally small: it +//! parses configuration, resolves the bind address, registers the Hotline +//! handshake, and starts a [`WireframeServer`]. Future work will attach the +//! serializer and protocol routes described in the roadmap.Also applies to: 22-27, 76-92
src/wireframe/preamble.rs (1)
51-63: Align handshake error tags with tests and lint policyUpdate this module so the new handshake-specific
DecodeErrorstrings and the tests stay in agreement, and avoid using#[allow]for Clippy:
- Fix test expectations for the new error tags
decode_error_for_handshakenow emitsOtherString("handshake:invalid-protocol-id")andOtherString("handshake:unsupported-version"). TheDisplayimpl for bincode'sDecodeErrorprints variants inDebugform, soerr.to_string()from these variants no longer contains"invalid protocol id"or"unsupported version"with spaces.Update the unit tests in this file to assert on the canonical tags instead:
@@ - #[rstest] - #[tokio::test] - async fn rejects_invalid_protocol() { @@ - assert!( - err.to_string().contains("invalid protocol id"), - "expected detailed message, got {err:?}" - ); + assert!( + err.to_string().contains("handshake:invalid-protocol-id"), + "expected detailed message tag, got {err:?}" + ); @@ - #[rstest] - #[tokio::test] - async fn rejects_unsupported_version() { @@ - assert!( - err.to_string().contains("unsupported version"), - "expected detailed message, got {err:?}" - ); + assert!( + err.to_string().contains("handshake:unsupported-version"), + "expected detailed message tag, got {err:?}" + );
- Replace
#[allow]with#[expect]for Clippy
then_failurestill uses#[allow(clippy::needless_pass_by_value)], which breaks the lint policy that forbids#[allow]and requires scoped#[expect(..., reason = \"…\")]instead.Mirror the pattern used on
given_malformed:- #[then("decoding fails with \"{message}\"")] - #[allow(clippy::needless_pass_by_value)] - fn then_failure(world: &HandshakeWorld, message: String) { + #[then("decoding fails with \"{message}\"")] + #[expect( + clippy::needless_pass_by_value, + reason = "rstest-bdd step parameters must be owned; keep String until macro supports &str \ + captures" + )] + fn then_failure(world: &HandshakeWorld, message: String) {
♻️ Duplicate comments (2)
src/wireframe/handshake.rs (2)
49-74: Deduplicatepreamble_byteswith the existing helperDeduplicate
test_support::preamble_byteswith the existing helper insrc/wireframe/preamble.rsto keep the handshake layout defined in one place. Expose a single shared helper (for example in a small test module) and reuse it from both preamble and handshake tests rather than maintaining two near‑identical implementations.
232-365: Replace#[allow]attributes and simplify the BDD runtime worldRemove both
#[allow(unused_braces)]on theworldfixture and#[allow(clippy::needless_pass_by_value)]onwhen_custom; the lint policy forbids#[allow]and requires either fixing the code or using narrowly scoped#[expect(.., reason = "FIXME: …")].
- Restructure the
worldfixture to avoid theunused_braceswarning (for example, by writing it as a normal function body without the extra braces), or, if a suppression is genuinely unavoidable, replace#[allow(unused_braces)]with#[expect(unused_braces, reason = "FIXME: link-to-tracking-issue")]on the minimal item.- Eliminate
needless_pass_by_valueby changingtag: Stringinwhen_customto borrow (&str) so the step definition no longer needs a lint suppression.In a follow‑up, drop the bespoke
RuntimeplusRefCellstate inHandshakeWorldand run the BDD steps on the existing async test runtime to reduce complexity and align with the adjacent async tests.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (12)
.cargo/config.toml(1 hunks).markdownlint-cli2.jsonc(1 hunks)Cargo.toml(1 hunks)docs/design.md(1 hunks)docs/roadmap.md(1 hunks)docs/users-guide.md(1 hunks)docs/wireframe-users-guide.md(1 hunks)src/server/wireframe.rs(2 hunks)src/wireframe/handshake.rs(1 hunks)src/wireframe/mod.rs(1 hunks)src/wireframe/preamble.rs(1 hunks)tests/features/wireframe_handshake_hooks.feature(1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.rs
📄 CodeRabbit inference engine (AGENTS.md)
**/*.rs: Runmake check-fmt,make lint, andmake testbefore committing Rust code.
Clippy warnings MUST be disallowed in Rust code.
Fix any warnings emitted during Rust tests in the code itself rather than silencing them.
Where a Rust function is too long, extract meaningfully named helper functions adhering to separation of concerns and CQRS.
Where a Rust function has too many parameters, group related parameters in meaningfully named structs.
Where a Rust function is returning a large error, consider usingArcto reduce the amount of data returned.
Write unit and behavioural tests for new Rust functionality. Run both before and after making any change.
Every Rust module must begin with a module level (//!) comment explaining the module's purpose and utility.
Document public Rust APIs using Rustdoc comments (///) so documentation can be generated with cargo doc.
Prefer immutable data and avoid unnecessarymutbindings in Rust.
Handle errors with theResulttype instead of panicking in Rust where feasible.
Avoidunsafecode in Rust unless absolutely necessary, and document any usage clearly.
Place function attributes after doc comments in Rust.
Do not usereturnin single-line Rust functions.
Use predicate functions for Rust conditional criteria with more than two branches.
Lints must not be silenced in Rust except as a last resort.
Rust lint rule suppressions must be tightly scoped and include a clear reason.
Preferexpectoverallowin Rust error handling.
Userstestfixtures for shared setup in Rust tests.
Replace duplicated Rust tests with#[rstest(...)]parameterized cases.
Prefermockallfor mocks/stubs in Rust tests.
Useconcat!()to combine long string literals in Rust rather than escaping newlines with a backslash.
Prefer single-line versions of Rust functions where appropriate (e.g.,pub fn new(id: u64) -> Self { Self(id) }instead of a multi-line version).
Use NewTypes to model domain values and eliminate 'i...
Files:
src/wireframe/mod.rssrc/wireframe/handshake.rssrc/wireframe/preamble.rssrc/server/wireframe.rs
⚙️ CodeRabbit configuration file
**/*.rs: * Seek to keep the cognitive complexity of functions no more than 9.
- Adhere to single responsibility and CQRS
- Place function attributes after doc comments.
- Do not use
returnin single-line functions.- Move conditionals with >2 branches into a predicate function.
- Avoid
unsafeunless absolutely necessary.- Every module must begin with a
//!doc comment that explains the module's purpose and utility.- Comments and docs must follow en-GB-oxendict (-ize / -yse / -our) spelling and grammar
- Lints must not be silenced except as a last resort.
#[allow]is forbidden.- Only narrowly scoped
#[expect(lint, reason = "...")]is allowed.- No lint groups, no blanket or file-wide suppression.
- Include
FIXME:with link if a fix is expected.- Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.
- Use
rstestfixtures for shared setup and to avoid repetition between tests.- Replace duplicated tests with
#[rstest(...)]parameterised cases.- Prefer
mockallfor mocks/stubs.- Prefer
.expect()over.unwrap()in tests..expect()and.unwrap()are forbidden outside of tests. Errors must be propagated.- Ensure that any API or behavioural changes are reflected in the documentation in
docs/- Ensure that any completed roadmap steps are recorded in the appropriate roadmap in
docs/- Files must not exceed 400 lines in length
- Large modules must be decomposed
- Long match statements or dispatch tables should be decomposed by domain and collocated with targets
- Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
- Environment access (env::set_var and env::remove_var) are always unsafe in Rust 2024 and MUST be marked as such
- For testing of functionality depending upon environment variables, dependency injection and...
Files:
src/wireframe/mod.rssrc/wireframe/handshake.rssrc/wireframe/preamble.rssrc/server/wireframe.rs
docs/**/*.md
📄 CodeRabbit inference engine (AGENTS.md)
docs/**/*.md: Use markdown files within thedocs/directory as a knowledge base and source of truth for project requirements, dependency choices, and architectural decisions.
Documentation must use en-GB-oxendict spelling and grammar ('-ize' / '-yse' / '-our'). (EXCEPTION: the LICENSE filename is left unchanged for community consistency.)
docs/**/*.md: Use British English based on the Oxford English Dictionary (en-GB-oxendict), including: -ize suffixes (realize, organization), -lyse suffixes (analyse, paralyse, catalyse), -our suffixes (colour, behaviour, neighbour), -re suffixes (calibre, centre, fibre), double 'l' (cancelled, counsellor, cruellest), maintain 'e' (likeable, liveable, rateable), -ogue suffixes (analogue, catalogue)
Keep US spelling when used in an API (e.g., 'color')
Use the Oxford comma: 'ships, planes, and hovercraft' where it aids comprehension
Treat company names as collective nouns: 'Lille Industries are expanding'
Write headings in sentence case
Use Markdown headings (#, ##, ###, and so on) in order without skipping levels
Follow markdownlint recommendations for Markdown formatting
Always provide a language identifier for fenced code blocks; use 'plaintext' for non-code text
Use '-' as the first level bullet and renumber lists when items change
Prefer inline links using 'text' or angle brackets around the URL
Ensure blank lines before and after bulleted lists and fenced blocks
Ensure tables have a delimiter line below the header row
Expand any uncommon acronym on first use, for example, Continuous Integration (CI)
Wrap paragraphs at 80 columns
Do not wrap tables in documentation
Use footnotes referenced with '[^label]'
Where it adds clarity, include Mermaid diagrams in documentation
When embedding figures in documentation, use '' and provide brief alt text describing the content
Add a short description before each Mermaid diagram so screen readers can understand itAll Markdown documents that include a ...
Files:
docs/wireframe-users-guide.mddocs/users-guide.mddocs/roadmap.mddocs/design.md
**/*.md
📄 CodeRabbit inference engine (AGENTS.md)
**/*.md: Validate Markdown files usingmake markdownlint.
Runmake fmtafter any documentation changes to format all Markdown files and fix table markup.
Validate Mermaid diagrams in Markdown files by runningmake nixie.
Markdown paragraphs and bullet points must be wrapped at 80 columns.
Code blocks in Markdown must be wrapped at 120 columns.
Tables and headings in Markdown must not be wrapped.
Use dashes (-) for list bullets in Markdown.
Use GitHub-flavoured Markdown footnotes ([^1]) for references and footnotes.
Files:
docs/wireframe-users-guide.mddocs/users-guide.mddocs/roadmap.mddocs/design.md
⚙️ CodeRabbit configuration file
**/*.md: * Avoid 2nd person or 1st person pronouns ("I", "you", "we")
- Use en-GB-oxendict (-ize / -yse / -our) spelling and grammar
- Headings must not be wrapped.
- Documents must start with a level 1 heading
- Headings must correctly increase or decrease by no more than one level at a time
- Use GitHub-flavoured Markdown style for footnotes and endnotes.
- Numbered footnotes must be numbered by order of appearance in the document.
Files:
docs/wireframe-users-guide.mddocs/users-guide.mddocs/roadmap.mddocs/design.md
docs/**/*.{md,rs}
📄 CodeRabbit inference engine (docs/documentation-style-guide.md)
Wrap code at 120 columns
Files:
docs/wireframe-users-guide.mddocs/users-guide.mddocs/roadmap.mddocs/design.md
**/Cargo.toml
📄 CodeRabbit inference engine (AGENTS.md)
**/Cargo.toml: Use explicit version ranges inCargo.tomland keep dependencies up-to-date.
All crate versions specified inCargo.tomlmust use SemVer-compatible caret requirements (e.g.,some-crate = "1.2.3").
The use of wildcard (*) or open-ended inequality (>=) version requirements inCargo.tomlis strictly forbidden.
Tilde requirements (~) inCargo.tomlshould only be used where a dependency must be locked to patch-level updates for a specific, documented reason.
Files:
Cargo.toml
🧬 Code graph analysis (3)
src/wireframe/mod.rs (1)
src/wireframe/preamble.rs (1)
handshake(28-28)
src/wireframe/handshake.rs (2)
src/protocol.rs (1)
write_handshake_reply(94-102)src/wireframe/preamble.rs (2)
preamble_bytes(67-79)world(196-200)
src/server/wireframe.rs (4)
src/wireframe/preamble.rs (1)
handshake(28-28)tests/handshake.rs (1)
handshake(16-52)tests/payload_reject.rs (1)
handshake(21-42)src/wireframe/handshake.rs (1)
install(35-47)
🪛 LanguageTool
docs/wireframe-users-guide.md
[grammar] ~369-~369: There seems to be a noun/verb agreement error. Did you mean “accepts” or “accepted”?
Context: ...he stream to the application. Transient accept failures trigger exponential backoff ca...
(SINGULAR_NOUN_VERB_AGREEMENT)
[uncategorized] ~379-~379: Use a comma before ‘so’ if it connects two independent clauses (unless they are closely connected and short).
Context: ...bind and accept failures as typed errors so callers can react appropriately.[^21] ...
(COMMA_COMPOUND_SENTENCE_2)
[uncategorized] ~493-~493: Possible missing comma found.
Context: ...through the same hooks used for regular frames so existing observability continues to ...
(AI_HYDRA_LEO_MISSING_COMMA)
[uncategorized] ~502-~502: Use a comma before ‘so’ if it connects two independent clauses (unless they are closely connected and short).
Context: ...ma.[^10][^12] - Emit version N on egress so clients observe a single schema. - Publ...
(COMMA_COMPOUND_SENTENCE_2)
[uncategorized] ~570-~570: Use a comma before ‘so’ if it connects two independent clauses (unless they are closely connected and short).
Context: ...come no-ops when the feature is disabled so instrumentation can stay in place.[^33]...
(COMMA_COMPOUND_SENTENCE_2)
🔍 Remote MCP Ref
Let me try searching for Hotline protocol specifications:The search results didn't directly provide Hotline protocol handshake error codes. Let me search for more specific information about the Wireframe framework and this implementation:The searches aren't returning specific Wireframe library documentation. Let me refocus on understanding the implementation by searching for any related GitHub repositories or specific error codes:Based on my search efforts, I've found that the Hotline protocol handshake details are documented in the Hotline Wiki. Now let me compile a summary of the relevant context for reviewing this PR.
Summary of Relevant Context for PR Review
Based on the available documentation and context provided, here are the key findings relevant to reviewing this PR:
Hotline Protocol Handshake
After establishing a TCP connection, both client and server start a handshake process to confirm that each complies with the other's requirements. The information provided in this initial data exchange identifies protocols and their versions used in the communication. In cases where the capabilities of one of the subjects do not comply with the other's requirements, the connection is dropped. In case of an error, client and server close the connection.
PR Implementation Scope
The PR introduces:
-
Error Code Mapping: The implementation maps decode errors to 4 handshake reply codes:
- Code 0: Success (valid Hotline handshake)
- Code 1: Invalid protocol
- Code 2: Unsupported version
- Code 3: Timeout (idle connection after 5 seconds)
-
Timeout Configuration: A 5-second (HANDSHAKE_TIMEOUT) idle timeout is enforced before routing occurs, with idle sockets dropped after this duration.
-
Integration Strategy: Uses Wireframe's preamble callback system (on_preamble_decode_success, on_preamble_decode_failure, preamble_timeout) rather than local patches to the vendored wireframe fork.
-
Test Coverage: Includes 4 behavioral test scenarios covering success, invalid protocol, unsupported version, and idle timeout cases.
Key Changes to Review
- Cargo.toml: Wireframe dependency upgraded from 0.1.0-alpha1 to 0.1.0 (stable release)
- New Module:
src/wireframe/handshake.rs- Public APIinstall(server, timeout)function - Error Mapping:
src/wireframe/preamble.rsenhanced to explicitly map HandshakeError variants to specific error codes - Documentation: Updated design.md, users-guide.md, roadmap.md with handshake semantics; new comprehensive wireframe-users-guide.md
Validation Considerations
The implementation appears to handle the documented Hotline protocol handshake flow correctly with appropriate error conditions and timeout enforcement. The move to stable wireframe (0.1.0) and removal of the vendored fork dependency improves maintainability.,
⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Sourcery review
- GitHub Check: build-test (postgres)
🔇 Additional comments (9)
Cargo.toml (1)
53-53: Upgrade wireframe to stable 0.1.0 and keep vendor in syncRetain this pinned SemVer and synchronise
vendor/wireframeandCargo.lockwith cratewireframe = "0.1.0", then run the fullcargo test --workspacesweep to validate the new upstream behaviour end to end..cargo/config.toml (1)
1-8: Use dev profile tuning and check-cfg consistentlyKeep this dev profile as the default for local work and ensure
make check-fmt,make lint, andcargo checkare re-run so the new[profile.dev]and[check-cfg]settings are exercised in CI and local workflows.Also applies to: 12-12
.markdownlint-cli2.jsonc (1)
13-20: Ignore vendored Wireframe docs in markdownlintLeave
vendor/wireframe/**in the ignore list so markdownlint enforces house style only on first-party Markdown while avoiding churn from upstream-vendored content.docs/roadmap.md (1)
52-59: Keep handshake roadmap status aligned with implementationRetain this completed task description; it accurately records the move to
wireframev0.1.0 preamble hooks, the five-second handshake timeout, and the associated test coverage, and it keeps the roadmap in sync with the implemented behaviour.src/wireframe/mod.rs (1)
7-8: Expose handshake adapter module publiclyKeep
pub mod handshake;so server bootstrap and tests can install the Hotline handshake layer viacrate::wireframe::handshakewhile preserving the transport/domain separation described in the module docs.docs/users-guide.md (1)
32-37: Document Hotline handshake semantics and timeout clearlyRetain this description of the 12-byte preamble, reply codes 0–3, and the five-second handshake timeout so the documented Wireframe server behaviour matches the installed handshake layer and associated tests.
tests/features/wireframe_handshake_hooks.feature (1)
1-22: Keep these handshake scenarios as the canonical behavioural specRetain these four scenarios; they cover the expected Hotline reply codes (0–3) and align with the BDD step patterns and handshake implementation, giving clear executable documentation for the handshake contract.
src/wireframe/handshake.rs (2)
28-47: Keepinstallas the single entry-point for Hotline handshake wiringLeave
installas the sole place that attaches the Hotline preamble handlers and timeout. The generic bounds and.on_preamble_decode_*().preamble_timeout(timeout)chain cleanly separate handshake behaviour from server bootstrap and make the five‑second idle cap easy to override in tests.
121-229: Retain these unit tests; they pin the wire protocol contractKeep the three unit tests (
replies_success,replies_handshake_errors,replies_timeout_for_idle_socket) as written. They exercise the public bootstrap (start_server+install) and assert the exact 8‑byte reply layout and error codes for success, invalid protocol, unsupported version, and timeout, which is the right level of detail for guarding future refactors.
…shake Update handshake error string comparisons to use starts_with instead of exact match, allowing for descriptive suffixes. Also add detailed error descriptions for InvalidProtocol and UnsupportedVersion handshake errors to Improve error handling and diagnostics. Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 5
♻️ Duplicate comments (1)
src/wireframe/handshake.rs (1)
320-324: Replace#[allow(unused_braces)]with#[expect].
#[allow]is forbidden by the coding guidelines. Use a narrowly scoped#[expect]with a reason instead. As per coding guidelines and past review comments.- // `rstest` reports this function as a fixture; rustc flags the required block - // as `unused_braces`, so suppress the false positive locally. - #[allow(unused_braces)] + #[expect( + unused_braces, + reason = "rstest fixture macro requires braces; rustc false positive" + )] #[fixture] fn world() -> HandshakeWorld { HandshakeWorld::new() }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (2)
src/wireframe/handshake.rs(1 hunks)src/wireframe/preamble.rs(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs
📄 CodeRabbit inference engine (AGENTS.md)
**/*.rs: Runmake check-fmt,make lint, andmake testbefore committing Rust code.
Clippy warnings MUST be disallowed in Rust code.
Fix any warnings emitted during Rust tests in the code itself rather than silencing them.
Where a Rust function is too long, extract meaningfully named helper functions adhering to separation of concerns and CQRS.
Where a Rust function has too many parameters, group related parameters in meaningfully named structs.
Where a Rust function is returning a large error, consider usingArcto reduce the amount of data returned.
Write unit and behavioural tests for new Rust functionality. Run both before and after making any change.
Every Rust module must begin with a module level (//!) comment explaining the module's purpose and utility.
Document public Rust APIs using Rustdoc comments (///) so documentation can be generated with cargo doc.
Prefer immutable data and avoid unnecessarymutbindings in Rust.
Handle errors with theResulttype instead of panicking in Rust where feasible.
Avoidunsafecode in Rust unless absolutely necessary, and document any usage clearly.
Place function attributes after doc comments in Rust.
Do not usereturnin single-line Rust functions.
Use predicate functions for Rust conditional criteria with more than two branches.
Lints must not be silenced in Rust except as a last resort.
Rust lint rule suppressions must be tightly scoped and include a clear reason.
Preferexpectoverallowin Rust error handling.
Userstestfixtures for shared setup in Rust tests.
Replace duplicated Rust tests with#[rstest(...)]parameterized cases.
Prefermockallfor mocks/stubs in Rust tests.
Useconcat!()to combine long string literals in Rust rather than escaping newlines with a backslash.
Prefer single-line versions of Rust functions where appropriate (e.g.,pub fn new(id: u64) -> Self { Self(id) }instead of a multi-line version).
Use NewTypes to model domain values and eliminate 'i...
Files:
src/wireframe/preamble.rssrc/wireframe/handshake.rs
⚙️ CodeRabbit configuration file
**/*.rs: * Seek to keep the cognitive complexity of functions no more than 9.
- Adhere to single responsibility and CQRS
- Place function attributes after doc comments.
- Do not use
returnin single-line functions.- Move conditionals with >2 branches into a predicate function.
- Avoid
unsafeunless absolutely necessary.- Every module must begin with a
//!doc comment that explains the module's purpose and utility.- Comments and docs must follow en-GB-oxendict (-ize / -yse / -our) spelling and grammar
- Lints must not be silenced except as a last resort.
#[allow]is forbidden.- Only narrowly scoped
#[expect(lint, reason = "...")]is allowed.- No lint groups, no blanket or file-wide suppression.
- Include
FIXME:with link if a fix is expected.- Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.
- Use
rstestfixtures for shared setup and to avoid repetition between tests.- Replace duplicated tests with
#[rstest(...)]parameterised cases.- Prefer
mockallfor mocks/stubs.- Prefer
.expect()over.unwrap()in tests..expect()and.unwrap()are forbidden outside of tests. Errors must be propagated.- Ensure that any API or behavioural changes are reflected in the documentation in
docs/- Ensure that any completed roadmap steps are recorded in the appropriate roadmap in
docs/- Files must not exceed 400 lines in length
- Large modules must be decomposed
- Long match statements or dispatch tables should be decomposed by domain and collocated with targets
- Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
- Environment access (env::set_var and env::remove_var) are always unsafe in Rust 2024 and MUST be marked as such
- For testing of functionality depending upon environment variables, dependency injection and...
Files:
src/wireframe/preamble.rssrc/wireframe/handshake.rs
🧬 Code graph analysis (1)
src/wireframe/handshake.rs (2)
src/protocol.rs (1)
write_handshake_reply(94-102)src/wireframe/preamble.rs (2)
preamble_bytes(67-79)world(196-200)
⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Sourcery review
- GitHub Check: build-test (postgres)
- GitHub Check: coverage
🔇 Additional comments (6)
src/wireframe/handshake.rs (6)
1-7: Module documentation is clear and purposeful.The
//!comment explains the module's role, the 5-second timeout, and testability via injectable timeouts. Good.
28-47: Public API is well-documented and idiomatic.The
installfunction cleanly wires handlers and timeout onto the server builder. The#[must_use]annotation correctly signals that the returned server must be consumed.
82-94: Verify behaviour for unmapped decode errors.When
error_code_for_decodereturnsNone, no reply is sent andOk(())is returned. Confirm this is intentional—clients may hang awaiting a reply before the server closes the socket.
143-161: Test server helper is well-designed.
start_serveraccepts a configurable timeout, enabling fast tests for idle-timeout scenarios. The oneshot shutdown channel ensures clean teardown.
163-229: Unit tests provide good coverage.The tests exercise success, invalid protocol, unsupported version, and idle-timeout paths. Parameterised cases via
#[rstest]reduce duplication. Assertions use.expect()with descriptive messages as per guidelines.
354-365: BDD scenario bindings are correctly wired.The four scenarios cover the key handshake outcomes: success, invalid protocol, unsupported version, and timeout. The
let _ = world;pattern is the expected idiom forrstest_bddscenario functions.
Update documentation to explain that idle sockets trigger `preamble_timeout`, which invokes `on_preamble_decode_failure` to emit the 8-byte Hotline timeout reply before closing the connection after the five-second `HANDSHAKE_TIMEOUT`. Also fix minor punctuation and formatting issues in the wireframe users guide to improve readability. Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
…tokens - Introduced HANDSHAKE_INVALID_PROTOCOL_TOKEN and HANDSHAKE_UNSUPPORTED_VERSION_TOKEN constants in protocol.rs to unify error message tokens. - Improved error message creation by using these constants in preamble.rs and handshake.rs. - Removed inline handshake test utility functions from handshake.rs. - Added a new wireframe/test_helpers.rs module to centralize handshake test utilities like constructing preamble buffers and reading handshake replies. - Updated handshake.rs and tests to use the shared test helpers module for cleaner, more maintainable test code. This refactoring improves code reuse, consistency in error messages, and test maintainability. Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (2)
src/wireframe/handshake.rs (2)
301-303: Replace forbidden#[allow]with#[expect].The coding guidelines explicitly forbid
#[allow]in all circumstances. Use#[expect]with a reason instead.Apply this diff:
- // `rstest` reports this function as a fixture; rustc flags the required block - // as `unused_braces`, so suppress the false positive locally. - #[allow(unused_braces)] + #[expect( + unused_braces, + reason = "rstest fixture macro expansion triggers unused_braces false positive" + )] #[fixture] fn world() -> HandshakeWorld { HandshakeWorld::new() }As per coding guidelines forbidding
#[allow].
314-321: Replace forbidden#[allow]with#[expect].This
#[allow(clippy::needless_pass_by_value)]violates the coding guidelines. Use#[expect]with a reason.Apply this diff:
- #[allow(clippy::needless_pass_by_value)] + #[expect( + clippy::needless_pass_by_value, + reason = "rstest-bdd step parameters must be owned; keep String until macro supports &str captures" + )] #[when("I send a Hotline handshake with protocol \"{tag}\" and version {version}")] fn when_custom(world: &HandshakeWorld, tag: String, version: u16) {As per coding guidelines: "
#[allow]is forbidden. Only narrowly scoped#[expect(lint, reason = "...")]is allowed."
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (4)
src/protocol.rs(1 hunks)src/wireframe/handshake.rs(1 hunks)src/wireframe/preamble.rs(2 hunks)src/wireframe/test_helpers.rs(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs
📄 CodeRabbit inference engine (AGENTS.md)
**/*.rs: Runmake check-fmt,make lint, andmake testbefore committing Rust code.
Clippy warnings MUST be disallowed in Rust code.
Fix any warnings emitted during Rust tests in the code itself rather than silencing them.
Where a Rust function is too long, extract meaningfully named helper functions adhering to separation of concerns and CQRS.
Where a Rust function has too many parameters, group related parameters in meaningfully named structs.
Where a Rust function is returning a large error, consider usingArcto reduce the amount of data returned.
Write unit and behavioural tests for new Rust functionality. Run both before and after making any change.
Every Rust module must begin with a module level (//!) comment explaining the module's purpose and utility.
Document public Rust APIs using Rustdoc comments (///) so documentation can be generated with cargo doc.
Prefer immutable data and avoid unnecessarymutbindings in Rust.
Handle errors with theResulttype instead of panicking in Rust where feasible.
Avoidunsafecode in Rust unless absolutely necessary, and document any usage clearly.
Place function attributes after doc comments in Rust.
Do not usereturnin single-line Rust functions.
Use predicate functions for Rust conditional criteria with more than two branches.
Lints must not be silenced in Rust except as a last resort.
Rust lint rule suppressions must be tightly scoped and include a clear reason.
Preferexpectoverallowin Rust error handling.
Userstestfixtures for shared setup in Rust tests.
Replace duplicated Rust tests with#[rstest(...)]parameterized cases.
Prefermockallfor mocks/stubs in Rust tests.
Useconcat!()to combine long string literals in Rust rather than escaping newlines with a backslash.
Prefer single-line versions of Rust functions where appropriate (e.g.,pub fn new(id: u64) -> Self { Self(id) }instead of a multi-line version).
Use NewTypes to model domain values and eliminate 'i...
Files:
src/protocol.rssrc/wireframe/test_helpers.rssrc/wireframe/handshake.rssrc/wireframe/preamble.rs
⚙️ CodeRabbit configuration file
**/*.rs: * Seek to keep the cognitive complexity of functions no more than 9.
- Adhere to single responsibility and CQRS
- Place function attributes after doc comments.
- Do not use
returnin single-line functions.- Move conditionals with >2 branches into a predicate function.
- Avoid
unsafeunless absolutely necessary.- Every module must begin with a
//!doc comment that explains the module's purpose and utility.- Comments and docs must follow en-GB-oxendict (-ize / -yse / -our) spelling and grammar
- Lints must not be silenced except as a last resort.
#[allow]is forbidden.- Only narrowly scoped
#[expect(lint, reason = "...")]is allowed.- No lint groups, no blanket or file-wide suppression.
- Include
FIXME:with link if a fix is expected.- Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.
- Use
rstestfixtures for shared setup and to avoid repetition between tests.- Replace duplicated tests with
#[rstest(...)]parameterised cases.- Prefer
mockallfor mocks/stubs.- Prefer
.expect()over.unwrap()in tests..expect()and.unwrap()are forbidden outside of tests. Errors must be propagated.- Ensure that any API or behavioural changes are reflected in the documentation in
docs/- Ensure that any completed roadmap steps are recorded in the appropriate roadmap in
docs/- Files must not exceed 400 lines in length
- Large modules must be decomposed
- Long match statements or dispatch tables should be decomposed by domain and collocated with targets
- Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
- Environment access (env::set_var and env::remove_var) are always unsafe in Rust 2024 and MUST be marked as such
- For testing of functionality depending upon environment variables, dependency injection and...
Files:
src/protocol.rssrc/wireframe/test_helpers.rssrc/wireframe/handshake.rssrc/wireframe/preamble.rs
🧬 Code graph analysis (3)
src/wireframe/test_helpers.rs (1)
src/wireframe/preamble.rs (1)
preamble_bytes(74-86)
src/wireframe/handshake.rs (4)
src/protocol.rs (1)
write_handshake_reply(99-107)src/wireframe/preamble.rs (1)
preamble_bytes(74-86)src/wireframe/test_helpers.rs (2)
preamble_bytes(14-26)recv_reply(29-33)tests/runtime_selection_bdd.rs (1)
runtime(26-26)
src/wireframe/preamble.rs (1)
src/protocol.rs (1)
parse_handshake(64-79)
🪛 GitHub Actions: CI
src/wireframe/handshake.rs
[error] 19-19: unresolved import super::test_helpers (E0432) during cargo clippy --no-default-features --features postgres -- -D warnings
🪛 GitHub Check: build-test (postgres)
src/wireframe/handshake.rs
[failure] 19-19:
unresolved import super::test_helpers
🪛 GitHub Check: build-test (sqlite)
src/wireframe/handshake.rs
[failure] 19-19:
unresolved import super::test_helpers
🪛 GitHub Check: coverage
src/wireframe/handshake.rs
[failure] 19-19:
unresolved import super::test_helpers
⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Sourcery review
🔇 Additional comments (13)
src/wireframe/test_helpers.rs (2)
1-11: LGTM: Clean test utilities module.The module-level documentation clearly explains the purpose, and the
#[cfg(test)]gating ensures these utilities are only compiled during testing.
29-33: LGTM:recv_replyis appropriate for test helpers.The use of
.expect()is acceptable here since the entire module is gated with#[cfg(test)].src/wireframe/preamble.rs (2)
14-21: LGTM: Token imports are correctly scoped.The explicit error token constants enable structured error mapping without brittle substring matching.
62-71: LGTM: Structured error mapping aligns with handshake protocol.The
decode_error_for_handshakefunction now uses the shared token constants, ensuring consistent error signalling between the preamble decoder and the handshake reply logic. This addresses the previous review feedback requesting centralised sentinel strings.Based on learnings from past review comments.
src/protocol.rs (1)
34-37: LGTM: Error token constants are well-defined.The sentinel constants provide a single source of truth for handshake error signalling, preventing drift between the preamble decoder and reply logic. The documentation clearly explains their purpose.
Based on learnings from past review comments requesting centralised tokens.
src/wireframe/handshake.rs (8)
1-7: LGTM: Module documentation is clear and comprehensive.The doc comment explains the module's purpose, its integration with the Wireframe runtime, and the testing flexibility around timeout overrides.
30-49: LGTM:installAPI is clean and well-documented.The function signature is appropriately generic, the documentation explains the timeout parameter's purpose, and the implementation is straightforward. The use of
#[must_use]ensures callers don't accidentally discard the configured server.
51-69: LGTM: Handler implementations follow the Wireframe patterns correctly.The success and failure handlers correctly return boxed futures with the appropriate signatures. The error code mapping delegates to
error_code_for_decode, maintaining separation of concerns.
71-96: LGTM: Error mapping uses structured predicates.The refactored approach with
is_invalid_protocolandis_unsupported_versionpredicates addresses earlier review feedback about multi-branch conditionals. The use ofstarts_withon the shared token constants provides robust error detection.Based on learnings from past review comments requesting predicate extraction.
140-159: LGTM: Success test validates the OK reply correctly.The test constructs a valid handshake, writes it, receives the reply, and verifies the reply code. The use of
.expect()with descriptive messages is appropriate for test code.
161-186: LGTM: Parameterised error tests cover invalid protocol and unsupported version.The
#[rstest]parameterisation elegantly covers both error cases without duplication, and the test logic correctly adjusts the version based on the expected error code.
188-206: LGTM: Timeout test validates idle connection handling.The test uses a shortened timeout (100ms) to avoid slow tests, connects without sending data, and correctly verifies that the server replies with
HANDSHAKE_ERR_TIMEOUT.
229-297: BDD world implementation is functional.The
HandshakeWorldstructure withRefCell-based state management and a manualRuntimeworks correctly, though it adds some cognitive overhead. The implementation properly manages server lifecycle via theDroptrait. As noted in past review comments, this could be simplified with async fixtures oncerstest-bddv0.2.0 is available, but the current approach is acceptable.Based on learnings from past review comments noting that full async refactoring is pending library support.
…t_helpers module - Removed `preamble_bytes` function from `preamble.rs` test module. - Exported `test_helpers` module in `wireframe/mod.rs`. - Adjusted imports in `handshake.rs` and `preamble.rs` tests to use `wireframe::test_helpers::preamble_bytes`. - Added `#[expect]` attributes to suppress false positives in handshake test fixtures and steps. This cleans up test helper usage by centralizing common utilities and improves test code hygiene. Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
Removed an unused `#[expect]` attribute and the unused `test_helpers` import in the handshake module's tests, cleaning up warning suppressions that were no longer needed. Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
Added #[expect(unused_braces)] to the world fixture in handshake tests to suppress a false positive warning triggered by the rstest fixture macro expansion. Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/wireframe/preamble.rs (2)
241-243: Replace#[allow]with#[expect].
#[allow]is forbidden per coding guidelines. Use#[expect]with a reason. As per coding guidelines.- #[allow(clippy::needless_pass_by_value)] + #[expect( + clippy::needless_pass_by_value, + reason = "rstest-bdd step parameters must be owned; keep String until macro supports &str \ + captures" + )] #[then("the sub-protocol is \"{tag}\"")] fn then_sub_protocol(world: &HandshakeWorld, tag: String) {
276-278: Replace#[allow]with#[expect].Same issue as above.
#[allow]is forbidden. As per coding guidelines.- #[allow(clippy::needless_pass_by_value)] + #[expect( + clippy::needless_pass_by_value, + reason = "rstest-bdd step parameters must be owned; keep String until macro supports &str \ + captures" + )] #[then("decoding fails with \"{message}\"")] fn then_failure(world: &HandshakeWorld, message: String) {
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (3)
src/wireframe/handshake.rs(1 hunks)src/wireframe/mod.rs(1 hunks)src/wireframe/preamble.rs(4 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs
📄 CodeRabbit inference engine (AGENTS.md)
**/*.rs: Runmake check-fmt,make lint, andmake testbefore committing Rust code.
Clippy warnings MUST be disallowed in Rust code.
Fix any warnings emitted during Rust tests in the code itself rather than silencing them.
Where a Rust function is too long, extract meaningfully named helper functions adhering to separation of concerns and CQRS.
Where a Rust function has too many parameters, group related parameters in meaningfully named structs.
Where a Rust function is returning a large error, consider usingArcto reduce the amount of data returned.
Write unit and behavioural tests for new Rust functionality. Run both before and after making any change.
Every Rust module must begin with a module level (//!) comment explaining the module's purpose and utility.
Document public Rust APIs using Rustdoc comments (///) so documentation can be generated with cargo doc.
Prefer immutable data and avoid unnecessarymutbindings in Rust.
Handle errors with theResulttype instead of panicking in Rust where feasible.
Avoidunsafecode in Rust unless absolutely necessary, and document any usage clearly.
Place function attributes after doc comments in Rust.
Do not usereturnin single-line Rust functions.
Use predicate functions for Rust conditional criteria with more than two branches.
Lints must not be silenced in Rust except as a last resort.
Rust lint rule suppressions must be tightly scoped and include a clear reason.
Preferexpectoverallowin Rust error handling.
Userstestfixtures for shared setup in Rust tests.
Replace duplicated Rust tests with#[rstest(...)]parameterized cases.
Prefermockallfor mocks/stubs in Rust tests.
Useconcat!()to combine long string literals in Rust rather than escaping newlines with a backslash.
Prefer single-line versions of Rust functions where appropriate (e.g.,pub fn new(id: u64) -> Self { Self(id) }instead of a multi-line version).
Use NewTypes to model domain values and eliminate 'i...
Files:
src/wireframe/handshake.rssrc/wireframe/preamble.rssrc/wireframe/mod.rs
⚙️ CodeRabbit configuration file
**/*.rs: * Seek to keep the cognitive complexity of functions no more than 9.
- Adhere to single responsibility and CQRS
- Place function attributes after doc comments.
- Do not use
returnin single-line functions.- Move conditionals with >2 branches into a predicate function.
- Avoid
unsafeunless absolutely necessary.- Every module must begin with a
//!doc comment that explains the module's purpose and utility.- Comments and docs must follow en-GB-oxendict (-ize / -yse / -our) spelling and grammar
- Lints must not be silenced except as a last resort.
#[allow]is forbidden.- Only narrowly scoped
#[expect(lint, reason = "...")]is allowed.- No lint groups, no blanket or file-wide suppression.
- Include
FIXME:with link if a fix is expected.- Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.
- Use
rstestfixtures for shared setup and to avoid repetition between tests.- Replace duplicated tests with
#[rstest(...)]parameterised cases.- Prefer
mockallfor mocks/stubs.- Prefer
.expect()over.unwrap()in tests..expect()and.unwrap()are forbidden outside of tests. Errors must be propagated.- Ensure that any API or behavioural changes are reflected in the documentation in
docs/- Ensure that any completed roadmap steps are recorded in the appropriate roadmap in
docs/- Files must not exceed 400 lines in length
- Large modules must be decomposed
- Long match statements or dispatch tables should be decomposed by domain and collocated with targets
- Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
- Environment access (env::set_var and env::remove_var) are always unsafe in Rust 2024 and MUST be marked as such
- For testing of functionality depending upon environment variables, dependency injection and...
Files:
src/wireframe/handshake.rssrc/wireframe/preamble.rssrc/wireframe/mod.rs
🧬 Code graph analysis (3)
src/wireframe/handshake.rs (4)
src/protocol.rs (1)
write_handshake_reply(99-107)src/wireframe/test_helpers.rs (2)
preamble_bytes(14-26)recv_reply(29-33)tests/runtime_selection_bdd.rs (1)
runtime(26-26)src/wireframe/preamble.rs (1)
world(194-198)
src/wireframe/preamble.rs (2)
src/protocol.rs (1)
parse_handshake(64-79)src/wireframe/test_helpers.rs (1)
preamble_bytes(14-26)
src/wireframe/mod.rs (1)
src/wireframe/preamble.rs (1)
handshake(35-35)
🪛 GitHub Actions: CI
src/wireframe/handshake.rs
[error] 300-300: this lint expectation is unfulfilled (during cargo-llvm-cov run).
🪛 GitHub Check: build-test (postgres)
src/wireframe/handshake.rs
[failure] 300-300:
this lint expectation is unfulfilled
🪛 GitHub Check: build-test (sqlite)
src/wireframe/handshake.rs
[failure] 300-300:
this lint expectation is unfulfilled
🪛 GitHub Check: coverage
src/wireframe/handshake.rs
[failure] 300-300:
this lint expectation is unfulfilled
🔍 Remote MCP
The documentation search isn't returning the specific Wireframe or Hotline protocol docs. Let me check if there's a GitHub repository I can access through Deepwiki for the project context:The web search results are about TLS/SSL handshakes, not the specific Hotline protocol or Wireframe library. These appear to be project-internal concepts. Let me search for information about the testing approach and behavior-driven development patterns used:Good! I found relevant information about Cucumber BDD testing framework for Rust. Now let me search for information about tokio async timeouts and preamble decode patterns, which are mentioned in the PR:Excellent! I now have good contextual information about Cucumber BDD testing and Tokio timeouts. Let me compile my findings into a concise summary for the PR reviewer.
Additional Context Summary for PR Review
Based on my research, here is relevant context that would help in reviewing this pull request:
Behavior-Driven Development Testing Approach
The PR includes a new feature file (tests/features/wireframe_handshake_hooks.feature) using the Cucumber BDD framework. Cucumber is a specification for running tests in a BDD (behavior-driven development) style workflow that provides a human-readable syntax for the definition of features, via the language Gherkin. Cucumber is a testing framework for Rust with async support. The feature scenarios in the PR test four distinct handshake outcomes: successful handshakes, invalid protocol errors, unsupported version errors, and idle connection timeouts—each expressed as human-readable Given-When-Then scenarios.
Async Timeout Implementation Patterns
The handshake module implements a 5-second idle timeout. Timeouts in Tokio can be implemented using tokio::time::timeout(), which wraps an async operation and returns an error if it doesn't complete within the specified duration. Timeout wraps a future or stream, setting an upper bound to the amount of time it is allowed to execute. If the future or stream does not complete in time, then it is canceled and an error is returned. The PR's use of preamble_timeout(timeout) to enforce the five-second idle cap follows this pattern—sockets that don't complete a handshake within the timeout window will trigger the on_preamble_decode_failure handler with a timeout error code.
Test Helper Architecture
The new src/wireframe/test_helpers.rs provides utilities for test infrastructure:
preamble_bytes()constructs a Hotline preamble buffer for test scenariosrecv_reply()is an async function that reads exactly REPLY_LEN bytes from a TcpStream
This pattern aligns with how Cucumber tests in Rust often require shared test utilities to construct and verify protocol-level data.
Dependency and Version Management
The PR upgrades the Wireframe library from version 0.1.0-alpha1 to 0.1.0, migrating from a pre-release to stable. This version bump likely corresponds to the introduction of the preamble callback APIs (on_preamble_decode_success, on_preamble_decode_failure, preamble_timeout) that the new handshake module depends on. The development Cargo profile changes (opt-level=1, debug=1, incremental compilation) optimize the development iteration speed.
Key Review Points:
- The test helpers and feature scenarios comprehensively cover the four error paths (OK, invalid protocol, unsupported version, timeout)
- The timeout is enforced at the async level via Tokio, ensuring idle sockets are dropped within the 5-second window
- The public
handshake::install()API provides a clean, composable interface for integrating the handshake layer into the server bootstrap
⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Sourcery review
🔇 Additional comments (18)
src/wireframe/mod.rs (1)
7-10: LGTM!The module declarations are correctly structured:
handshakeis public for server integration, andtest_helpersis appropriately gated with#[cfg(test)]to prevent compilation in production builds.src/wireframe/preamble.rs (4)
14-21: LGTM!The imports now use the centralised protocol tokens (
HANDSHAKE_INVALID_PROTOCOL_TOKEN,HANDSHAKE_UNSUPPORTED_VERSION_TOKEN), ensuring bothpreamble.rsandhandshake.rsreference the same constants. This addresses the earlier review feedback about preventing silent drift.
62-71: LGTM!The match-based error mapping with stable sentinel tokens is robust and aligns with the handshake module's predicates.
82-85: LGTM!Test imports correctly reference the shared
test_helpers::preamble_bytes, eliminating duplication.
165-168: LGTM!BDD test imports correctly consolidated to use shared helpers.
src/wireframe/handshake.rs (13)
1-7: LGTM!The module-level doc comment clearly explains the purpose, the 8-byte reply semantics, and the configurable timeout for test flexibility.
9-28: LGTM!Imports are well-organised and minimal. The use of
BoxFuturefromfutures_utilis appropriate for the callback signatures.
30-49: LGTM!The
installfunction provides a clean, composable API. The#[must_use]attribute correctly signals that the returned server must not be discarded. Documentation is clear about usingHANDSHAKE_TIMEOUTfor production.
51-55: LGTM!The success handler correctly writes
HANDSHAKE_OKand ignores the preamble content (validation already passed during decode).
57-69: LGTM!The failure handler correctly maps decode errors to Hotline error codes. Returning
Ok(())for unmapped errors is appropriate—the connection will be closed by the Wireframe runtime regardless.
71-96: LGTM!The error mapping uses centralised sentinel tokens and predicate functions, addressing earlier review feedback about brittle string matching. The structured
Iovariant matching forTimedOutis robust.
120-138: LGTM!The
start_serverhelper is well-structured, using.expect()with descriptive messages. Spawning the server in a task with shutdown channel is a clean pattern.
140-159: LGTM!The success test correctly validates the protocol ID and error code in the reply. Uses
.expect()with descriptive context.
161-186: LGTM!Good use of
#[rstest]parameterised cases to cover both invalid protocol and unsupported version scenarios without duplication.
188-206: LGTM!The timeout test uses a short 100ms timeout to keep tests fast whilst still exercising the timeout path. The outer 1-second timeout ensures the test fails fast if the server doesn't respond.
229-297: LGTM!The
HandshakeWorldimplementation is functional. TheRefCell-based approach and manualRuntimeare noted in issue #235 for future simplification oncerstest-bddv0.2.0 provides better async support.
315-326: LGTM!The
#[expect]attribute onwhen_customis correctly formed with a reason, complying with coding guidelines.
338-349: LGTM!The scenario declarations correctly reference the feature file and cover all four handshake outcomes: success, invalid protocol, unsupported version, and timeout.
- Added allow attribute in handshake.rs to avoid unfulfilled lint expectations during tests. - Replaced clippy::needless_pass_by_value allow with expect and reason comments in preamble.rs for BDD test functions to clarify owned parameter usage requirements. Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Gates Passed
6 Quality Gates Passed
See analysis details in CodeScene
Quality Gate Profile: Pay Down Tech Debt
Want more control? Customize Code Health rules or catch issues early with our IDE extension and CLI tool.
Summary
handshake::install(server, timeout)and wired via.accept_backoff(backoff).src/wireframe/mod.rsnow exports the handshake module.Changes
src/wireframe/handshake.rsinstall(server, timeout)-> configures Hotline preamble handling by:DecodeErrorto Hotline error codespreamble_timeout(timeout): enforce the handshake idle timeoutsuccess_handler()-> writesHANDSHAKE_OKreplyfailure_handler()-> writes appropriate error codes based onDecodeErrorerror_code_for_decode()-> maps decode errors toHANDSHAKE_ERR_INVALID,HANDSHAKE_ERR_UNSUPPORTED_VERSION,HANDSHAKE_ERR_TIMEOUTsrc/server/wireframe.rshandshake::install(server, protocol::HANDSHAKE_TIMEOUT).accept_backoff(backoff)HANDSHAKE_TIMEOUTsrc/wireframe/mod.rspub mod handshake;exposes handshake module to userstests/features/wireframe_handshake_hooks.featurewith scenarios for success, invalid protocol, unsupported version, and idle timeouthandshake.rsto verify success, error mapping, and timeout behaviordocs/design.md: notes that the Wireframe bootstrap mirrors legacy handshake semantics via preamble callbacks and a 5-secondHANDSHAKE_TIMEOUTdocs/users-guide.md: updated to reflect that the Wireframe listener decodes Hotline 12-byte handshake preamble, sends 8-byte replies, and drops idle sockets after the handshake timeoutdocs/roadmap.md: updated acceptance criteria to include five-second handshake timeout handling as part of the handshake wiringWhy
Test plan
tests/features/wireframe_handshake_hooks.featureto validate end-to-end handshake behaviourHow to test locally
cargo test --workspaceHANDSHAKE_TIMEOUTor override with a shorter duration in tests as neededNotes
WireframeServer::new(...).with_preamble::<HotlinePreamble>() // plus handshake install for timeout handlingTask link
Summary by Sourcery
Integrate a reusable Wireframe handshake module for Hotline that wires preamble hooks, reply codes, and timeouts into the server bootstrap while updating docs, tests, and dependencies to reflect the new behaviour.
New Features:
Enhancements:
Documentation:
Tests:
📎 Task: https://www.terragonlabs.com/task/0423192a-d49c-4450-ab18-2085cdc18a71