Skip to content

Feat/platform agnostic tcp connectors - #250

Open
lxsaah wants to merge 14 commits into
mainfrom
feat/platform-agnostic-connectors
Open

Feat/platform agnostic tcp connectors#250
lxsaah wants to merge 14 commits into
mainfrom
feat/platform-agnostic-connectors

Conversation

@lxsaah

@lxsaah lxsaah commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Wave B of design 052, first connector: the TCP connector moves onto the runtime-neutral I/O layer wave A added. The adapter owns the socket; this crate contributes only length-prefix framing.

What this delivers

  • One implementation instead of two runtime forks. tokio_transport.rs (302 lines) and embassy_transport.rs (591) are deleted, and with them the entire all(tokio-runtime, embassy-runtime) aliasing block — TokioTcpServer/EmbassyTcpServer, TokioTcpConnection/EmbassyTcpConnection and friends existed only to disambiguate two implementations of the same concept. aimdb-tcp-connector/src goes from 1 044 lines to 487.
  • Zero unsafe impls in any connector crate. The three here were the last outside the adapters.
  • Nothing runtime-specific in the library graph. No tokio, embassy-net, embassy-futures or embedded-io-async — checked with cargo tree, not by deleting files and assuming. tokio is a dev-dependency; embassy-net is reachable only under the _test-embassy-loopback feature.

Breaking

pub use connector::{framed_dialer, framed_dialer_at, framed_dialer_bounded, framed_listener,
                    framed_listener_bounded, split_host_port, EndpointError,
                    TcpClient, TcpServer, DEFAULT_PORT};
  • TcpServer::new takes an already-bound listener rather than a bind string — the adapter does the binding.
  • Framer::encode returns Result<(), FrameFault>, and next_frame's error widens from () to FrameFault (aimdb-core). Both production framers and every test double are updated.
  • TokioNet::listen returns std::io::Result instead of TransportResult (aimdb-tokio-adapter). Callers using ? or .expect(..) need no change.
  • split_host_port is fallible, returning EndpointError, and now lives in aimdb_core::session::endpoint — re-exported here under the same path.
  • TransportError gains Framing and Busy. It is #[non_exhaustive], so this breaks no downstream match.

What review changed

The first four commits did the migration. The remaining nine came out of review and are most of this diff's insertions. Almost all of it restores behaviour the first cut dropped, rather than adding anything new.

Errors the migration stopped reporting. Each of these had become silent:

was now
a corrupt length prefix treated as recoverable; the connection read on forever, reinterpreting payload as headers FrameFault::FatalTransportError::Framing, connection closes
an oversized outbound frame dropped, and send returned Ok(()) dropped and reported — the drop was right, the silence was not
a bind failure every cause flattened to a bare Io AddrInUse / PermissionDenied / AddrNotAvailable survive
a second dial on Embassy's single socket bare Io, indistinguishable from a dead peer TransportError::Busy, and the existing dial-failed log names it

Configurability the migration dropped. max_frame had become unreachable: the framed type aliases used fn() -> LengthFramer, which is nameable but stateless, so it could only ever produce the 64 KiB default. A LengthFramers factory carries the bound and is still nameable, restoring TcpServer::max_frame(n), TcpClient::bounded(..) and the *_bounded constructors.

Endpoint grammar: one implementation instead of three. split_host_port mangled unbracketed IPv6 (fe80::1 → host fe80:, port 1) and silently substituted the default port for a malformed one, so a typo dialled a different — possibly live — service. It is now correct, fallible, and moved into aimdb-core because aimdb-client had grown a 56-line private copy and cannot depend on this crate: parse_endpoint resolves tcp:// URLs whether or not the TCP transport is compiled in. That copy is gone; the client keeps only its own policy, that a URL must name its port.

One related bug is not fixed here: aimdb_core::connector::parse_connector_url has the same unbracketed-IPv6 and silent-port behaviour. It is pre-existing on main, backs ConnectorUrl for mqtt/knx/ws, and changing it needs its own testing per connector.

Worth a look in review

Framer reports how badly it failed, not just that it did. A self-delimiting format resyncs on its next delimiter; a length prefix has none. Only the framer knows which case it is in, so FrameFault lets it say, rather than the connection guessing. COBS keeps its existing skip-and-resync behaviour unchanged.

TcpServer is single-use. The listener is moved in and taken on first build, so a second build fails rather than silently serving nothing. AimDbBuilder consumes its connectors, so this is unreachable through the normal path; a direct caller wanting to serve again constructs a new server with a new listener.

EmbassyTcpDialer: Clone shares one socket. The derive exists so a framed dialer can satisfy SessionClientConnector's bound. A clone dialing while another handle holds the link now gets Busy — waiting instead would be worse, since TcpSocketSlot holds room for exactly one waiter. A second concurrent connection needs a second EmbassyNet::tcp with its own buffers.

Verification

Test counts on the paths this touches: framing 10, connector round-trip 5, tokio round-trip 1, Embassy loopback 5, core endpoint grammar 7. Run per feature leg rather than as one sweep, since --all-features does not compose here: aimdb-core, aimdb-client (default / transport-serial / transport-tcp), aimdb-tcp-connector (_test-tokio, _test-embassy-loopback), aimdb-tokio-adapter, aimdb-embassy-adapter.

Two of the new tests are mutation-verified — stubbing the code they guard makes them fail, which the previous suite would not have done for either:

  • a_policy_allowed_write_lands_through_the_built_server — drops with_config → the write returns Denied; stubs apply_writablerecord.list reports writable: false.
  • a_cloned_dialer_reports_a_busy_socket — a clone dialing over a live link must report Busy, not Io.

Also covered per feature leg: clippy -D warnings, thumbv7em-none-eabihf for the no_std build, and cargo doc with RUSTDOCFLAGS=-D warnings.

- Updated `Cargo.toml` and `Cargo.lock` to remove unnecessary dependencies and streamline the project.
- Modified `endpoint.rs` to utilize the new `framed_dialer_at` function for TCP connections.
- Enhanced `connector.rs` with a new `split_host_port` function to handle host:port parsing and added a `framed_dialer_at` function for cleaner dialing.
- Removed the `embassy_transport.rs` and `tokio_transport.rs` files as they are superseded by the new connector implementation.
- Updated `lib.rs` to reflect the changes in module structure and removed deprecated transport modules.
- Adjusted example in `tcp_demo.rs` to align with the new dialing approach.
@lxsaah lxsaah changed the title Feat/platform agnostic connectors Feat/platform agnostic tcp connectors Sep 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant