My own corner of monopam
README.md

bundle #

An OCaml implementation of the Bundle Protocol Version 7 (RFC 9171), the store-and-forward protocol of Delay-Tolerant Networking (DTN).

A link to a spacecraft, a sensor in a remote field or a network after a disaster may be down for hours, with round trips measured in minutes. TCP/IP assumes an end-to-end path exists at the moment a packet is sent, and fails on such links. The Bundle Protocol instead moves self-contained messages, called bundles, from node to node: each node stores a bundle until the next hop is reachable, then forwards it. NASA runs DTN on the International Space Station, and CCSDS 734.20-O-1 profiles version 7 of the protocol for space missions.

This package provides the bundle codec and the Bundle Protocol Agent of RFC 9171 Section 5 as a pure state machine, with no I/O. The Bundle Protocol Agent is the part of a node that implements the protocol: it accepts bundles from local applications and from convergence layers, decides for each one whether to deliver it locally, forward it or delete it, and keeps it in storage while no next hop is reachable. A convergence layer is the adapter that carries whole bundles between two neighbouring nodes over one underlying transport (a TCP connection, UDP, a space link); the Bundle Protocol above it never sees that transport. The companion package bundle-eio runs that state machine over a real network with Eio.

Features #

  • An RFC 9171 bundle codec, over CBOR (RFC 8949) with nox-cbor, and streaming I/O with bytesrw.
  • The standard block types (payload, previous node, bundle age and hop count), with unknown extension blocks kept as raw bytes.
  • CRC-16 X.25 and CRC-32C block integrity checks. Each block is a CBOR array whose last element is its CRC, and the block's crc_type says whether it carries none, 2 bytes (CRC-16 X.25) or 4 bytes (CRC-32C). The sender computes the CRC over the block's whole CBOR encoding with that last element's bytes set to zero, then writes it in; the receiver recomputes it the same way and refuses the block on a mismatch (RFC 9171 Section 4.2.1).
  • Endpoint identifiers in the dtn: and ipn: schemes.
  • Administrative records (RFC 9171 Section 6.1): the [type code, content] array, with the bundle status report of Section 6.1.1 decoded field by field and every other record type, including the private-use codes RFC 9713 reserves, carried as its code and CBOR content.
  • The Bundle Protocol Agent (RFC 9171 Section 5) as a pure transition from the current time and an event to a new state and a list of actions: dispatch, local delivery with reassembly of fragments (Section 5.9), forwarding with the Previous Node, Bundle Age and hop count blocks updated (Section 5.4), and lifetime expiry counted from the creation time (Section 5.5).
  • An Eio driver, in the bundle-eio package, with a TCPCLv4 (RFC 9174) convergence layer: it opens a TCP session, negotiates the session parameters with the peer, and sends each bundle as length-prefixed segments that the receiver acknowledges.

Installation #

Install with opam:

$ opam install bundle bundle-eio

If opam cannot find the packages, they may not yet be released in the public opam-repository. Add the overlay repository, then install them:

$ opam repo add samoht https://tangled.org/gazagnaire.org/opam-overlay.git
$ opam update
$ opam install bundle bundle-eio

Usage #

Create a bundle, encode it and decode it back:

(* Create a bundle *)
let bundle =
  Bundle.v
    ~source:(Bundle.Ipn (1L, 1L))
    ~destination:(Bundle.Ipn (2L, 1L))
    ~creation_timestamp:{ Bundle.time = 0L; seq = 0L }
    ~payload:"Hello, DTN!"
    ()

(* Encode to bytes *)
let encoded = Bundle.encode bundle

(* Decode from bytes *)
let () =
  match Bundle.decode encoded with
  | Ok decoded ->
      Printf.printf "Payload: %s\n" (Option.get (Bundle.payload decoded))
  | Error e -> Fmt.pr "Error: %a@." Bundle.pp_error e

Running a node with Eio #

Bundle.Node decides what happens to each bundle: Bundle.Node.incoming maps the current DTN time and an event (a received bundle, a local request to send one, a concluded send, an expired lifetime timer) to a new state and a list of actions. Because time arrives as an input, the same state machine runs under Eio, in a unikernel and in the deterministic test suite.

The bundle-eio package carries those actions out. Bundles cross a convergence layer, lifetime timers run on the Eio clock, and service events reach the application as they happen. The convergence layer is a functor argument, and Bundle_eio.Tcpcl provides TCPCLv4 over an ocaml-tcpcl session. The node's configuration names its local endpoint, for example Bundle.Node.config ~local_eid:(Bundle.Dtn "node") ():

let run_node ~sw ~clock ~mono ~net ~peer ~config bundle =
  match Tcpcl_eio.connect ~sw ~clock:mono ~net peer with
  | Error e -> Fmt.failwith "%a" Tcpcl_eio.pp_error e
  | Ok session ->
      let agent = Bundle_eio.Tcpcl.v ~sw ~clock session config in
      Bundle_eio.submit agent bundle;
      Bundle_eio.receive agent

In a MirageOS unikernel, the Dtn device of nox-mirage.eio exposes the same agent.

API #

Bundle provides:

  • the types Bundle.t (a bundle), Bundle.eid (an endpoint identifier: dtn:none, a dtn: URI or ipn:node.service), Bundle.primary_block, Bundle.canonical_block and Bundle.timestamp;
  • Bundle.v to create a bundle, Bundle.encode and Bundle.decode to convert it to and from bytes, and Bundle.read to read one from a CBOR decoder;
  • Bundle.payload, Bundle.previous_node, Bundle.age and Bundle.hop_count to read the standard blocks;
  • Bundle.bundle_id, the identity of a bundle (RFC 9171 Section 4.2), and Bundle.status_report_reason, the reason codes of RFC 9171 Table 1;
  • Bundle.Node, the agent state machine: Bundle.Node.config and Bundle.Node.v create one, and Bundle.Node.incoming applies an event;
  • Bundle.Event, the service events the agent reports (received, delivered, forwarded, deleted).
  • Bundle.Admin_record, the administrative record codec and its bundle status report (RFC 9171 Section 6.1).

Bundle_eio provides the running agent, with submit, receive, next_event and stored, and the CONVERGENCE_LAYER signature with its Make functor.

  • ION is the DTN implementation in C from NASA JPL, used on flight missions.
  • µD3TN is a small C implementation for embedded systems.
  • DTN7 provides BPv7 implementations in Go and Rust.

This implementation is written in OCaml with few C dependencies, and uses bytesrw and Eio. Its protocol core has no I/O, so one state machine serves the Eio driver, the unikernel device and the tests. Bundle Protocol Security (RFC 9172 and RFC 9173) is the separate bpsec package, which works over the same Bundle.t.

The node reassembles fragments addressed to it (RFC 9171 Section 5.9) but never fragments a bundle (Section 5.8, which leaves fragmenting to the node's discretion), generates no bundle status reports (Section 6.1.1, a SHOULD), and never retries a forward whose sending failed (Section 5.4, Step 5 leaves the retry to the implementation).

References #

  • RFC 9171, Bundle Protocol Version 7
  • RFC 9174, Delay-Tolerant Networking TCP Convergence-Layer Protocol Version 4
  • RFC 9172, Bundle Protocol Security (BPSec)
  • CCSDS 734.20-O-1, CCSDS Bundle Protocol Specification, the experimental CCSDS profile of RFC 9171 (Orange Book, April 2025)

Licence #

ISC License. See LICENSE.md for details.