Bundle Protocol Version 7 (RFC 9171) for Delay-Tolerant Networking
at main 525 lines 18 kB view raw
1(*--------------------------------------------------------------------------- 2 Copyright (c) 2025 Thomas Gazagnaire. All rights reserved. 3 SPDX-License-Identifier: ISC 4 ---------------------------------------------------------------------------*) 5 6(* Test vectors based on RFC 9171 examples *) 7 8let eid_testable = Alcotest.testable Bundle.pp_eid ( = ) 9let error_testable = Alcotest.testable Bundle.pp_error ( = ) 10 11let bundle_testable = 12 Alcotest.testable Bundle.pp (fun a b -> Bundle.encode a = Bundle.encode b) 13 14let test_eid_dtn_none () = 15 let eid = Bundle.Dtn_none in 16 let cbor = Bundle.eid_to_cbor eid in 17 let decoded = Bundle.eid_of_cbor cbor in 18 Alcotest.(check (result eid_testable string)) 19 "dtn:none roundtrip" (Ok eid) decoded 20 21let test_eid_dtn () = 22 let eid = Bundle.Dtn "//node-1/incoming" in 23 let cbor = Bundle.eid_to_cbor eid in 24 let decoded = Bundle.eid_of_cbor cbor in 25 Alcotest.(check (result eid_testable string)) 26 "dtn URI roundtrip" (Ok eid) decoded 27 28let test_eid_ipn () = 29 let eid = Bundle.Ipn (1L, 1L) in 30 let cbor = Bundle.eid_to_cbor eid in 31 let decoded = Bundle.eid_of_cbor cbor in 32 Alcotest.(check (result eid_testable string)) 33 "ipn:1.1 roundtrip" (Ok eid) decoded 34 35let test_bundle_flags () = 36 let flags = 37 Bundle.{ default_flags with ack_requested = true; report_delivery = true } 38 in 39 let encoded = Bundle.int_of_flags flags in 40 let decoded = Bundle.flags_of_int encoded in 41 Alcotest.(check bool) 42 "ack_requested" flags.ack_requested decoded.ack_requested; 43 Alcotest.(check bool) 44 "report_delivery" flags.report_delivery decoded.report_delivery; 45 Alcotest.(check bool) "is_fragment" flags.is_fragment decoded.is_fragment 46 47let test_block_flags () = 48 let flags = 49 Bundle.{ block_flags_default with replicate_in_fragment = true } 50 in 51 let encoded = Bundle.int_of_block_flags flags in 52 let decoded = Bundle.block_flags_of_int encoded in 53 Alcotest.(check bool) 54 "replicate_in_fragment" flags.replicate_in_fragment 55 decoded.replicate_in_fragment 56 57let test_crc_type () = 58 Alcotest.(check int) "No_crc" 0 (Bundle.int_of_crc_type Bundle.No_crc); 59 Alcotest.(check int) "Crc16" 1 (Bundle.int_of_crc_type Bundle.Crc16); 60 Alcotest.(check int) "Crc32c" 2 (Bundle.int_of_crc_type Bundle.Crc32c); 61 Alcotest.(check (result (of_pp Bundle.pp_crc_type) int)) 62 "decode 0" (Ok Bundle.No_crc) (Bundle.crc_type_of_int 0); 63 Alcotest.(check (result (of_pp Bundle.pp_crc_type) int)) 64 "decode 2" (Ok Bundle.Crc32c) (Bundle.crc_type_of_int 2); 65 Alcotest.(check (result (of_pp Bundle.pp_crc_type) int)) 66 "decode invalid" (Error 99) 67 (Bundle.crc_type_of_int 99) 68 69let test_simple_bundle () = 70 let bundle = 71 Bundle.v 72 ~source:(Bundle.Ipn (1L, 1L)) 73 ~destination:(Bundle.Ipn (2L, 1L)) 74 ~creation_timestamp:{ Bundle.time = 0L; seq = 0L } 75 ~payload:"Hello, DTN!" () 76 in 77 Alcotest.(check (option string)) 78 "payload" (Some "Hello, DTN!") (Bundle.payload bundle); 79 Alcotest.(check int) "version" 7 bundle.primary.version; 80 Alcotest.(check (of_pp Bundle.pp_eid)) 81 "source" 82 (Bundle.Ipn (1L, 1L)) 83 bundle.primary.source; 84 Alcotest.(check (of_pp Bundle.pp_eid)) 85 "destination" 86 (Bundle.Ipn (2L, 1L)) 87 bundle.primary.destination 88 89let test_bundle_roundtrip () = 90 let bundle = 91 Bundle.v ~source:(Bundle.Dtn "//sender/app") 92 ~destination:(Bundle.Dtn "//receiver/app") 93 ~creation_timestamp:{ Bundle.time = 1000000L; seq = 42L } 94 ~lifetime:3600000L ~payload:"Test payload data" () 95 in 96 let encoded = Bundle.encode bundle in 97 let decoded = Bundle.decode encoded in 98 match decoded with 99 | Ok decoded_bundle -> 100 Alcotest.(check (option string)) 101 "payload preserved" (Bundle.payload bundle) 102 (Bundle.payload decoded_bundle); 103 Alcotest.(check (of_pp Bundle.pp_eid)) 104 "source preserved" bundle.primary.source decoded_bundle.primary.source; 105 Alcotest.(check (of_pp Bundle.pp_eid)) 106 "destination preserved" bundle.primary.destination 107 decoded_bundle.primary.destination; 108 Alcotest.(check int64) 109 "lifetime preserved" bundle.primary.lifetime 110 decoded_bundle.primary.lifetime 111 | Error e -> Alcotest.failf "decode failed: %a" Bundle.pp_error e 112 113let test_bundle_no_crc () = 114 let bundle = 115 Bundle.v ~crc_type:Bundle.No_crc 116 ~source:(Bundle.Ipn (1L, 1L)) 117 ~destination:(Bundle.Ipn (2L, 1L)) 118 ~creation_timestamp:{ Bundle.time = 0L; seq = 0L } 119 ~payload:"No CRC test" () 120 in 121 let encoded = Bundle.encode bundle in 122 let decoded = Bundle.decode encoded in 123 Alcotest.(check (result bundle_testable error_testable)) 124 "no CRC roundtrip" (Ok bundle) decoded 125 126let test_bundle_crc16 () = 127 let bundle = 128 Bundle.v ~crc_type:Bundle.Crc16 129 ~source:(Bundle.Ipn (1L, 1L)) 130 ~destination:(Bundle.Ipn (2L, 1L)) 131 ~creation_timestamp:{ Bundle.time = 0L; seq = 0L } 132 ~payload:"CRC-16 test" () 133 in 134 let encoded = Bundle.encode bundle in 135 let decoded = Bundle.decode encoded in 136 match decoded with 137 | Ok _ -> () 138 | Error e -> 139 Alcotest.fail 140 (Fmt.str "CRC-16 bundle decode failed: %a" Bundle.pp_error e) 141 142(* {1 Fragment Tests} *) 143 144let test_fragment_fields () = 145 (* RFC 9171 Section 4.3.1: fragment_offset and total_adu_length *) 146 let flags = Bundle.{ default_flags with is_fragment = true } in 147 let primary = 148 { 149 Bundle.version = 7; 150 flags; 151 crc_type = Bundle.Crc32c; 152 destination = Bundle.Ipn (2L, 1L); 153 source = Bundle.Ipn (1L, 1L); 154 report_to = Bundle.Ipn (1L, 1L); 155 creation_timestamp = { Bundle.time = 1000L; seq = 0L }; 156 lifetime = 3600000L; 157 fragment_offset = Some 1024L; 158 total_adu_length = Some 4096L; 159 } 160 in 161 let payload_block = 162 { 163 Bundle.block_type = Bundle.Payload; 164 block_number = 1; 165 flags = Bundle.block_flags_default; 166 crc_type = Bundle.Crc32c; 167 data = Bundle.Payload_data "fragment data"; 168 } 169 in 170 let bundle = { Bundle.primary; blocks = [ payload_block ] } in 171 let encoded = Bundle.encode bundle in 172 match Bundle.decode encoded with 173 | Ok decoded -> 174 Alcotest.(check bool) "is_fragment" true decoded.primary.flags.is_fragment; 175 Alcotest.(check (option int64)) 176 "fragment_offset" (Some 1024L) decoded.primary.fragment_offset; 177 Alcotest.(check (option int64)) 178 "total_adu_length" (Some 4096L) decoded.primary.total_adu_length 179 | Error e -> Alcotest.failf "fragment decode failed: %a" Bundle.pp_error e 180 181let test_fragment_offset_zero () = 182 (* First fragment has offset 0 *) 183 let flags = Bundle.{ default_flags with is_fragment = true } in 184 let primary = 185 { 186 Bundle.version = 7; 187 flags; 188 crc_type = Bundle.No_crc; 189 destination = Bundle.Ipn (2L, 1L); 190 source = Bundle.Ipn (1L, 1L); 191 report_to = Bundle.Ipn (1L, 1L); 192 creation_timestamp = { Bundle.time = 0L; seq = 0L }; 193 lifetime = 3600000L; 194 fragment_offset = Some 0L; 195 total_adu_length = Some 2048L; 196 } 197 in 198 let payload_block = 199 { 200 Bundle.block_type = Bundle.Payload; 201 block_number = 1; 202 flags = Bundle.block_flags_default; 203 crc_type = Bundle.No_crc; 204 data = Bundle.Payload_data "first fragment"; 205 } 206 in 207 let bundle = { Bundle.primary; blocks = [ payload_block ] } in 208 let encoded = Bundle.encode bundle in 209 match Bundle.decode encoded with 210 | Ok decoded -> 211 Alcotest.(check (option int64)) 212 "fragment_offset" (Some 0L) decoded.primary.fragment_offset 213 | Error e -> Alcotest.failf "decode failed: %a" Bundle.pp_error e 214 215(* {1 Additional Block Type Tests} *) 216 217let test_hop_count_block () = 218 (* RFC 9171 Section 4.4.4: Hop Count block type 10 *) 219 let primary = 220 { 221 Bundle.version = 7; 222 flags = Bundle.default_flags; 223 crc_type = Bundle.No_crc; 224 destination = Bundle.Ipn (2L, 1L); 225 source = Bundle.Ipn (1L, 1L); 226 report_to = Bundle.Ipn (1L, 1L); 227 creation_timestamp = { Bundle.time = 0L; seq = 0L }; 228 lifetime = 3600000L; 229 fragment_offset = None; 230 total_adu_length = None; 231 } 232 in 233 let hop_count_block = 234 { 235 Bundle.block_type = Bundle.Hop_count; 236 block_number = 2; 237 flags = Bundle.block_flags_default; 238 crc_type = Bundle.No_crc; 239 data = Bundle.Hop_count_data { limit = 30; count = 5 }; 240 } 241 in 242 let payload_block = 243 { 244 Bundle.block_type = Bundle.Payload; 245 block_number = 1; 246 flags = Bundle.block_flags_default; 247 crc_type = Bundle.No_crc; 248 data = Bundle.Payload_data "test"; 249 } 250 in 251 let bundle = 252 { Bundle.primary; blocks = [ hop_count_block; payload_block ] } 253 in 254 let encoded = Bundle.encode bundle in 255 match Bundle.decode encoded with 256 | Ok decoded -> 257 Alcotest.(check (option (pair int int))) 258 "hop count" 259 (Some (30, 5)) 260 (Bundle.hop_count decoded) 261 | Error e -> Alcotest.failf "decode failed: %a" Bundle.pp_error e 262 263let test_previous_node_block () = 264 (* RFC 9171 Section 4.4.2: Previous Node block type 6 *) 265 let prev_eid = Bundle.Ipn (3L, 0L) in 266 let primary = 267 { 268 Bundle.version = 7; 269 flags = Bundle.default_flags; 270 crc_type = Bundle.No_crc; 271 destination = Bundle.Ipn (2L, 1L); 272 source = Bundle.Ipn (1L, 1L); 273 report_to = Bundle.Ipn (1L, 1L); 274 creation_timestamp = { Bundle.time = 0L; seq = 0L }; 275 lifetime = 3600000L; 276 fragment_offset = None; 277 total_adu_length = None; 278 } 279 in 280 let prev_node_block = 281 { 282 Bundle.block_type = Bundle.Previous_node; 283 block_number = 2; 284 flags = Bundle.block_flags_default; 285 crc_type = Bundle.No_crc; 286 data = Bundle.Previous_node_data prev_eid; 287 } 288 in 289 let payload_block = 290 { 291 Bundle.block_type = Bundle.Payload; 292 block_number = 1; 293 flags = Bundle.block_flags_default; 294 crc_type = Bundle.No_crc; 295 data = Bundle.Payload_data "test"; 296 } 297 in 298 let bundle = 299 { Bundle.primary; blocks = [ prev_node_block; payload_block ] } 300 in 301 let encoded = Bundle.encode bundle in 302 match Bundle.decode encoded with 303 | Ok decoded -> 304 Alcotest.(check (option (of_pp Bundle.pp_eid))) 305 "previous node" (Some prev_eid) 306 (Bundle.previous_node decoded) 307 | Error e -> Alcotest.failf "decode failed: %a" Bundle.pp_error e 308 309let test_bundle_age_block () = 310 (* RFC 9171 Section 4.4.3: Bundle Age block type 7 *) 311 let primary = 312 { 313 Bundle.version = 7; 314 flags = Bundle.default_flags; 315 crc_type = Bundle.No_crc; 316 destination = Bundle.Ipn (2L, 1L); 317 source = Bundle.Ipn (1L, 1L); 318 report_to = Bundle.Ipn (1L, 1L); 319 creation_timestamp = { Bundle.time = 0L; seq = 0L }; 320 lifetime = 3600000L; 321 fragment_offset = None; 322 total_adu_length = None; 323 } 324 in 325 let age_block = 326 { 327 Bundle.block_type = Bundle.Bundle_age; 328 block_number = 2; 329 flags = Bundle.block_flags_default; 330 crc_type = Bundle.No_crc; 331 data = Bundle.Bundle_age_data 12345L; 332 } 333 in 334 let payload_block = 335 { 336 Bundle.block_type = Bundle.Payload; 337 block_number = 1; 338 flags = Bundle.block_flags_default; 339 crc_type = Bundle.No_crc; 340 data = Bundle.Payload_data "test"; 341 } 342 in 343 let bundle = { Bundle.primary; blocks = [ age_block; payload_block ] } in 344 let encoded = Bundle.encode bundle in 345 match Bundle.decode encoded with 346 | Ok decoded -> 347 Alcotest.(check (option int64)) 348 "bundle age" (Some 12345L) (Bundle.age decoded) 349 | Error e -> Alcotest.failf "decode failed: %a" Bundle.pp_error e 350 351(* {1 do_not_fragment Flag Tests} *) 352 353let test_do_not_fragment_flag () = 354 (* RFC 9171 Section 4.2.3: must_not_fragment flag *) 355 let flags = Bundle.{ default_flags with must_not_fragment = true } in 356 let encoded_flags = Bundle.int_of_flags flags in 357 Alcotest.(check bool) "bit 2 set" true (encoded_flags land 0x04 <> 0); 358 let decoded_flags = Bundle.flags_of_int encoded_flags in 359 Alcotest.(check bool) "must_not_fragment" true decoded_flags.must_not_fragment; 360 (* Roundtrip through a full bundle *) 361 let bundle = 362 Bundle.v ~flags 363 ~source:(Bundle.Ipn (1L, 1L)) 364 ~destination:(Bundle.Ipn (2L, 1L)) 365 ~creation_timestamp:{ Bundle.time = 0L; seq = 0L } 366 ~payload:"no fragment" () 367 in 368 let encoded = Bundle.encode bundle in 369 match Bundle.decode encoded with 370 | Ok decoded -> 371 Alcotest.(check bool) 372 "must_not_fragment preserved" true 373 decoded.primary.flags.must_not_fragment 374 | Error e -> Alcotest.failf "decode failed: %a" Bundle.pp_error e 375 376(* {1 CRC-32C Tests} *) 377 378let test_crc32c_roundtrip () = 379 (* Verify CRC-32C encoding and decoding *) 380 let bundle = 381 Bundle.v ~crc_type:Bundle.Crc32c 382 ~source:(Bundle.Ipn (1L, 1L)) 383 ~destination:(Bundle.Ipn (2L, 1L)) 384 ~creation_timestamp:{ Bundle.time = 500000L; seq = 1L } 385 ~payload:"CRC-32C payload" () 386 in 387 let encoded = Bundle.encode bundle in 388 match Bundle.decode encoded with 389 | Ok decoded -> 390 Alcotest.(check (option string)) 391 "payload preserved" (Some "CRC-32C payload") (Bundle.payload decoded); 392 Alcotest.(check (of_pp Bundle.pp_crc_type)) 393 "crc_type preserved" Bundle.Crc32c decoded.primary.crc_type 394 | Error e -> Alcotest.failf "CRC-32C decode failed: %a" Bundle.pp_error e 395 396let test_crc32c_compute () = 397 (* Verify compute_crc produces correct length for CRC-32C *) 398 let data = "test data for CRC-32C" in 399 let crc = Bundle.compute_crc Bundle.Crc32c data in 400 Alcotest.(check int) "CRC-32C is 4 bytes" 4 (String.length crc); 401 (* No_crc should produce empty string *) 402 let no_crc = Bundle.compute_crc Bundle.No_crc data in 403 Alcotest.(check int) "No_crc is 0 bytes" 0 (String.length no_crc); 404 (* CRC-16 should produce 2 bytes *) 405 let crc16 = Bundle.compute_crc Bundle.Crc16 data in 406 Alcotest.(check int) "CRC-16 is 2 bytes" 2 (String.length crc16) 407 408(* {1 Multiple Extension Blocks Test} *) 409 410let test_multiple_extension_blocks () = 411 (* Bundle with hop count, previous node, bundle age, and payload *) 412 let primary = 413 { 414 Bundle.version = 7; 415 flags = Bundle.default_flags; 416 crc_type = Bundle.Crc32c; 417 destination = Bundle.Dtn "//receiver/app"; 418 source = Bundle.Dtn "//sender/app"; 419 report_to = Bundle.Dtn "//sender/app"; 420 creation_timestamp = { Bundle.time = 1000000L; seq = 0L }; 421 lifetime = 7200000L; 422 fragment_offset = None; 423 total_adu_length = None; 424 } 425 in 426 let hop_count_block = 427 { 428 Bundle.block_type = Bundle.Hop_count; 429 block_number = 2; 430 flags = Bundle.block_flags_default; 431 crc_type = Bundle.No_crc; 432 data = Bundle.Hop_count_data { limit = 20; count = 0 }; 433 } 434 in 435 let prev_node_block = 436 { 437 Bundle.block_type = Bundle.Previous_node; 438 block_number = 3; 439 flags = Bundle.block_flags_default; 440 crc_type = Bundle.No_crc; 441 data = Bundle.Previous_node_data (Bundle.Ipn (5L, 0L)); 442 } 443 in 444 let age_block = 445 { 446 Bundle.block_type = Bundle.Bundle_age; 447 block_number = 4; 448 flags = Bundle.block_flags_default; 449 crc_type = Bundle.No_crc; 450 data = Bundle.Bundle_age_data 500L; 451 } 452 in 453 let payload_block = 454 { 455 Bundle.block_type = Bundle.Payload; 456 block_number = 1; 457 flags = Bundle.block_flags_default; 458 crc_type = Bundle.Crc32c; 459 data = Bundle.Payload_data "multi-block bundle"; 460 } 461 in 462 let bundle = 463 { 464 Bundle.primary; 465 blocks = [ hop_count_block; prev_node_block; age_block; payload_block ]; 466 } 467 in 468 let encoded = Bundle.encode bundle in 469 match Bundle.decode encoded with 470 | Ok decoded -> 471 Alcotest.(check (option (pair int int))) 472 "hop count" 473 (Some (20, 0)) 474 (Bundle.hop_count decoded); 475 Alcotest.(check (option (of_pp Bundle.pp_eid))) 476 "previous node" 477 (Some (Bundle.Ipn (5L, 0L))) 478 (Bundle.previous_node decoded); 479 Alcotest.(check (option int64)) 480 "bundle age" (Some 500L) (Bundle.age decoded); 481 Alcotest.(check (option string)) 482 "payload" (Some "multi-block bundle") (Bundle.payload decoded) 483 | Error e -> Alcotest.failf "decode failed: %a" Bundle.pp_error e 484 485let test_block_type_codes () = 486 (* RFC 9171 Section 4.4: verify block type integer codes *) 487 Alcotest.(check int) "Payload" 1 (Bundle.int_of_block_type Bundle.Payload); 488 Alcotest.(check int) 489 "Previous_node" 6 490 (Bundle.int_of_block_type Bundle.Previous_node); 491 Alcotest.(check int) 492 "Bundle_age" 7 493 (Bundle.int_of_block_type Bundle.Bundle_age); 494 Alcotest.(check int) 495 "Hop_count" 10 496 (Bundle.int_of_block_type Bundle.Hop_count); 497 Alcotest.(check int) 498 "Other 200" 200 499 (Bundle.int_of_block_type (Bundle.Other 200)) 500 501let suite = 502 ( "bundle", 503 [ 504 Alcotest.test_case "EID dtn:none" `Quick test_eid_dtn_none; 505 Alcotest.test_case "EID dtn URI" `Quick test_eid_dtn; 506 Alcotest.test_case "EID ipn" `Quick test_eid_ipn; 507 Alcotest.test_case "bundle flags" `Quick test_bundle_flags; 508 Alcotest.test_case "block flags" `Quick test_block_flags; 509 Alcotest.test_case "CRC type" `Quick test_crc_type; 510 Alcotest.test_case "simple bundle" `Quick test_simple_bundle; 511 Alcotest.test_case "roundtrip" `Quick test_bundle_roundtrip; 512 Alcotest.test_case "no CRC" `Quick test_bundle_no_crc; 513 Alcotest.test_case "CRC-16" `Quick test_bundle_crc16; 514 Alcotest.test_case "fragment fields" `Quick test_fragment_fields; 515 Alcotest.test_case "fragment offset zero" `Quick test_fragment_offset_zero; 516 Alcotest.test_case "hop count block" `Quick test_hop_count_block; 517 Alcotest.test_case "previous node block" `Quick test_previous_node_block; 518 Alcotest.test_case "bundle age block" `Quick test_bundle_age_block; 519 Alcotest.test_case "do_not_fragment flag" `Quick test_do_not_fragment_flag; 520 Alcotest.test_case "CRC-32C roundtrip" `Quick test_crc32c_roundtrip; 521 Alcotest.test_case "CRC-32C compute" `Quick test_crc32c_compute; 522 Alcotest.test_case "multiple extension blocks" `Quick 523 test_multiple_extension_blocks; 524 Alcotest.test_case "block type codes" `Quick test_block_type_codes; 525 ] )