Skip to content

netbat: add bounded response decoding and blocking client call #228

Description

@heyoub

Downstream pressure

Texo is composing a resumable remote read-replica circuit over syncbat + netbat. The server path is complete and bounded (serve_tcp_listener, serve_stream, encode_response), and the client can use encode_request, but the public client-side response surface is asymmetric:

let request = netbat::encode_request("texo.replica.page.read", input);
stream.write_all(&request)?;
// No public decode_response / call_tcp equivalent exists.

decode_line only accepts NETBAT/1 CALL ... request frames. ResponseFrame is constructible, but OK <hex-output>\n / ERR <code> <hex-message>\n cannot be decoded through a public bounded API.

A downstream therefore has to duplicate protocol parsing, hex bounds, stable error-code handling, line termination, read deadlines, and EOF/truncation classification. That makes a supposedly shared boundary protocol less reusable and risks subtly different security limits per consumer.

Minimal reproducer

Against v0.10.0 / current main:

let wire = netbat::encode_response(Ok(b"hello"));
assert_eq!(wire, b"OK 68656c6c6f\n");

// Desired public inverse; no equivalent exists today.
let response = netbat::decode_response(&wire, &netbat::Limits::default())?;
assert_eq!(response.output(), b"hello");

The same gap exists for a one-request blocking client helper: downstream code must separately implement connect/read/write timeouts and bounded newline reads even though the server has canonical handling for those concerns.

Proposed neutral surface

Names are maintainer discretion; the contract matters:

pub enum DecodedResponse {
    Ok(ResponseFrame),
    Err { code: String, message: Vec<u8> },
}

pub fn decode_response(line: &[u8], limits: &Limits)
    -> Result<DecodedResponse, NetbatError>;

pub fn call_tcp(
    addr: SocketAddr,
    operation: &str,
    input: &[u8],
    limits: &Limits,
    timeouts: IoTimeouts,
) -> Result<ResponseFrame, NetbatError>;

call_tcp may be deferred if the decoder lands first. No async runtime is requested.

Acceptance criteria

  • decode_response(encode_response(Ok(bytes))) round-trips arbitrary bounded bytes.
  • ERR code grammar is stable and validated; message bytes are hex-decoded under an explicit cap.
  • Missing newline, extra fields, odd/non-hex payloads, unknown status tokens, EOF, and oversized lines fail with typed stable errors.
  • max_output_bytes is enforced on decoded OK bytes, not just hex text size.
  • ERR messages use max_stream_error_message_bytes or a dedicated request-response error cap.
  • A blocking client helper, if included, enforces absolute/bounded connect, read, and write deadlines and never performs an unbounded read_line.
  • Property tests cover encode/decode round trips and total parsing over arbitrary input.
  • Mutation tests kill removed size checks, token checks, and newline/truncation handling.

Downstream workaround

Texo will isolate a small parser/client in compat::netbat, tested against netbat::encode_response golden frames. It will be deleted in favor of the upstream API when shipped; no Texo vocabulary is needed upstream.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions