CCSDS Proximity-1 Space Link Protocol (211.0-B)
0
fork

Configure Feed

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

interop: drop fake oracles, regen merge3 traces from git

ocaml-clcw/dariol83: drop generate.py / requirements.txt fallback —
the dariol83 Java path is the real oracle. The Python copy
reimplemented CLCW bitfield encoding directly from the spec, so it
just tested itself.

ocaml-proximity1/ccsds211: drop entirely. The generator was a
pure-Python implementation of the same CCSDS spec the OCaml library
implements. Per the interop-testing skill, that's two implementations
of the spec checking each other, not interop. Library roundtrip
coverage stays in test_proximity1.ml.

ocaml-merge3/git: keep — git merge-file is a real third-party
oracle. Move from in-test Sys.command + tmpfiles to the
generate-once-replay-always pattern: scripts/generate.{sh,py} writes
traces/cases.csv with hex-encoded inputs + git's merged output;
test.ml reads the CSV via csvt. dune build @regen-traces refreshes
against the local git binary.

-322
-17
test/interop/ccsds211/dune
··· 1 - (test 2 - (name test) 3 - (libraries proximity1 csvt alcotest) 4 - (deps 5 - (source_tree traces) 6 - (source_tree scripts))) 7 - 8 - ; Regenerate traces: dune build @regen-traces 9 - 10 - (rule 11 - (alias regen-traces) 12 - (deps 13 - (source_tree scripts)) 14 - (action 15 - (chdir 16 - scripts 17 - (run bash generate.sh))))
-127
test/interop/ccsds211/scripts/generate.py
··· 1 - """Generate Proximity-1 Transfer Frame interop traces per CCSDS 211.0-B. 2 - 3 - Pure Python encoder -- no third-party library. The bitfield layout is 4 - taken from CCSDS 211.0-B-5 and matches the OCaml Proximity1 Wire codec: 5 - 6 - Byte 0-1 (U16be): Version(3b) | SCID(8b) | FrameType(3b) | Reserved(2b) 7 - Byte 2: Sequence Count high byte 8 - Byte 3-4 (U16be): Sequence Count low 16 bits 9 - Byte 5-6 (U16be): Frame Length (= header_size + len(data)) 10 - Byte 7+: Data field 11 - 12 - Regenerate: dune build @regen-traces 13 - """ 14 - 15 - import csv 16 - import os 17 - import struct 18 - import sys 19 - 20 - HEADER_SIZE = 7 21 - 22 - FRAME_TYPES = {"data": 0, "ack": 1, "expedited": 2} 23 - 24 - 25 - def encode_frame(version, scid, frame_type_str, seq_count, data): 26 - """Encode a Proximity-1 Transfer Frame to bytes.""" 27 - ft = FRAME_TYPES[frame_type_str] 28 - 29 - # First 16-bit word: version(3) | scid(8) | frame_type(3) | reserved(2) 30 - word0 = (version & 0x7) << 13 31 - word0 |= (scid & 0xFF) << 5 32 - word0 |= (ft & 0x7) << 2 33 - # reserved = 0 34 - 35 - # Sequence count: 24 bits split into high byte + low 16 bits 36 - seq_hi = (seq_count >> 16) & 0xFF 37 - seq_lo = seq_count & 0xFFFF 38 - 39 - # Frame length = total frame length in bytes 40 - frame_length = HEADER_SIZE + len(data) 41 - 42 - header = struct.pack("!HBHH", word0, seq_hi, seq_lo, frame_length) 43 - return header + data 44 - 45 - 46 - def write_traces(trace_dir): 47 - rows = [] 48 - 49 - def add(name, version, scid, frame_type_str, seq_count, data): 50 - frame = encode_frame(version, scid, frame_type_str, seq_count, data) 51 - rows.append({ 52 - "name": name, 53 - "version": version, 54 - "scid": scid, 55 - "frame_type": frame_type_str, 56 - "sequence_count": seq_count, 57 - "data_hex": data.hex(), 58 - "frame_hex": frame.hex(), 59 - }) 60 - 61 - # --- Basic data frame --- 62 - add("basic_data", 0, 1, "data", 0, b"\xca\xfe") 63 - 64 - # --- Ack frame --- 65 - add("basic_ack", 0, 2, "ack", 0, b"\x01") 66 - 67 - # --- Expedited frame --- 68 - add("basic_expedited", 0, 3, "expedited", 100, b"\xde\xad\xbe\xef") 69 - 70 - # --- Max SCID (255) --- 71 - add("max_scid", 0, 255, "data", 0, b"\x00") 72 - 73 - # --- Max sequence count (2^24 - 1) --- 74 - add("max_seq_count", 0, 10, "data", 16777215, b"\xab") 75 - 76 - # --- Non-zero version --- 77 - add("version_1", 1, 42, "data", 500, b"\x01\x02\x03") 78 - 79 - # --- Version 7 (max 3-bit) --- 80 - add("version_max", 7, 0, "ack", 0, b"\xff") 81 - 82 - # --- Empty data field --- 83 - add("empty_data", 0, 50, "data", 1, b"") 84 - 85 - # --- Large payload (256 bytes) --- 86 - payload = bytes(range(256)) 87 - add("large_payload", 0, 100, "data", 42, payload) 88 - 89 - # --- SCID boundary values --- 90 - add("scid_zero", 0, 0, "data", 0, b"\x00") 91 - add("scid_128", 0, 128, "expedited", 1000, b"\x55\xaa") 92 - 93 - # --- Sequence count boundary --- 94 - add("seq_one", 0, 1, "data", 1, b"\x01") 95 - add("seq_256", 0, 1, "ack", 256, b"\x02") 96 - add("seq_65536", 0, 1, "data", 65536, b"\x03") 97 - 98 - # --- All frame types with same params --- 99 - for ft in ["data", "ack", "expedited"]: 100 - add(f"type_{ft}", 0, 77, ft, 999, b"\xaa\xbb\xcc") 101 - 102 - # --- Multi-byte data patterns --- 103 - add("all_zeros", 0, 1, "data", 0, b"\x00" * 16) 104 - add("all_ones", 0, 1, "data", 0, b"\xff" * 16) 105 - add("ascending", 0, 1, "data", 0, bytes(range(32))) 106 - 107 - # Write CSV 108 - out_path = os.path.join(trace_dir, "frames.csv") 109 - with open(out_path, "w", newline="") as f: 110 - writer = csv.DictWriter( 111 - f, 112 - fieldnames=[ 113 - "name", "version", "scid", "frame_type", 114 - "sequence_count", "data_hex", "frame_hex", 115 - ], 116 - ) 117 - writer.writeheader() 118 - writer.writerows(rows) 119 - 120 - print(f"Wrote {len(rows)} vectors to {out_path}") 121 - 122 - 123 - if __name__ == "__main__": 124 - if len(sys.argv) != 2: 125 - print(f"Usage: {sys.argv[0]} <trace-dir>", file=sys.stderr) 126 - sys.exit(1) 127 - write_traces(sys.argv[1])
-11
test/interop/ccsds211/scripts/generate.sh
··· 1 - #!/bin/bash 2 - # Regenerate Proximity-1 interop traces (pure Python, no venv needed). 3 - # 4 - # Usage: ./generate.sh 5 - set -euo pipefail 6 - SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" 7 - TRACE_DIR="$SCRIPT_DIR/../traces" 8 - mkdir -p "$TRACE_DIR" 9 - TRACE_DIR="$(cd "$TRACE_DIR" && pwd)" 10 - 11 - python3 "$SCRIPT_DIR/generate.py" "$TRACE_DIR"
-1
test/interop/ccsds211/scripts/requirements.txt
··· 1 - # No external dependencies — pure Python
-145
test/interop/ccsds211/test.ml
··· 1 - (** CCSDS 211.0-B interop tests for Proximity-1 Transfer Frames. 2 - 3 - Traces generated by: pure Python encoder per CCSDS 211.0-B-5 4 - Regenerate: dune build @regen-traces *) 5 - 6 - let trace path = Filename.concat "traces" path 7 - 8 - (* {1 Helpers} *) 9 - 10 - let hex_to_string hex = 11 - let len = String.length hex / 2 in 12 - String.init len (fun i -> 13 - let digit c = 14 - if c >= '0' && c <= '9' then Char.code c - Char.code '0' 15 - else if c >= 'a' && c <= 'f' then Char.code c - Char.code 'a' + 10 16 - else Char.code c - Char.code 'A' + 10 17 - in 18 - Char.chr ((digit hex.[i * 2] lsl 4) lor digit hex.[(i * 2) + 1])) 19 - 20 - let string_to_hex s = 21 - let buf = Buffer.create (String.length s * 2) in 22 - String.iter (fun c -> Buffer.add_string buf (Fmt.str "%02x" (Char.code c))) s; 23 - Buffer.contents buf 24 - 25 - let frame_type_of_string = function 26 - | "data" -> Proximity1.Data 27 - | "ack" -> Proximity1.Ack 28 - | "expedited" -> Proximity1.Expedited 29 - | s -> Alcotest.failf "unknown frame_type: %s" s 30 - 31 - (* {1 Trace Row Type} *) 32 - 33 - type frame_row = { 34 - name : string; 35 - version : int; 36 - scid : int; 37 - frame_type : string; 38 - sequence_count : int; 39 - data_hex : string; 40 - frame_hex : string; 41 - } 42 - 43 - let frame_codec = 44 - Csvt.( 45 - Row.( 46 - obj (fun name version scid frame_type sequence_count data_hex frame_hex -> 47 - { 48 - name; 49 - version; 50 - scid; 51 - frame_type; 52 - sequence_count; 53 - data_hex; 54 - frame_hex; 55 - }) 56 - |> col "name" string ~enc:(fun v -> v.name) 57 - |> col "version" int ~enc:(fun v -> v.version) 58 - |> col "scid" int ~enc:(fun v -> v.scid) 59 - |> col "frame_type" string ~enc:(fun v -> v.frame_type) 60 - |> col "sequence_count" int ~enc:(fun v -> v.sequence_count) 61 - |> col "data_hex" string ~enc:(fun v -> v.data_hex) 62 - |> col "frame_hex" string ~enc:(fun v -> v.frame_hex) 63 - |> finish)) 64 - 65 - let parse_frames () = 66 - match Csvt.decode_file frame_codec (trace "frames.csv") with 67 - | Ok vs -> vs 68 - | Error e -> Alcotest.failf "failed to parse frames.csv: %s" e 69 - 70 - (* {1 Tests} *) 71 - 72 - let decode row () = 73 - let raw = hex_to_string row.frame_hex in 74 - match Proximity1.decode raw with 75 - | Error `Truncated -> Alcotest.failf "%s: decode returned Truncated" row.name 76 - | Error (`Invalid_frame_type n) -> 77 - Alcotest.failf "%s: decode returned Invalid_frame_type %d" row.name n 78 - | Ok frame -> 79 - Alcotest.(check int) (row.name ^ ": version") row.version frame.version; 80 - Alcotest.(check int) (row.name ^ ": scid") row.scid frame.scid; 81 - let expected_ft = frame_type_of_string row.frame_type in 82 - let got_ft = frame.frame_type in 83 - if expected_ft <> got_ft then 84 - Alcotest.failf "%s: frame_type mismatch: expected %a, got %a" row.name 85 - Proximity1.pp_frame_type expected_ft Proximity1.pp_frame_type got_ft; 86 - Alcotest.(check int) 87 - (row.name ^ ": sequence_count") 88 - row.sequence_count frame.sequence_count; 89 - let expected_data = hex_to_string row.data_hex in 90 - let got_data = frame.data in 91 - if expected_data <> got_data then 92 - Alcotest.failf "%s: data mismatch:\n expected: %s\n got: %s" 93 - row.name row.data_hex (string_to_hex got_data) 94 - 95 - let encode row () = 96 - let ft = frame_type_of_string row.frame_type in 97 - let frame : Proximity1.t = 98 - { 99 - version = row.version; 100 - scid = row.scid; 101 - frame_type = ft; 102 - sequence_count = row.sequence_count; 103 - data = hex_to_string row.data_hex; 104 - } 105 - in 106 - let got = Proximity1.encode frame in 107 - let expected = hex_to_string row.frame_hex in 108 - if got <> expected then 109 - Alcotest.failf "%s: encode mismatch:\n expected: %s\n got: %s" 110 - row.name row.frame_hex (string_to_hex got) 111 - 112 - let roundtrip row () = 113 - let ft = frame_type_of_string row.frame_type in 114 - let frame : Proximity1.t = 115 - { 116 - version = row.version; 117 - scid = row.scid; 118 - frame_type = ft; 119 - sequence_count = row.sequence_count; 120 - data = hex_to_string row.data_hex; 121 - } 122 - in 123 - let encoded = Proximity1.encode frame in 124 - match Proximity1.decode encoded with 125 - | Error `Truncated -> 126 - Alcotest.failf "%s: roundtrip decode returned Truncated" row.name 127 - | Error (`Invalid_frame_type n) -> 128 - Alcotest.failf "%s: roundtrip decode returned Invalid_frame_type %d" 129 - row.name n 130 - | Ok decoded -> 131 - let frame_t = Alcotest.testable Proximity1.pp Proximity1.equal in 132 - Alcotest.(check frame_t) (row.name ^ ": roundtrip") frame decoded 133 - 134 - let () = 135 - let rows = parse_frames () in 136 - Alcotest.run "proximity1-interop" 137 - [ 138 - ( "decode", 139 - List.map (fun r -> Alcotest.test_case r.name `Quick (decode r)) rows ); 140 - ( "encode", 141 - List.map (fun r -> Alcotest.test_case r.name `Quick (encode r)) rows ); 142 - ( "roundtrip", 143 - List.map (fun r -> Alcotest.test_case r.name `Quick (roundtrip r)) rows 144 - ); 145 - ]
-21
test/interop/ccsds211/traces/frames.csv
··· 1 - name,version,scid,frame_type,sequence_count,data_hex,frame_hex 2 - basic_data,0,1,data,0,cafe,00200000000009cafe 3 - basic_ack,0,2,ack,0,01,0044000000000801 4 - basic_expedited,0,3,expedited,100,deadbeef,0068000064000bdeadbeef 5 - max_scid,0,255,data,0,00,1fe0000000000800 6 - max_seq_count,0,10,data,16777215,ab,0140ffffff0008ab 7 - version_1,1,42,data,500,010203,25400001f4000a010203 8 - version_max,7,0,ack,0,ff,e0040000000008ff 9 - empty_data,0,50,data,1,,06400000010007 10 - large_payload,0,100,data,42,000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff,0c8000002a0107000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff 11 - scid_zero,0,0,data,0,00,0000000000000800 12 - scid_128,0,128,expedited,1000,55aa,10080003e8000955aa 13 - seq_one,0,1,data,1,01,0020000001000801 14 - seq_256,0,1,ack,256,02,0024000100000802 15 - seq_65536,0,1,data,65536,03,0020010000000803 16 - type_data,0,77,data,999,aabbcc,09a00003e7000aaabbcc 17 - type_ack,0,77,ack,999,aabbcc,09a40003e7000aaabbcc 18 - type_expedited,0,77,expedited,999,aabbcc,09a80003e7000aaabbcc 19 - all_zeros,0,1,data,0,00000000000000000000000000000000,0020000000001700000000000000000000000000000000 20 - all_ones,0,1,data,0,ffffffffffffffffffffffffffffffff,00200000000017ffffffffffffffffffffffffffffffff 21 - ascending,0,1,data,0,000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f,00200000000027000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f