Bundle Protocol Version 7 (RFC 9171) for Delay-Tolerant Networking
at main 658 lines 21 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 8module Cbor = Cbort.Cbor 9module Cbor_rw = Cbort.Rw 10 11(* {1 Endpoint IDs} *) 12 13type eid = Dtn_none | Dtn of string | Ipn of int64 * int64 14 15let pp_eid fmt = function 16 | Dtn_none -> Fmt.pf fmt "dtn:none" 17 | Dtn s -> Fmt.pf fmt "dtn:%s" s 18 | Ipn (node, service) -> Fmt.pf fmt "ipn:%Ld.%Ld" node service 19 20let eid_to_cbor = function 21 | Dtn_none -> Cbor.Array [ Cbor.int 1; Cbor.int 0 ] 22 | Dtn s -> Cbor.Array [ Cbor.int 1; Cbor.Text s ] 23 | Ipn (node, service) -> 24 Cbor.Array 25 [ Cbor.int 2; Cbor.Array [ Cbor.int64 node; Cbor.int64 service ] ] 26 27let err_unknown_eid_scheme n = Error (Fmt.str "unknown EID scheme: %Ld" n) 28 29let eid_of_cbor cbor = 30 match Cbor.to_array cbor with 31 | Some [ scheme; ssp ] -> ( 32 match Cbor.to_int64 scheme with 33 | Some 1L -> ( 34 (* dtn scheme *) 35 match Cbor.to_int64 ssp with 36 | Some 0L -> Ok Dtn_none 37 | _ -> ( 38 match Cbor.to_text ssp with 39 | Some s -> Ok (Dtn s) 40 | None -> Error "invalid dtn SSP")) 41 | Some 2L -> ( 42 (* ipn scheme *) 43 match Cbor.to_array ssp with 44 | Some [ node; service ] -> ( 45 match (Cbor.to_int64 node, Cbor.to_int64 service) with 46 | Some n, Some s -> Ok (Ipn (n, s)) 47 | _ -> Error "invalid ipn node/service") 48 | _ -> Error "ipn SSP must be array of 2") 49 | Some n -> err_unknown_eid_scheme n 50 | None -> Error "EID scheme must be uint") 51 | _ -> Error "EID must be array of 2" 52 53(* {1 CRC Types} *) 54 55type crc_type = No_crc | Crc16 | Crc32c 56 57let pp_crc_type fmt = function 58 | No_crc -> Fmt.string fmt "none" 59 | Crc16 -> Fmt.string fmt "CRC-16" 60 | Crc32c -> Fmt.string fmt "CRC-32C" 61 62let int_of_crc_type = function No_crc -> 0 | Crc16 -> 1 | Crc32c -> 2 63 64let crc_type_of_int = function 65 | 0 -> Ok No_crc 66 | 1 -> Ok Crc16 67 | 2 -> Ok Crc32c 68 | n -> Error n 69 70(* {1 Bundle Processing Flags} *) 71 72type flags = { 73 is_fragment : bool; 74 is_admin_record : bool; 75 must_not_fragment : bool; 76 ack_requested : bool; 77 status_time_requested : bool; 78 report_reception : bool; 79 report_forwarding : bool; 80 report_delivery : bool; 81 report_deletion : bool; 82} 83 84let default_flags = 85 { 86 is_fragment = false; 87 is_admin_record = false; 88 must_not_fragment = false; 89 ack_requested = false; 90 status_time_requested = false; 91 report_reception = false; 92 report_forwarding = false; 93 report_delivery = false; 94 report_deletion = false; 95 } 96 97let pp_flags fmt f = 98 let flags = [] in 99 let flags = if f.is_fragment then "fragment" :: flags else flags in 100 let flags = if f.is_admin_record then "admin" :: flags else flags in 101 let flags = if f.must_not_fragment then "no-frag" :: flags else flags in 102 let flags = if f.ack_requested then "ack" :: flags else flags in 103 let flags = 104 if f.status_time_requested then "status-time" :: flags else flags 105 in 106 let flags = if f.report_reception then "report-recv" :: flags else flags in 107 let flags = if f.report_forwarding then "report-fwd" :: flags else flags in 108 let flags = if f.report_delivery then "report-dlv" :: flags else flags in 109 let flags = if f.report_deletion then "report-del" :: flags else flags in 110 Fmt.pf fmt "[%s]" (String.concat ", " (List.rev flags)) 111 112let int_of_flags f = 113 let n = 0 in 114 let n = if f.is_fragment then n lor 0x000001 else n in 115 let n = if f.is_admin_record then n lor 0x000002 else n in 116 let n = if f.must_not_fragment then n lor 0x000004 else n in 117 let n = if f.ack_requested then n lor 0x000020 else n in 118 let n = if f.status_time_requested then n lor 0x000040 else n in 119 let n = if f.report_reception then n lor 0x004000 else n in 120 let n = if f.report_forwarding then n lor 0x010000 else n in 121 let n = if f.report_delivery then n lor 0x020000 else n in 122 let n = if f.report_deletion then n lor 0x040000 else n in 123 n 124 125let flags_of_int n = 126 { 127 is_fragment = n land 0x000001 <> 0; 128 is_admin_record = n land 0x000002 <> 0; 129 must_not_fragment = n land 0x000004 <> 0; 130 ack_requested = n land 0x000020 <> 0; 131 status_time_requested = n land 0x000040 <> 0; 132 report_reception = n land 0x004000 <> 0; 133 report_forwarding = n land 0x010000 <> 0; 134 report_delivery = n land 0x020000 <> 0; 135 report_deletion = n land 0x040000 <> 0; 136 } 137 138(* {1 Block Processing Flags} *) 139 140type block_flags = { 141 replicate_in_fragment : bool; 142 report_if_unprocessed : bool; 143 delete_if_unprocessed : bool; 144 discard_if_unprocessed : bool; 145} 146 147let block_flags_default = 148 { 149 replicate_in_fragment = false; 150 report_if_unprocessed = false; 151 delete_if_unprocessed = false; 152 discard_if_unprocessed = false; 153 } 154 155let pp_block_flags fmt f = 156 let flags = [] in 157 let flags = if f.replicate_in_fragment then "replicate" :: flags else flags in 158 let flags = if f.report_if_unprocessed then "report" :: flags else flags in 159 let flags = if f.delete_if_unprocessed then "delete" :: flags else flags in 160 let flags = if f.discard_if_unprocessed then "discard" :: flags else flags in 161 Fmt.pf fmt "[%s]" (String.concat ", " (List.rev flags)) 162 163let int_of_block_flags f = 164 let n = 0 in 165 let n = if f.replicate_in_fragment then n lor 0x01 else n in 166 let n = if f.report_if_unprocessed then n lor 0x02 else n in 167 let n = if f.delete_if_unprocessed then n lor 0x04 else n in 168 let n = if f.discard_if_unprocessed then n lor 0x10 else n in 169 n 170 171let block_flags_of_int n = 172 { 173 replicate_in_fragment = n land 0x01 <> 0; 174 report_if_unprocessed = n land 0x02 <> 0; 175 delete_if_unprocessed = n land 0x04 <> 0; 176 discard_if_unprocessed = n land 0x10 <> 0; 177 } 178 179(* {1 Creation Timestamp} *) 180 181type timestamp = { time : int64; seq : int64 } 182 183let pp_timestamp fmt ts = Fmt.pf fmt "{ time=%Ld; seq=%Ld }" ts.time ts.seq 184 185(* {1 Primary Block} *) 186 187type primary_block = { 188 version : int; 189 flags : flags; 190 crc_type : crc_type; 191 destination : eid; 192 source : eid; 193 report_to : eid; 194 creation_timestamp : timestamp; 195 lifetime : int64; 196 fragment_offset : int64 option; 197 total_adu_length : int64 option; 198} 199 200let pp_primary_block fmt pb = 201 Fmt.pf fmt 202 "@[<v 2>primary {@ version=%d;@ flags=%a;@ crc=%a;@ dest=%a;@ src=%a;@ \ 203 report_to=%a;@ timestamp=%a;@ lifetime=%Ld%a%a@ }@]" 204 pb.version pp_flags pb.flags pp_crc_type pb.crc_type pp_eid pb.destination 205 pp_eid pb.source pp_eid pb.report_to pp_timestamp pb.creation_timestamp 206 pb.lifetime 207 (fun fmt -> function 208 | Some off -> Fmt.pf fmt ";@ fragment_offset=%Ld" off | None -> ()) 209 pb.fragment_offset 210 (fun fmt -> function 211 | Some len -> Fmt.pf fmt ";@ total_adu_length=%Ld" len | None -> ()) 212 pb.total_adu_length 213 214(* {1 Canonical Blocks} *) 215 216type block_type = 217 | Payload 218 | Previous_node 219 | Bundle_age 220 | Hop_count 221 | Other of int 222 223let pp_block_type fmt = function 224 | Payload -> Fmt.string fmt "payload" 225 | Previous_node -> Fmt.string fmt "previous-node" 226 | Bundle_age -> Fmt.string fmt "bundle-age" 227 | Hop_count -> Fmt.string fmt "hop-count" 228 | Other n -> Fmt.pf fmt "other(%d)" n 229 230let int_of_block_type = function 231 | Payload -> 1 232 | Previous_node -> 6 233 | Bundle_age -> 7 234 | Hop_count -> 10 235 | Other n -> n 236 237let block_type_of_int = function 238 | 1 -> Payload 239 | 6 -> Previous_node 240 | 7 -> Bundle_age 241 | 10 -> Hop_count 242 | n -> Other n 243 244type block_data = 245 | Payload_data of string 246 | Previous_node_data of eid 247 | Bundle_age_data of int64 248 | Hop_count_data of { limit : int; count : int } 249 | Unknown_data of string 250 251let pp_block_data fmt = function 252 | Payload_data b -> Fmt.pf fmt "payload[%d bytes]" (String.length b) 253 | Previous_node_data eid -> Fmt.pf fmt "previous-node=%a" pp_eid eid 254 | Bundle_age_data age -> Fmt.pf fmt "age=%Ld ms" age 255 | Hop_count_data { limit; count } -> Fmt.pf fmt "hop-count=%d/%d" count limit 256 | Unknown_data b -> Fmt.pf fmt "unknown[%d bytes]" (String.length b) 257 258type canonical_block = { 259 block_type : block_type; 260 block_number : int; 261 flags : block_flags; 262 crc_type : crc_type; 263 data : block_data; 264} 265 266let pp_canonical_block fmt cb = 267 Fmt.pf fmt "@[<v 2>block#%d {@ type=%a;@ flags=%a;@ crc=%a;@ data=%a@ }@]" 268 cb.block_number pp_block_type cb.block_type pp_block_flags cb.flags 269 pp_crc_type cb.crc_type pp_block_data cb.data 270 271(* {1 Bundle} *) 272 273type t = { primary : primary_block; blocks : canonical_block list } 274 275let pp fmt bundle = 276 Fmt.pf fmt "@[<v 2>bundle {@ %a" pp_primary_block bundle.primary; 277 List.iter (fun b -> Fmt.pf fmt "@ %a" pp_canonical_block b) bundle.blocks; 278 Fmt.pf fmt "@ }@]" 279 280(* {1 Errors} *) 281 282type error = 283 | Invalid_version of int 284 | Invalid_crc_type of int 285 | Invalid_eid_scheme of int 286 | Invalid_block_type of int 287 | Missing_payload_block 288 | Crc_mismatch of { expected : string; computed : string } 289 | Cbor_error of string 290 | Truncated of string 291 292let pp_hex fmt s = String.iter (fun c -> Fmt.pf fmt "%02x" (Char.code c)) s 293 294let pp_error fmt = function 295 | Invalid_version v -> Fmt.pf fmt "invalid version: %d (expected 7)" v 296 | Invalid_crc_type n -> Fmt.pf fmt "invalid CRC type: %d" n 297 | Invalid_eid_scheme n -> Fmt.pf fmt "invalid EID scheme: %d" n 298 | Invalid_block_type n -> Fmt.pf fmt "invalid block type: %d" n 299 | Missing_payload_block -> Fmt.pf fmt "missing payload block" 300 | Crc_mismatch { expected; computed } -> 301 Fmt.pf fmt "CRC mismatch: expected %a, computed %a" pp_hex expected pp_hex 302 computed 303 | Cbor_error msg -> Fmt.pf fmt "CBOR error: %s" msg 304 | Truncated msg -> Fmt.pf fmt "truncated: %s" msg 305 306open Result.Syntax 307 308(* Helper to convert Option to Result with Cbor_error *) 309let require msg = function Some v -> Ok v | None -> Error (Cbor_error msg) 310 311(* {1 CRC Computation} *) 312 313let crc16_x25 = Crc.crc16_x25 314let crc32c = Crc.crc32c 315 316let compute_crc crc_type data = 317 match crc_type with 318 | No_crc -> "" 319 | Crc16 -> 320 let crc = crc16_x25 data in 321 let buf = Bytes.create 2 in 322 Bytes.set_uint16_be buf 0 crc; 323 Bytes.to_string buf 324 | Crc32c -> 325 let crc = crc32c data in 326 let buf = Bytes.create 4 in 327 Bytes.set_int32_be buf 0 (Int32.of_int crc); 328 Bytes.to_string buf 329 330(* {1 CBOR Helpers} *) 331 332let cbor_encode (v : Cbor.t) : string = 333 let buf = Buffer.create 256 in 334 let writer = Bytesrw.Bytes.Writer.of_buffer buf in 335 let enc = Cbor_rw.encoder writer in 336 Cbor_rw.write_cbor enc v; 337 Cbor_rw.flush_encoder enc; 338 Buffer.contents buf 339 340let cbor_decode (s : string) : Cbor.t option = 341 try 342 let reader = Bytesrw.Bytes.Reader.of_string s in 343 let dec = Cbor_rw.decoder reader in 344 Some (Cbor_rw.read_cbor dec) 345 with End_of_file | Failure _ | Invalid_argument _ -> None 346 347(* {1 Encoding} *) 348 349let encode_timestamp ts = Cbor.Array [ Cbor.int64 ts.time; Cbor.int64 ts.seq ] 350 351let encode_block_data = function 352 | Payload_data b -> Cbor.Bytes b 353 | Previous_node_data eid -> 354 let cbor = eid_to_cbor eid in 355 Cbor.Bytes (cbor_encode cbor) 356 | Bundle_age_data age -> Cbor.Bytes (cbor_encode (Cbor.int64 age)) 357 | Hop_count_data { limit; count } -> 358 let cbor = Cbor.Array [ Cbor.int limit; Cbor.int count ] in 359 Cbor.Bytes (cbor_encode cbor) 360 | Unknown_data b -> Cbor.Bytes b 361 362let encode_primary pb = 363 let elements = 364 [ 365 Cbor.int pb.version; 366 Cbor.int (int_of_flags pb.flags); 367 Cbor.int (int_of_crc_type pb.crc_type); 368 eid_to_cbor pb.destination; 369 eid_to_cbor pb.source; 370 eid_to_cbor pb.report_to; 371 encode_timestamp pb.creation_timestamp; 372 Cbor.int64 pb.lifetime; 373 ] 374 in 375 let elements = 376 match pb.fragment_offset with 377 | Some off -> elements @ [ Cbor.int64 off ] 378 | None -> elements 379 in 380 let elements = 381 match pb.total_adu_length with 382 | Some len -> elements @ [ Cbor.int64 len ] 383 | None -> elements 384 in 385 (* Add CRC placeholder if needed *) 386 let elements = 387 match pb.crc_type with 388 | No_crc -> elements 389 | Crc16 -> elements @ [ Cbor.Bytes (String.make 2 '\x00') ] 390 | Crc32c -> elements @ [ Cbor.Bytes (String.make 4 '\x00') ] 391 in 392 let arr = Cbor.Array elements in 393 (* Compute CRC if needed *) 394 match pb.crc_type with 395 | No_crc -> arr 396 | _ -> ( 397 let encoded = cbor_encode arr in 398 let crc = compute_crc pb.crc_type encoded in 399 let crc_len = String.length crc in 400 (* Replace the last crc_len bytes with computed CRC *) 401 let encoded_bytes = Bytes.of_string encoded in 402 Bytes.blit_string crc 0 encoded_bytes 403 (Bytes.length encoded_bytes - crc_len) 404 crc_len; 405 (* Re-decode to get proper CBOR structure *) 406 match cbor_decode (Bytes.to_string encoded_bytes) with 407 | Some v -> v 408 | None -> arr) 409 410let encode_canonical cb = 411 let elements = 412 [ 413 Cbor.int (int_of_block_type cb.block_type); 414 Cbor.int cb.block_number; 415 Cbor.int (int_of_block_flags cb.flags); 416 Cbor.int (int_of_crc_type cb.crc_type); 417 encode_block_data cb.data; 418 ] 419 in 420 let elements = 421 match cb.crc_type with 422 | No_crc -> elements 423 | Crc16 -> elements @ [ Cbor.Bytes (String.make 2 '\x00') ] 424 | Crc32c -> elements @ [ Cbor.Bytes (String.make 4 '\x00') ] 425 in 426 let arr = Cbor.Array elements in 427 match cb.crc_type with 428 | No_crc -> arr 429 | _ -> ( 430 let encoded = cbor_encode arr in 431 let crc = compute_crc cb.crc_type encoded in 432 let crc_len = String.length crc in 433 let encoded_bytes = Bytes.of_string encoded in 434 Bytes.blit_string crc 0 encoded_bytes 435 (Bytes.length encoded_bytes - crc_len) 436 crc_len; 437 match cbor_decode (Bytes.to_string encoded_bytes) with 438 | Some v -> v 439 | None -> arr) 440 441let to_cbor bundle = 442 let primary_cbor = encode_primary bundle.primary in 443 let blocks_cbor = List.map encode_canonical bundle.blocks in 444 (* BPv7 uses indefinite-length array, but cbort doesn't have Iarray, 445 so we use regular Array which should be compatible *) 446 Cbor.Array (primary_cbor :: blocks_cbor) 447 448let encode bundle = cbor_encode (to_cbor bundle) 449let write enc bundle = Cbor_rw.write_cbor enc (to_cbor bundle) 450 451(* {1 Decoding} *) 452 453let decode_timestamp cbor = 454 match Cbor.to_array cbor with 455 | Some [ time_cbor; seq_cbor ] -> ( 456 match (Cbor.to_int64 time_cbor, Cbor.to_int64 seq_cbor) with 457 | Some time, Some seq -> Ok { time; seq } 458 | _ -> Error (Cbor_error "timestamp fields must be uint")) 459 | _ -> Error (Cbor_error "timestamp must be array of 2") 460 461let decode_block_data block_type data_bytes = 462 let require_cbor msg = cbor_decode data_bytes |> require msg in 463 match block_type with 464 | Payload -> Ok (Payload_data data_bytes) 465 | Previous_node -> 466 let* cbor = require_cbor "failed to decode previous node CBOR" in 467 let* eid = 468 eid_of_cbor cbor |> Result.map_error (fun msg -> Cbor_error msg) 469 in 470 Ok (Previous_node_data eid) 471 | Bundle_age -> 472 let* cbor = require_cbor "failed to decode bundle age CBOR" in 473 let* age = Cbor.to_int64 cbor |> require "bundle age must be uint" in 474 Ok (Bundle_age_data age) 475 | Hop_count -> ( 476 let* cbor = require_cbor "failed to decode hop count CBOR" in 477 let* elements = 478 Cbor.to_array cbor |> require "hop count must be array of 2" 479 in 480 match elements with 481 | [ limit_cbor; count_cbor ] -> 482 let* limit = 483 Cbor.to_int64 limit_cbor |> require "hop count fields must be uint" 484 in 485 let* count = 486 Cbor.to_int64 count_cbor |> require "hop count fields must be uint" 487 in 488 Ok 489 (Hop_count_data 490 { limit = Int64.to_int limit; count = Int64.to_int count }) 491 | _ -> Error (Cbor_error "hop count must be array of 2")) 492 | Other _ -> Ok (Unknown_data data_bytes) 493 494let decode_primary cbor = 495 let* elements = Cbor.to_array cbor |> require "primary block must be array" in 496 if List.length elements < 8 then 497 Error (Truncated "primary block needs at least 8 elements") 498 else 499 let get i = List.nth elements i in 500 let* version = Cbor.to_int64 (get 0) |> require "version must be uint" in 501 if version <> 7L then Error (Invalid_version (Int64.to_int version)) 502 else 503 let* flags_int = Cbor.to_int64 (get 1) |> require "flags must be uint" in 504 let flags = flags_of_int (Int64.to_int flags_int) in 505 let* crc_int = Cbor.to_int64 (get 2) |> require "CRC type must be uint" in 506 let* crc_type = 507 crc_type_of_int (Int64.to_int crc_int) 508 |> Result.map_error (fun n -> Invalid_crc_type n) 509 in 510 let* destination = 511 eid_of_cbor (get 3) |> Result.map_error (fun msg -> Cbor_error msg) 512 in 513 let* source = 514 eid_of_cbor (get 4) |> Result.map_error (fun msg -> Cbor_error msg) 515 in 516 let* report_to = 517 eid_of_cbor (get 5) |> Result.map_error (fun msg -> Cbor_error msg) 518 in 519 let* creation_timestamp = decode_timestamp (get 6) in 520 let* lifetime = 521 Cbor.to_int64 (get 7) |> require "lifetime must be uint" 522 in 523 let fragment_offset, total_adu_length = 524 if flags.is_fragment && List.length elements >= 10 then 525 (Cbor.to_int64 (get 8), Cbor.to_int64 (get 9)) 526 else (None, None) 527 in 528 Ok 529 { 530 version = 7; 531 flags; 532 crc_type; 533 destination; 534 source; 535 report_to; 536 creation_timestamp; 537 lifetime; 538 fragment_offset; 539 total_adu_length; 540 } 541 542let decode_canonical cbor = 543 let* elements = 544 Cbor.to_array cbor |> require "canonical block must be array" 545 in 546 if List.length elements < 5 then 547 Error (Truncated "canonical block needs at least 5 elements") 548 else 549 let get i = List.nth elements i in 550 let* type_int = 551 Cbor.to_int64 (get 0) |> require "block type must be uint" 552 in 553 let block_type = block_type_of_int (Int64.to_int type_int) in 554 let* block_number = 555 Cbor.to_int64 (get 1) |> require "block number must be uint" 556 in 557 let* flags_int = Cbor.to_int64 (get 2) |> require "flags must be uint" in 558 let flags = block_flags_of_int (Int64.to_int flags_int) in 559 let* crc_int = Cbor.to_int64 (get 3) |> require "CRC type must be uint" in 560 let* crc_type = 561 crc_type_of_int (Int64.to_int crc_int) 562 |> Result.map_error (fun n -> Invalid_crc_type n) 563 in 564 let* data_bytes = 565 Cbor.to_bytes (get 4) |> require "block data must be bytes" 566 in 567 let* data = decode_block_data block_type data_bytes in 568 Ok 569 { 570 block_type; 571 block_number = Int64.to_int block_number; 572 flags; 573 crc_type; 574 data; 575 } 576 577let of_cbor cbor = 578 let* elements = Cbor.to_array cbor |> require "bundle must be array" in 579 match elements with 580 | [] -> Error (Truncated "bundle must have at least primary block") 581 | primary_cbor :: blocks_cbor -> 582 let* primary = decode_primary primary_cbor in 583 let rec decode_blocks acc = function 584 | [] -> Ok (List.rev acc) 585 | b :: rest -> 586 let* block = decode_canonical b in 587 decode_blocks (block :: acc) rest 588 in 589 let* blocks = decode_blocks [] blocks_cbor in 590 let has_payload = List.exists (fun b -> b.block_type = Payload) blocks in 591 if has_payload then Ok { primary; blocks } 592 else Error Missing_payload_block 593 594let decode s = 595 match cbor_decode s with 596 | Some cbor -> of_cbor cbor 597 | None -> Error (Cbor_error "failed to decode CBOR") 598 599let read dec = 600 try 601 let cbor = Cbor_rw.read_cbor dec in 602 of_cbor cbor 603 with e -> Error (Cbor_error (Printexc.to_string e)) 604 605(* {1 Constructors} *) 606 607let v ?(flags = default_flags) ?(crc_type = Crc32c) ?report_to 608 ?(lifetime = 86400000L) ~source ~destination ~creation_timestamp ~payload () 609 = 610 let report_to = match report_to with Some e -> e | None -> source in 611 let primary = 612 { 613 version = 7; 614 flags; 615 crc_type; 616 destination; 617 source; 618 report_to; 619 creation_timestamp; 620 lifetime; 621 fragment_offset = None; 622 total_adu_length = None; 623 } 624 in 625 let payload_block = 626 { 627 block_type = Payload; 628 block_number = 1; 629 flags = block_flags_default; 630 crc_type; 631 data = Payload_data payload; 632 } 633 in 634 { primary; blocks = [ payload_block ] } 635 636let payload bundle = 637 List.find_map 638 (fun b -> match b.data with Payload_data p -> Some p | _ -> None) 639 bundle.blocks 640 641let previous_node bundle = 642 List.find_map 643 (fun b -> 644 match b.data with Previous_node_data eid -> Some eid | _ -> None) 645 bundle.blocks 646 647let age bundle = 648 List.find_map 649 (fun b -> match b.data with Bundle_age_data age -> Some age | _ -> None) 650 bundle.blocks 651 652let hop_count bundle = 653 List.find_map 654 (fun b -> 655 match b.data with 656 | Hop_count_data { limit; count } -> Some (limit, count) 657 | _ -> None) 658 bundle.blocks