Skip to content

Feat/platform agnostic tcp connectors - #250

Merged
lxsaah merged 16 commits into
mainfrom
feat/platform-agnostic-connectors
Sep 9, 2026
Merged

Feat/platform agnostic tcp connectors#250
lxsaah merged 16 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
@lxsaah
lxsaah merged commit e149bb3 into main Sep 9, 2026
9 checks passed
@lxsaah
lxsaah deleted the feat/platform-agnostic-connectors branch September 9, 2026 17:55
lxsaah added a commit that referenced this pull request Sep 9, 2026
Brings in the runtime-neutral TCP (#250) and serial (#251) connectors.

Conflict was in the Embassy KNX demo's imports, where both sides moved:
main rehomed `SerialServer` out of `embassy_transport` and wrapped its
halves in the new `EmbassyUart`, while this branch replaced
`KnxConnectorBuilder` with the sans-io `KnxConnector::new`. Kept both.

Also adds the embassy adapter's `net` feature to the demo. It used to
arrive transitively through the serial connector's `embassy-runtime`
feature; #251 made that crate runtime-neutral, so nothing pulls the
adapter in on its behalf any more and `EmbassyNet::udp`/`EmbassyDelay`
have to be requested where they are used.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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