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.

irmin: clean up dead files, document structure

+328
+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"
+152
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 23 + (fun c -> Buffer.add_string buf (Printf.sprintf "%02x" (Char.code c))) 24 + s; 25 + Buffer.contents buf 26 + 27 + let frame_type_of_string = function 28 + | "data" -> Proximity1.Data 29 + | "ack" -> Proximity1.Ack 30 + | "expedited" -> Proximity1.Expedited 31 + | s -> Alcotest.failf "unknown frame_type: %s" s 32 + 33 + (* {1 Trace Row Type} *) 34 + 35 + type frame_row = { 36 + name : string; 37 + version : int; 38 + scid : int; 39 + frame_type : string; 40 + sequence_count : int; 41 + data_hex : string; 42 + frame_hex : string; 43 + } 44 + 45 + let frame_codec = 46 + Csvt.( 47 + Row.( 48 + obj (fun name version scid frame_type sequence_count data_hex frame_hex -> 49 + { 50 + name; 51 + version; 52 + scid; 53 + frame_type; 54 + sequence_count; 55 + data_hex; 56 + frame_hex; 57 + }) 58 + |> col "name" string ~enc:(fun v -> v.name) 59 + |> col "version" int ~enc:(fun v -> v.version) 60 + |> col "scid" int ~enc:(fun v -> v.scid) 61 + |> col "frame_type" string ~enc:(fun v -> v.frame_type) 62 + |> col "sequence_count" int ~enc:(fun v -> v.sequence_count) 63 + |> col "data_hex" string ~enc:(fun v -> v.data_hex) 64 + |> col "frame_hex" string ~enc:(fun v -> v.frame_hex) 65 + |> finish)) 66 + 67 + let parse_frames () = 68 + match Csvt.decode_file frame_codec (trace "frames.csv") with 69 + | Ok vs -> vs 70 + | Error e -> Alcotest.failf "failed to parse frames.csv: %a" Csvt.pp_error e 71 + 72 + (* {1 Tests} *) 73 + 74 + let test_decode row () = 75 + let raw = hex_to_string row.frame_hex in 76 + match Proximity1.decode raw with 77 + | Error `Truncated -> Alcotest.failf "%s: decode returned Truncated" row.name 78 + | Error (`Invalid_frame_type n) -> 79 + Alcotest.failf "%s: decode returned Invalid_frame_type %d" row.name n 80 + | Ok frame -> 81 + Alcotest.(check int) (row.name ^ ": version") row.version frame.version; 82 + Alcotest.(check int) (row.name ^ ": scid") row.scid frame.scid; 83 + let expected_ft = frame_type_of_string row.frame_type in 84 + let got_ft = frame.frame_type in 85 + if expected_ft <> got_ft then 86 + Alcotest.failf "%s: frame_type mismatch: expected %a, got %a" row.name 87 + Proximity1.pp_frame_type expected_ft Proximity1.pp_frame_type got_ft; 88 + Alcotest.(check int) 89 + (row.name ^ ": sequence_count") 90 + row.sequence_count frame.sequence_count; 91 + let expected_data = hex_to_string row.data_hex in 92 + let got_data = frame.data in 93 + if expected_data <> got_data then 94 + Alcotest.failf "%s: data mismatch:\n expected: %s\n got: %s" 95 + row.name row.data_hex (string_to_hex got_data) 96 + 97 + let test_encode row () = 98 + let ft = frame_type_of_string row.frame_type in 99 + let frame : Proximity1.t = 100 + { 101 + version = row.version; 102 + scid = row.scid; 103 + frame_type = ft; 104 + sequence_count = row.sequence_count; 105 + data = hex_to_string row.data_hex; 106 + } 107 + in 108 + let got = Proximity1.encode frame in 109 + let expected = hex_to_string row.frame_hex in 110 + if got <> expected then 111 + Alcotest.failf "%s: encode mismatch:\n expected: %s\n got: %s" 112 + row.name row.frame_hex (string_to_hex got) 113 + 114 + let test_roundtrip row () = 115 + let ft = frame_type_of_string row.frame_type in 116 + let frame : Proximity1.t = 117 + { 118 + version = row.version; 119 + scid = row.scid; 120 + frame_type = ft; 121 + sequence_count = row.sequence_count; 122 + data = hex_to_string row.data_hex; 123 + } 124 + in 125 + let encoded = Proximity1.encode frame in 126 + match Proximity1.decode encoded with 127 + | Error `Truncated -> 128 + Alcotest.failf "%s: roundtrip decode returned Truncated" row.name 129 + | Error (`Invalid_frame_type n) -> 130 + Alcotest.failf "%s: roundtrip decode returned Invalid_frame_type %d" 131 + row.name n 132 + | Ok decoded -> 133 + let frame_t = Alcotest.testable Proximity1.pp Proximity1.equal in 134 + Alcotest.(check frame_t) (row.name ^ ": roundtrip") frame decoded 135 + 136 + let () = 137 + let rows = parse_frames () in 138 + Alcotest.run "proximity1-interop" 139 + [ 140 + ( "decode", 141 + List.map 142 + (fun r -> Alcotest.test_case r.name `Quick (test_decode r)) 143 + rows ); 144 + ( "encode", 145 + List.map 146 + (fun r -> Alcotest.test_case r.name `Quick (test_encode r)) 147 + rows ); 148 + ( "roundtrip", 149 + List.map 150 + (fun r -> Alcotest.test_case r.name `Quick (test_roundtrip r)) 151 + rows ); 152 + ]
+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