Bundle Protocol Version 7 (RFC 9171) for Delay-Tolerant Networking
1(*---------------------------------------------------------------------------
2 Copyright (c) 2025 Thomas Gazagnaire. All rights reserved.
3 SPDX-License-Identifier: ISC
4 ---------------------------------------------------------------------------*)
5
6(** Bundle Protocol Version 7 (RFC 9171, CCSDS 734.2-B-1).
7
8 This module implements BPv7 bundle encoding and decoding using CBOR. A
9 bundle consists of a primary block followed by one or more canonical blocks
10 (including at least one payload block).
11
12 {b Bundle structure (CBOR indefinite array)}
13 {v
14 [_ primary_block, canonical_block, ..., canonical_block ]
15 v}
16
17 {b Primary block (CBOR array, 8-11 elements)}
18 {v
19 [ version, flags, crc_type, dest_eid, src_eid, report_to_eid,
20 creation_timestamp, lifetime, [fragment_offset], [total_adu_len], [crc] ]
21 v}
22
23 {b Canonical block (CBOR array, 5-6 elements)}
24 {v
25 [ block_type, block_number, flags, crc_type, data, [crc] ]
26 v}
27
28 {b References}
29 - {{:https://datatracker.ietf.org/doc/html/rfc9171}RFC 9171} - Bundle
30 Protocol Version 7
31 - {{:https://public.ccsds.org/Pubs/734x2b1.pdf}CCSDS 734.2-B-1} - CCSDS
32 Bundle Protocol Specification *)
33
34(** {1 Endpoint IDs} *)
35
36type eid =
37 | Dtn_none (** The null endpoint [dtn:none], anonymous source. *)
38 | Dtn of string (** A dtn-scheme endpoint [dtn://node-name/demux]. *)
39 | Ipn of int64 * int64 (** An ipn-scheme endpoint [ipn:node.service]. *)
40
41val pp_eid : eid Fmt.t
42(** [pp_eid ppf eid] pretty-prints the endpoint ID. *)
43
44val eid_to_cbor : eid -> Cbort.Cbor.t
45(** [eid_to_cbor eid] encodes an endpoint ID as CBOR. *)
46
47val eid_of_cbor : Cbort.Cbor.t -> (eid, string) result
48(** [eid_of_cbor cbor] decodes an endpoint ID from CBOR. *)
49
50(** {1 CRC Types} *)
51
52type crc_type =
53 | No_crc (** No CRC present. *)
54 | Crc16 (** CRC-16 X.25 (2 bytes). *)
55 | Crc32c (** CRC-32C Castagnoli (4 bytes). *)
56
57val pp_crc_type : crc_type Fmt.t
58
59val int_of_crc_type : crc_type -> int
60(** [int_of_crc_type ct] returns the integer code for the CRC type. *)
61
62val crc_type_of_int : int -> (crc_type, int) result
63(** [crc_type_of_int n] decodes a CRC type from its integer code. *)
64
65val compute_crc : crc_type -> string -> string
66(** [compute_crc crc_type data] computes the CRC for [data] and returns the
67 result as a string. For [No_crc], returns empty string. For [Crc16], returns
68 2 bytes (CRC-16 X.25). For [Crc32c], returns 4 bytes (CRC-32C Castagnoli).
69*)
70
71(** {1 Bundle Processing Flags} *)
72
73type flags = {
74 is_fragment : bool;
75 is_admin_record : bool;
76 must_not_fragment : bool;
77 ack_requested : bool;
78 status_time_requested : bool;
79 report_reception : bool;
80 report_forwarding : bool;
81 report_delivery : bool;
82 report_deletion : bool;
83}
84(** Bundle processing control flags (RFC 9171 Section 4.2.3). *)
85
86val default_flags : flags
87(** Default bundle flags with all flags set to false. *)
88
89val pp_flags : flags Fmt.t
90
91val int_of_flags : flags -> int
92(** [int_of_flags flags] encodes flags as an integer bit field. *)
93
94val flags_of_int : int -> flags
95(** [flags_of_int n] decodes flags from an integer bit field. *)
96
97(** {1 Block Processing Flags} *)
98
99type block_flags = {
100 replicate_in_fragment : bool;
101 report_if_unprocessed : bool;
102 delete_if_unprocessed : bool;
103 discard_if_unprocessed : bool;
104}
105(** Block processing control flags (RFC 9171 Section 4.2.4). *)
106
107val block_flags_default : block_flags
108(** Default block flags with all flags set to false. *)
109
110val pp_block_flags : block_flags Fmt.t
111
112val int_of_block_flags : block_flags -> int
113(** Convert block flags to integer. *)
114
115val block_flags_of_int : int -> block_flags
116(** Convert integer to block flags. *)
117
118(** {1 Creation Timestamp} *)
119
120type timestamp = {
121 time : int64;
122 (** DTN time: milliseconds since 2000-01-01 00:00:00 UTC. Zero means
123 unknown. *)
124 seq : int64; (** Sequence number for bundles created at the same time. *)
125}
126(** Bundle creation timestamp (RFC 9171 Section 4.2.7). *)
127
128val pp_timestamp : timestamp Fmt.t
129
130(** {1 Primary Block} *)
131
132type primary_block = {
133 version : int; (** Bundle protocol version (must be 7). *)
134 flags : flags;
135 crc_type : crc_type;
136 destination : eid;
137 source : eid;
138 report_to : eid;
139 creation_timestamp : timestamp;
140 lifetime : int64; (** Lifetime in milliseconds from creation time. *)
141 fragment_offset : int64 option;
142 (** Present only if [flags.is_fragment] is true. *)
143 total_adu_length : int64 option;
144 (** Present only if [flags.is_fragment] is true. *)
145}
146(** Primary bundle block (RFC 9171 Section 4.3.1). *)
147
148val pp_primary_block : primary_block Fmt.t
149
150(** {1 Canonical Blocks} *)
151
152type block_type =
153 | Payload (** Block type 1: Bundle payload. *)
154 | Previous_node
155 (** Block type 6: Previous node that forwarded this bundle. *)
156 | Bundle_age (** Block type 7: Age of bundle in milliseconds. *)
157 | Hop_count (** Block type 10: Hop limit and count. *)
158 | Other of int (** Other/extension block types. *)
159
160val pp_block_type : block_type Fmt.t
161
162val int_of_block_type : block_type -> int
163(** Convert block type to integer. *)
164
165val block_type_of_int : int -> block_type
166(** Convert integer to block type. *)
167
168(** Block-type-specific data. *)
169type block_data =
170 | Payload_data of string (** Raw payload bytes. *)
171 | Previous_node_data of eid (** Node ID of previous forwarder. *)
172 | Bundle_age_data of int64 (** Bundle age in milliseconds. *)
173 | Hop_count_data of { limit : int; count : int }
174 (** Hop limit (1-255) and current count. *)
175 | Unknown_data of string (** Raw bytes for unknown block types. *)
176
177val pp_block_data : block_data Fmt.t
178
179type canonical_block = {
180 block_type : block_type;
181 block_number : int;
182 (** Unique identifier within the bundle (>0, primary is 0). *)
183 flags : block_flags;
184 crc_type : crc_type;
185 data : block_data;
186}
187(** Canonical bundle block (RFC 9171 Section 4.3.2). *)
188
189val pp_canonical_block : canonical_block Fmt.t
190
191(** {1 Bundle} *)
192
193type t = {
194 primary : primary_block;
195 blocks : canonical_block list;
196 (** Canonical blocks. Must include at least one payload block. *)
197}
198(** A complete bundle. *)
199
200val pp : t Fmt.t
201
202(** {1 Errors} *)
203
204type error =
205 | Invalid_version of int
206 | Invalid_crc_type of int
207 | Invalid_eid_scheme of int
208 | Invalid_block_type of int
209 | Missing_payload_block
210 | Crc_mismatch of { expected : string; computed : string }
211 | Cbor_error of string
212 | Truncated of string
213
214val pp_error : error Fmt.t
215(** Pretty-print errors. *)
216
217(** {1 Encoding} *)
218
219val encode : t -> string
220(** [encode bundle] encodes the bundle as a CBOR byte string. *)
221
222val to_cbor : t -> Cbort.Cbor.t
223(** [to_cbor bundle] encodes the bundle as a CBOR value. *)
224
225(** {1 Decoding} *)
226
227val decode : string -> (t, error) result
228(** [decode s] decodes a bundle from a CBOR byte string. *)
229
230val of_cbor : Cbort.Cbor.t -> (t, error) result
231(** [of_cbor cbor] decodes a bundle from a CBOR value. *)
232
233(** {1 Streaming I/O} *)
234
235val write : Cbort.Rw.encoder -> t -> unit
236(** [write enc bundle] writes the bundle to a CBOR encoder. *)
237
238val read : Cbort.Rw.decoder -> (t, error) result
239(** [read dec] reads a bundle from a CBOR decoder. *)
240
241(** {1 Constructors} *)
242
243val v :
244 ?flags:flags ->
245 ?crc_type:crc_type ->
246 ?report_to:eid ->
247 ?lifetime:int64 ->
248 source:eid ->
249 destination:eid ->
250 creation_timestamp:timestamp ->
251 payload:string ->
252 unit ->
253 t
254(** [v ~source ~destination ~creation_timestamp ~payload ()] creates a simple
255 bundle with a single payload block.
256
257 @param flags Bundle processing flags (default: all false)
258 @param crc_type CRC type for blocks (default: [Crc32c])
259 @param report_to Endpoint for status reports (default: [source])
260 @param lifetime Bundle lifetime in ms (default: 86400000 = 24 hours). *)
261
262val payload : t -> string option
263(** [payload bundle] extracts the payload data from the bundle, if present. *)
264
265val previous_node : t -> eid option
266(** [previous_node bundle] extracts the previous node EID, if present. *)
267
268val age : t -> int64 option
269(** [age bundle] extracts the bundle age in milliseconds, if present. *)
270
271val hop_count : t -> (int * int) option
272(** [hop_count bundle] extracts [(limit, count)], if present. *)