AX.25 Amateur Radio Link-Layer Protocol
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

test(space): expand test suites for CCSDS and space protocol packages

+125
+125
test/test_ax25.ml
··· 307 307 (* PID for No_layer3 = 0xF0 *) 308 308 Alcotest.(check int) "PID" 0xF0 (Char.code (Bytes.get encoded 15)) 309 309 310 + (* {1 I-frame Sequence Counter Tests} *) 311 + 312 + let test_i_frame_ns_nr_wraparound () = 313 + (* AX.25 2.2: NS and NR are 3-bit counters cycling 0-7 *) 314 + for ns = 0 to 7 do 315 + for nr = 0 to 7 do 316 + let ctrl = I { ns; nr; poll = false } in 317 + let b = byte_of_control ctrl in 318 + match control_of_byte b with 319 + | I { ns = ns'; nr = nr'; poll } -> 320 + Alcotest.(check int) (Fmt.str "ns=%d" ns) ns ns'; 321 + Alcotest.(check int) (Fmt.str "nr=%d" nr) nr nr'; 322 + Alcotest.(check bool) "poll" false poll 323 + | _ -> Alcotest.failf "ns=%d nr=%d should decode as I frame" ns nr 324 + done 325 + done 326 + 327 + let test_i_frame_poll_final_bit () = 328 + (* Verify poll/final bit toggles correctly across all NS/NR values *) 329 + List.iter 330 + (fun poll -> 331 + let ctrl = I { ns = 0; nr = 0; poll } in 332 + let b = byte_of_control ctrl in 333 + match control_of_byte b with 334 + | I { poll = poll'; _ } -> 335 + Alcotest.(check bool) (Fmt.str "poll=%b" poll) poll poll' 336 + | _ -> Alcotest.fail "should decode as I frame") 337 + [ true; false ] 338 + 339 + let test_supervisory_nr_wraparound () = 340 + (* S-frames also use 3-bit NR counters *) 341 + for nr = 0 to 7 do 342 + List.iter 343 + (fun (name, mk) -> 344 + let ctrl = mk nr in 345 + let b = byte_of_control ctrl in 346 + match control_of_byte b with 347 + | RR { nr = nr'; _ } when name = "RR" -> 348 + Alcotest.(check int) (Fmt.str "RR nr=%d" nr) nr nr' 349 + | RNR { nr = nr'; _ } when name = "RNR" -> 350 + Alcotest.(check int) (Fmt.str "RNR nr=%d" nr) nr nr' 351 + | REJ { nr = nr'; _ } when name = "REJ" -> 352 + Alcotest.(check int) (Fmt.str "REJ nr=%d" nr) nr nr' 353 + | _ -> Alcotest.failf "%s nr=%d decode mismatch" name nr) 354 + [ 355 + ("RR", fun nr -> RR { nr; poll = false }); 356 + ("RNR", fun nr -> RNR { nr; poll = false }); 357 + ("REJ", fun nr -> REJ { nr; poll = false }); 358 + ] 359 + done 360 + 361 + (* {1 Maximum Digipeater Path Tests} *) 362 + 363 + let test_max_digipeaters () = 364 + (* AX.25 2.2 Section 6.4.1.4: up to 8 digipeaters *) 365 + let src = callsign_exn ~call:"N0CALL" ~ssid:0 in 366 + let dst = callsign_exn ~call:"CQ" ~ssid:0 in 367 + let digis = 368 + List.init 8 (fun i -> callsign_exn ~call:(Fmt.str "DIGI%d" i) ~ssid:i) 369 + in 370 + let info = Bytes.of_string "Max digis" in 371 + let frame = ui_frame ~src ~dst ~digis info in 372 + let encoded = encode frame in 373 + match decode encoded with 374 + | Error e -> Alcotest.failf "decode failed: %a" pp_error e 375 + | Ok frame' -> 376 + Alcotest.(check int) 377 + "8 digipeaters" 8 378 + (List.length frame'.address.digipeaters); 379 + Alcotest.(check string) 380 + "info preserved" "Max digis" 381 + (Bytes.to_string frame'.info) 382 + 383 + (* {1 Maximum Info Field Size Tests} *) 384 + 385 + let test_max_info_field () = 386 + (* AX.25 2.2: default maximum info field is 256 bytes *) 387 + let src = callsign_exn ~call:"N0CALL" ~ssid:0 in 388 + let dst = callsign_exn ~call:"CQ" ~ssid:0 in 389 + let info = Bytes.make 256 '\x41' in 390 + let frame = ui_frame ~src ~dst info in 391 + let encoded = encode frame in 392 + match decode encoded with 393 + | Error e -> Alcotest.failf "decode failed: %a" pp_error e 394 + | Ok frame' -> 395 + Alcotest.(check int) "info length" 256 (Bytes.length frame'.info); 396 + Alcotest.(check string) 397 + "info content" (Bytes.to_string info) 398 + (Bytes.to_string frame'.info) 399 + 400 + let test_i_frame_roundtrip () = 401 + (* I-frame with payload should encode/decode correctly *) 402 + let src = callsign_exn ~call:"N0CALL" ~ssid:1 in 403 + let dst = callsign_exn ~call:"W1AW" ~ssid:0 in 404 + let frame = 405 + { 406 + address = { destination = dst; source = src; digipeaters = [] }; 407 + control = I { ns = 5; nr = 3; poll = false }; 408 + pid = Some No_layer3; 409 + info = Bytes.of_string "I-frame data"; 410 + } 411 + in 412 + let encoded = encode frame in 413 + match decode encoded with 414 + | Error e -> Alcotest.failf "I-frame decode failed: %a" pp_error e 415 + | Ok frame' -> 416 + (match frame'.control with 417 + | I { ns; nr; poll } -> 418 + Alcotest.(check int) "ns" 5 ns; 419 + Alcotest.(check int) "nr" 3 nr; 420 + Alcotest.(check bool) "poll" false poll 421 + | _ -> Alcotest.fail "should decode as I frame"); 422 + Alcotest.(check string) 423 + "info" "I-frame data" 424 + (Bytes.to_string frame'.info) 425 + 310 426 (* {1 Test Suite} *) 311 427 312 428 let suite = ··· 345 461 Alcotest.test_case "spec minimum frame" `Quick test_spec_minimum_frame; 346 462 Alcotest.test_case "spec NJ7P-N7LEM frame" `Quick 347 463 test_spec_nj7p_n7lem_frame; 464 + Alcotest.test_case "I-frame NS/NR wraparound" `Quick 465 + test_i_frame_ns_nr_wraparound; 466 + Alcotest.test_case "I-frame poll/final bit" `Quick 467 + test_i_frame_poll_final_bit; 468 + Alcotest.test_case "supervisory NR wraparound" `Quick 469 + test_supervisory_nr_wraparound; 470 + Alcotest.test_case "max 8 digipeaters" `Quick test_max_digipeaters; 471 + Alcotest.test_case "max info field 256 bytes" `Quick test_max_info_field; 472 + Alcotest.test_case "I-frame roundtrip" `Quick test_i_frame_roundtrip; 348 473 ] )