Docs · Drivers

Build a driver

hop-core has no bearer system of its own, it only moves bytes. A drivergives it bearers: it implements a small trait for one transport or platform and registers it with the node. Everything else, sealing, addressing, routing, fragmentation, retransmit, dedup, delivery, stays in the core.

What the core does vs. what your driver does

hop-core owns

  • Sealing & addressing (keys, not IPs)
  • Routing & multipath spray-and-wait
  • Fragmentation to the bearer MTU & reassembly
  • Retransmit, dedup, delivery acks, custody

Your driver owns

  • Discovery, who's reachable right now
  • Connections & moving raw frames
  • Background operation & power for the platform
  • Reporting the MTU it can carry

The bearer trait

A bearer is three things: how big a frame it carries, how to send one, and a handle to push inbound frames and peer events back into the core.

// hop-core is bearerless. A Bearer just moves opaque frames between peers and
// reports who is reachable, the core seals, addresses, fragments, retransmits,
// dedups, and delivers on top of it.
pub trait Bearer: Send + Sync {
    // Max bytes per frame. The core fragments bundles to fit and reassembles them.
    fn mtu(&self) -> usize;

    // Best-effort send of one frame to a peer. The core owns retransmit and dedup.
    fn send_frame(&self, peer: &PeerId, frame: &[u8]) -> Result<(), BearerError>;

    // Called once at startup with a handle the bearer pushes events back through.
    fn attach(&self, core: BearerCtx);
}

Report what's happening

Your transport drives the core through the handle from attach, surface peers as they come and go, and hand every inbound frame straight in. The core does the rest.

// inside your bearer, as the transport discovers peers and receives bytes:
core.peer_up(peer_id);              // a peer became reachable
core.recv_frame(peer_id, &bytes);   // an inbound frame arrived, hand it to the core
core.peer_down(peer_id);            // the peer went away

Register it on the node

Add one bearer or several. A node with more than one bearer relays across all of them at once, that's how BLE, Wi-Fi/LAN, and the internet relay coexist on the same node.

// Register one or many bearers; the core sprays copies across whatever is up.
let node = HopNode::builder(secret)
    .bearer(BleBearer::new())     // your BLE driver
    .bearer(LanBearer::new())     // …and/or Wi-Fi/LAN, a sub-GHz radio, a test link
    .open()?;

From a host language

Native and embedded drivers (Linux/server,ESP32) implement the trait in Rust. OniOS and Android, the driver implements the same interface in Swift or Kotlin as a UniFFI foreign trait and hands it to the core, so the platform code that drives CoreBluetooth or the Android BLE stack plugs in exactly where a Rust bearer would.

// On iOS/Android the driver implements the same interface in the host language
// (a UniFFI foreign trait) and hands it to the core, e.g. driving CoreBluetooth:
final class CoreBluetoothBearer: Bearer {
    func mtu() -> UInt32 { 244 }
    func sendFrame(peer: PeerId, frame: Data) throws { /* L2CAP / GATT write */ }
    func attach(core: BearerCtx) { self.core = core }  // push recvFrame / peerUp here
}

Checklist

  • Implement mtu(), send_frame(), and attach().
  • On discovery/connect, call peer_up / peer_down.
  • On every inbound frame, call recv_frame, never reassemble yourself.
  • Register the bearer(s) on the node builder; don't route or retransmit, the core does.

Illustrative, the shape of the interface, not the exact signatures; the real trait ships with the SDK. Bringing an unusual transport or platform?Tell us.

Related

Any transportBLE bearerESP32 driverInterface reference