Bundle Protocol Version 7 (RFC 9171) for Delay-Tolerant Networking
at main 272 lines 8.2 kB view raw
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. *)