(*--------------------------------------------------------------------------- Copyright (c) 2025 Thomas Gazagnaire. All rights reserved. SPDX-License-Identifier: ISC ---------------------------------------------------------------------------*) (** Bundle Protocol Version 7 (RFC 9171, CCSDS 734.2-B-1). This module implements BPv7 bundle encoding and decoding using CBOR. A bundle consists of a primary block followed by one or more canonical blocks (including at least one payload block). {b Bundle structure (CBOR indefinite array)} {v [_ primary_block, canonical_block, ..., canonical_block ] v} {b Primary block (CBOR array, 8-11 elements)} {v [ version, flags, crc_type, dest_eid, src_eid, report_to_eid, creation_timestamp, lifetime, [fragment_offset], [total_adu_len], [crc] ] v} {b Canonical block (CBOR array, 5-6 elements)} {v [ block_type, block_number, flags, crc_type, data, [crc] ] v} {b References} - {{:https://datatracker.ietf.org/doc/html/rfc9171}RFC 9171} - Bundle Protocol Version 7 - {{:https://public.ccsds.org/Pubs/734x2b1.pdf}CCSDS 734.2-B-1} - CCSDS Bundle Protocol Specification *) (** {1 Endpoint IDs} *) type eid = | Dtn_none (** The null endpoint [dtn:none], anonymous source. *) | Dtn of string (** A dtn-scheme endpoint [dtn://node-name/demux]. *) | Ipn of int64 * int64 (** An ipn-scheme endpoint [ipn:node.service]. *) val pp_eid : eid Fmt.t (** [pp_eid ppf eid] pretty-prints the endpoint ID. *) val eid_to_cbor : eid -> Cbort.Cbor.t (** [eid_to_cbor eid] encodes an endpoint ID as CBOR. *) val eid_of_cbor : Cbort.Cbor.t -> (eid, string) result (** [eid_of_cbor cbor] decodes an endpoint ID from CBOR. *) (** {1 CRC Types} *) type crc_type = | No_crc (** No CRC present. *) | Crc16 (** CRC-16 X.25 (2 bytes). *) | Crc32c (** CRC-32C Castagnoli (4 bytes). *) val pp_crc_type : crc_type Fmt.t val int_of_crc_type : crc_type -> int (** [int_of_crc_type ct] returns the integer code for the CRC type. *) val crc_type_of_int : int -> (crc_type, int) result (** [crc_type_of_int n] decodes a CRC type from its integer code. *) val compute_crc : crc_type -> string -> string (** [compute_crc crc_type data] computes the CRC for [data] and returns the result as a string. For [No_crc], returns empty string. For [Crc16], returns 2 bytes (CRC-16 X.25). For [Crc32c], returns 4 bytes (CRC-32C Castagnoli). *) (** {1 Bundle Processing Flags} *) type flags = { is_fragment : bool; is_admin_record : bool; must_not_fragment : bool; ack_requested : bool; status_time_requested : bool; report_reception : bool; report_forwarding : bool; report_delivery : bool; report_deletion : bool; } (** Bundle processing control flags (RFC 9171 Section 4.2.3). *) val default_flags : flags (** Default bundle flags with all flags set to false. *) val pp_flags : flags Fmt.t val int_of_flags : flags -> int (** [int_of_flags flags] encodes flags as an integer bit field. *) val flags_of_int : int -> flags (** [flags_of_int n] decodes flags from an integer bit field. *) (** {1 Block Processing Flags} *) type block_flags = { replicate_in_fragment : bool; report_if_unprocessed : bool; delete_if_unprocessed : bool; discard_if_unprocessed : bool; } (** Block processing control flags (RFC 9171 Section 4.2.4). *) val block_flags_default : block_flags (** Default block flags with all flags set to false. *) val pp_block_flags : block_flags Fmt.t val int_of_block_flags : block_flags -> int (** Convert block flags to integer. *) val block_flags_of_int : int -> block_flags (** Convert integer to block flags. *) (** {1 Creation Timestamp} *) type timestamp = { time : int64; (** DTN time: milliseconds since 2000-01-01 00:00:00 UTC. Zero means unknown. *) seq : int64; (** Sequence number for bundles created at the same time. *) } (** Bundle creation timestamp (RFC 9171 Section 4.2.7). *) val pp_timestamp : timestamp Fmt.t (** {1 Primary Block} *) type primary_block = { version : int; (** Bundle protocol version (must be 7). *) flags : flags; crc_type : crc_type; destination : eid; source : eid; report_to : eid; creation_timestamp : timestamp; lifetime : int64; (** Lifetime in milliseconds from creation time. *) fragment_offset : int64 option; (** Present only if [flags.is_fragment] is true. *) total_adu_length : int64 option; (** Present only if [flags.is_fragment] is true. *) } (** Primary bundle block (RFC 9171 Section 4.3.1). *) val pp_primary_block : primary_block Fmt.t (** {1 Canonical Blocks} *) type block_type = | Payload (** Block type 1: Bundle payload. *) | Previous_node (** Block type 6: Previous node that forwarded this bundle. *) | Bundle_age (** Block type 7: Age of bundle in milliseconds. *) | Hop_count (** Block type 10: Hop limit and count. *) | Other of int (** Other/extension block types. *) val pp_block_type : block_type Fmt.t val int_of_block_type : block_type -> int (** Convert block type to integer. *) val block_type_of_int : int -> block_type (** Convert integer to block type. *) (** Block-type-specific data. *) type block_data = | Payload_data of string (** Raw payload bytes. *) | Previous_node_data of eid (** Node ID of previous forwarder. *) | Bundle_age_data of int64 (** Bundle age in milliseconds. *) | Hop_count_data of { limit : int; count : int } (** Hop limit (1-255) and current count. *) | Unknown_data of string (** Raw bytes for unknown block types. *) val pp_block_data : block_data Fmt.t type canonical_block = { block_type : block_type; block_number : int; (** Unique identifier within the bundle (>0, primary is 0). *) flags : block_flags; crc_type : crc_type; data : block_data; } (** Canonical bundle block (RFC 9171 Section 4.3.2). *) val pp_canonical_block : canonical_block Fmt.t (** {1 Bundle} *) type t = { primary : primary_block; blocks : canonical_block list; (** Canonical blocks. Must include at least one payload block. *) } (** A complete bundle. *) val pp : t Fmt.t (** {1 Errors} *) type error = | Invalid_version of int | Invalid_crc_type of int | Invalid_eid_scheme of int | Invalid_block_type of int | Missing_payload_block | Crc_mismatch of { expected : string; computed : string } | Cbor_error of string | Truncated of string val pp_error : error Fmt.t (** Pretty-print errors. *) (** {1 Encoding} *) val encode : t -> string (** [encode bundle] encodes the bundle as a CBOR byte string. *) val to_cbor : t -> Cbort.Cbor.t (** [to_cbor bundle] encodes the bundle as a CBOR value. *) (** {1 Decoding} *) val decode : string -> (t, error) result (** [decode s] decodes a bundle from a CBOR byte string. *) val of_cbor : Cbort.Cbor.t -> (t, error) result (** [of_cbor cbor] decodes a bundle from a CBOR value. *) (** {1 Streaming I/O} *) val write : Cbort.Rw.encoder -> t -> unit (** [write enc bundle] writes the bundle to a CBOR encoder. *) val read : Cbort.Rw.decoder -> (t, error) result (** [read dec] reads a bundle from a CBOR decoder. *) (** {1 Constructors} *) val v : ?flags:flags -> ?crc_type:crc_type -> ?report_to:eid -> ?lifetime:int64 -> source:eid -> destination:eid -> creation_timestamp:timestamp -> payload:string -> unit -> t (** [v ~source ~destination ~creation_timestamp ~payload ()] creates a simple bundle with a single payload block. @param flags Bundle processing flags (default: all false) @param crc_type CRC type for blocks (default: [Crc32c]) @param report_to Endpoint for status reports (default: [source]) @param lifetime Bundle lifetime in ms (default: 86400000 = 24 hours). *) val payload : t -> string option (** [payload bundle] extracts the payload data from the bundle, if present. *) val previous_node : t -> eid option (** [previous_node bundle] extracts the previous node EID, if present. *) val age : t -> int64 option (** [age bundle] extracts the bundle age in milliseconds, if present. *) val hop_count : t -> (int * int) option (** [hop_count bundle] extracts [(limit, count)], if present. *)