Architecture
Protocol Stack
+----------------------------------+
| Application / BP Agent |
| (aap2-send, custom code) |
+----------------------------------+
| BP7 Bundle (serialized CBOR)
v
+----------------------------------+
| uD3TN / BP7 |
| Bundle Processor |
+----------------------------------+
| raw bundle bytes
v
+----------------------------------+
| CSPCL | <- this library
| cspcl_send/recv_bundle() |
+----------------------------------+
| CSP connection + SFP fragments
v
+----------------------------------+
| libcsp |
| csp_sfp_send / csp_sfp_recv |
+----------------------------------+
| CSP packets
v
+----------------------------------+
| Physical / Virtual Link |
| CAN | SocketCAN | ZMQHUB |
+----------------------------------+
SFP Fragmentation
CSP has a small MTU (typically 256 bytes on CAN). BP7 bundles can be several kilobytes. CSPCL delegates fragmentation entirely to CSP’s Simple Fragmentation Protocol (SFP).
How SFP works
Each SFP fragment appends an 8-byte trailer:
+---------------------------+------------+-----------+
| payload (<=248 B) | offset | totalsize |
| | (4 bytes) | (4 bytes)|
+---------------------------+------------+-----------+
offset— byte position of this fragment within the full messagetotalsize— total reassembled message size
The receiver allocates a single buffer of totalsize on first fragment arrival and fills it as fragments arrive in order. An offset mismatch or size inconsistency causes the transfer to abort.
MTU budget
| Layer | Bytes |
|---|---|
| CSP MTU | 256 |
| SFP header | 8 |
| Usable payload | 248 |
A 10 KB bundle requires ceil(10240 / 248) = 42 fragments.
Connection Model
CSPCL uses one CSP connection per bundle transfer:
Sender Receiver
| |
|-- csp_connect() ------------>|
| | csp_accept()
|-- SFP fragment 0 ----------->|
|-- SFP fragment 1 ----------->|
| ... |
|-- SFP fragment N ----------->|
| | bundle reassembled
|-- csp_close() ------------->|
| | csp_close()
The receiver socket stays open across transfers; only the per-bundle connections are opened and closed.
Reliability
SFP is not reliable by itself — it does not retransmit lost fragments. Reliability is provided at other layers:
| Layer | Mechanism |
|---|---|
| Physical (CAN) | Hardware CRC + ack |
| BP7 custody transfer | End-to-end bundle retransmission |
| CSPCL | Best-effort; returns CSPCL_ERR_TIMEOUT or CSPCL_ERR_SFP on failure |
For lossy RF links where neither the physical layer nor BP custody is active, open the CSP connection with CSP_O_RDP before calling SFP.
Address Translation
CSPCL maps BP endpoint IDs to 8-bit CSP node addresses:
| BP Endpoint | CSP Address |
|---|---|
ipn:5.0 | 5 |
ipn:5.42 | 5 |
dtn://node7/sink | 7 |
The mapping is intentionally simple: use the node number directly. For networks where these do not coincide, a lookup table can be introduced in cspcl_endpoint_to_addr().
File Layout
cspcl/
+-- src/
| +-- cspcl.h Public C API
| +-- cspcl.c Implementation
| +-- cspcl_config.h Compile-time constants
+-- rust-bindings/
| +-- cspcl-sys/ Raw FFI bindings (bindgen)
| +-- cspcl/ Safe Rust wrapper crate
+-- ud3tn-integration/
| +-- src/
| +-- cla_csp.c uD3TN CLA implementation
| +-- cla_csp.h
+-- stubs/ CSP stubs for unit testing without libcsp
+-- tests/ C unit tests (CTest)
+-- tools/
+-- zmqhub_broker.py ZMQ hub for local testing
+-- simple_bundle_transfer.sh