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: standalone Proof module with PCC-inspired documentation

+327
+19
test/interop/python/dune
··· 1 + (test 2 + (name test) 3 + (libraries proximity1 alcotest) 4 + (deps 5 + (source_tree traces) 6 + (source_tree scripts))) 7 + 8 + ; Regenerate traces: REGEN_TRACES=1 dune build @regen-traces 9 + 10 + (rule 11 + (alias regen-traces) 12 + (enabled_if 13 + (= %{env:REGEN_TRACES=0} 1)) 14 + (deps 15 + (source_tree scripts)) 16 + (action 17 + (chdir 18 + scripts 19 + (run ./generate.py))))
+138
test/interop/python/scripts/generate.py
··· 1 + #!/usr/bin/env python3 2 + """Generate Proximity-1 interop traces for ocaml-proximity1. 3 + 4 + Oracle: Python (no deps needed for frame header encoding) 5 + Install: no dependencies 6 + 7 + Tests Proximity-1 Transfer Frame encoding/decoding per CCSDS 211.0-B-5. 8 + 9 + Frame Layout (7-byte header + variable data): 10 + Bytes 0-1 (U16 big-endian): 11 + Bits 15-13: Version Number (3 bits) 12 + Bits 12-5: Spacecraft ID (8 bits) 13 + Bits 4-2: Frame Type (3 bits, 0=Data 1=Ack 2=Expedited) 14 + Bits 1-0: Reserved (2 bits, always 0) 15 + Byte 2: Sequence Count high (top 8 of 24 bits) 16 + Bytes 3-4: Sequence Count low (bottom 16 of 24 bits, U16 big-endian) 17 + Bytes 5-6: Frame Length (total frame size in bytes, U16 big-endian) 18 + Bytes 7+: Data (frame_length - 7 bytes) 19 + 20 + Traces are committed to git. Only re-run when changing inputs 21 + or upgrading the oracle. 22 + """ 23 + import os, struct 24 + 25 + TRACE_DIR = os.path.join(os.path.dirname(__file__), "..", "traces") 26 + HEADER_SIZE = 7 27 + 28 + 29 + def encode_frame(version, scid, frame_type, sequence_count, data): 30 + """Encode a Proximity-1 frame to bytes per CCSDS 211.0-B-5.""" 31 + # First 16-bit word: Version(3) | SCID(8) | FrameType(3) | Reserved(2) 32 + word0 = 0 33 + word0 |= (version & 0x7) << 13 34 + word0 |= (scid & 0xFF) << 5 35 + word0 |= (frame_type & 0x7) << 2 36 + # reserved bits are 0 37 + 38 + seq_hi = (sequence_count >> 16) & 0xFF 39 + seq_lo = sequence_count & 0xFFFF 40 + frame_length = HEADER_SIZE + len(data) 41 + 42 + header = struct.pack(">HBH H", word0, seq_hi, seq_lo, frame_length) 43 + assert len(header) == HEADER_SIZE, f"header is {len(header)} bytes, expected {HEADER_SIZE}" 44 + return header + data 45 + 46 + 47 + # Frame type constants 48 + DATA = 0 49 + ACK = 1 50 + EXPEDITED = 2 51 + 52 + FRAME_TYPE_NAMES = {DATA: "Data", ACK: "Ack", EXPEDITED: "Expedited"} 53 + 54 + # Test vectors: various Proximity-1 frame configurations 55 + vectors = [ 56 + { 57 + "name": "data_hello", 58 + "version": 0, "scid": 42, "frame_type": DATA, 59 + "sequence_count": 1000, 60 + "data": b"hello", 61 + }, 62 + { 63 + "name": "ack_minimal", 64 + "version": 0, "scid": 100, "frame_type": ACK, 65 + "sequence_count": 0, 66 + "data": b"\x01", 67 + }, 68 + { 69 + "name": "expedited_max_seq", 70 + "version": 0, "scid": 255, "frame_type": EXPEDITED, 71 + "sequence_count": 0xFFFFFF, 72 + "data": b"\xff" * 100, 73 + }, 74 + { 75 + "name": "data_all_zeros", 76 + "version": 0, "scid": 0, "frame_type": DATA, 77 + "sequence_count": 0, 78 + "data": b"", 79 + }, 80 + { 81 + "name": "data_large_scid", 82 + "version": 0, "scid": 200, "frame_type": DATA, 83 + "sequence_count": 50000, 84 + "data": b"\xde\xad\xbe\xef", 85 + }, 86 + { 87 + "name": "ack_mid_seq", 88 + "version": 0, "scid": 77, "frame_type": ACK, 89 + "sequence_count": 123456, 90 + "data": b"\xaa\xbb", 91 + }, 92 + { 93 + "name": "data_seq_boundary", 94 + "version": 0, "scid": 1, "frame_type": DATA, 95 + "sequence_count": 0x010000, 96 + "data": b"boundary", 97 + }, 98 + { 99 + "name": "expedited_scid_128", 100 + "version": 0, "scid": 128, "frame_type": EXPEDITED, 101 + "sequence_count": 999999, 102 + "data": b"expedited payload", 103 + }, 104 + { 105 + "name": "data_binary_payload", 106 + "version": 0, "scid": 50, "frame_type": DATA, 107 + "sequence_count": 7, 108 + "data": bytes(range(256)), 109 + }, 110 + { 111 + "name": "version_nonzero", 112 + "version": 3, "scid": 10, "frame_type": DATA, 113 + "sequence_count": 42, 114 + "data": b"v3", 115 + }, 116 + ] 117 + 118 + os.makedirs(TRACE_DIR, exist_ok=True) 119 + with open(os.path.join(TRACE_DIR, "vectors.csv"), "w") as f: 120 + f.write("# Proximity-1 interop test vectors\n") 121 + f.write("# Oracle: Python (CCSDS 211.0-B-5 reference implementation)\n") 122 + f.write("# Format: name,version,scid,frame_type,sequence_count,data_hex,encoded_hex\n") 123 + for v in vectors: 124 + encoded = encode_frame( 125 + v["version"], v["scid"], v["frame_type"], 126 + v["sequence_count"], v["data"] 127 + ) 128 + f.write( 129 + f"{v['name']}," 130 + f"{v['version']}," 131 + f"{v['scid']}," 132 + f"{v['frame_type']}," 133 + f"{v['sequence_count']}," 134 + f"{v['data'].hex()}," 135 + f"{encoded.hex()}\n" 136 + ) 137 + 138 + print(f"Wrote {TRACE_DIR}/vectors.csv ({len(vectors)} vectors)")
+9
test/interop/python/scripts/generate.sh
··· 1 + #!/bin/bash 2 + set -euo pipefail 3 + SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" 4 + TRACE_DIR="$SCRIPT_DIR/../traces" 5 + mkdir -p "$TRACE_DIR" 6 + TRACE_DIR="$(cd "$TRACE_DIR" && pwd)" 7 + 8 + cd "$SCRIPT_DIR" 9 + python3 generate.py "$TRACE_DIR"
+148
test/interop/python/test.ml
··· 1 + (** Python interop tests for ocaml-proximity1. 2 + 3 + Tests Proximity-1 Transfer Frame encoding/decoding against a Python 4 + reference implementation of CCSDS 211.0-B-5. 5 + 6 + Traces generated by: Python (CCSDS 211.0-B-5 reference implementation) 7 + Regenerate: REGEN_TRACES=1 dune build @regen-traces *) 8 + 9 + let trace path = Filename.concat "traces" path 10 + 11 + (* Parse CSV trace: skip comments (#), split on comma *) 12 + let parse_csv path = 13 + let ic = open_in (trace path) in 14 + let lines = ref [] in 15 + (try 16 + while true do 17 + let line = input_line ic in 18 + if String.length line > 0 && line.[0] <> '#' then 19 + lines := String.split_on_char ',' line :: !lines 20 + done 21 + with End_of_file -> ()); 22 + close_in ic; 23 + List.rev !lines 24 + 25 + let bytes_of_hex hex = 26 + let len = String.length hex / 2 in 27 + String.init len (fun i -> 28 + Char.chr (int_of_string ("0x" ^ String.sub hex (i * 2) 2))) 29 + 30 + let hex_of_string s = 31 + let buf = Buffer.create (String.length s * 2) in 32 + String.iter 33 + (fun c -> Buffer.add_string buf (Printf.sprintf "%02x" (Char.code c))) 34 + s; 35 + Buffer.contents buf 36 + 37 + type vector = { 38 + name : string; 39 + version : int; 40 + scid : int; 41 + frame_type : int; 42 + sequence_count : int; 43 + data_hex : string; 44 + encoded : string; 45 + } 46 + 47 + let parse_vectors () = 48 + let rows = parse_csv "vectors.csv" in 49 + List.filter_map 50 + (fun row -> 51 + match row with 52 + | [ 53 + name; version; scid; frame_type; sequence_count; data_hex; encoded_hex; 54 + ] -> 55 + Some 56 + { 57 + name; 58 + version = int_of_string version; 59 + scid = int_of_string scid; 60 + frame_type = int_of_string frame_type; 61 + sequence_count = int_of_string sequence_count; 62 + data_hex; 63 + encoded = bytes_of_hex encoded_hex; 64 + } 65 + | _ -> Alcotest.failf "bad CSV row: %s" (String.concat "," row)) 66 + rows 67 + 68 + let frame_type_of_int n = 69 + match Proximity1.frame_type_of_int n with 70 + | Some ft -> ft 71 + | None -> Alcotest.failf "invalid frame type: %d" n 72 + 73 + let test_encode vec () = 74 + let ft = frame_type_of_int vec.frame_type in 75 + let data = bytes_of_hex vec.data_hex in 76 + let frame = 77 + Proximity1. 78 + { 79 + version = vec.version; 80 + scid = vec.scid; 81 + frame_type = ft; 82 + sequence_count = vec.sequence_count; 83 + data; 84 + } 85 + in 86 + let got = Proximity1.encode frame in 87 + if got <> vec.encoded then 88 + Alcotest.failf "%s: encode mismatch\n expected: %s\n got: %s" 89 + vec.name 90 + (hex_of_string vec.encoded) 91 + (hex_of_string got) 92 + 93 + let test_decode vec () = 94 + match Proximity1.decode vec.encoded with 95 + | Error `Truncated -> Alcotest.failf "%s: decode returned Truncated" vec.name 96 + | Error (`Invalid_frame_type n) -> 97 + Alcotest.failf "%s: decode returned Invalid_frame_type %d" vec.name n 98 + | Ok frame -> 99 + Alcotest.(check int) (vec.name ^ ": version") vec.version frame.version; 100 + Alcotest.(check int) (vec.name ^ ": scid") vec.scid frame.scid; 101 + Alcotest.(check int) 102 + (vec.name ^ ": frame_type") 103 + vec.frame_type 104 + (Proximity1.int_of_frame_type frame.frame_type); 105 + Alcotest.(check int) 106 + (vec.name ^ ": sequence_count") 107 + vec.sequence_count frame.sequence_count; 108 + let expected_data = bytes_of_hex vec.data_hex in 109 + Alcotest.(check string) (vec.name ^ ": data") expected_data frame.data 110 + 111 + let test_roundtrip vec () = 112 + let ft = frame_type_of_int vec.frame_type in 113 + let data = bytes_of_hex vec.data_hex in 114 + let frame = 115 + Proximity1. 116 + { 117 + version = vec.version; 118 + scid = vec.scid; 119 + frame_type = ft; 120 + sequence_count = vec.sequence_count; 121 + data; 122 + } 123 + in 124 + let encoded = Proximity1.encode frame in 125 + match Proximity1.decode encoded with 126 + | Error _ -> Alcotest.failf "%s: roundtrip decode failed" vec.name 127 + | Ok decoded -> 128 + if not (Proximity1.equal frame decoded) then 129 + Alcotest.failf "%s: roundtrip mismatch\n original: %a\n decoded: %a" 130 + vec.name Proximity1.pp frame Proximity1.pp decoded 131 + 132 + let () = 133 + let vectors = parse_vectors () in 134 + Alcotest.run "proximity1-interop" 135 + [ 136 + ( "encode", 137 + List.map 138 + (fun v -> Alcotest.test_case v.name `Quick (test_encode v)) 139 + vectors ); 140 + ( "decode", 141 + List.map 142 + (fun v -> Alcotest.test_case v.name `Quick (test_decode v)) 143 + vectors ); 144 + ( "roundtrip", 145 + List.map 146 + (fun v -> Alcotest.test_case v.name `Quick (test_roundtrip v)) 147 + vectors ); 148 + ]
+13
test/interop/python/traces/vectors.csv
··· 1 + # Proximity-1 interop test vectors 2 + # Oracle: Python (CCSDS 211.0-B-5 reference implementation) 3 + # Format: name,version,scid,frame_type,sequence_count,data_hex,encoded_hex 4 + data_hello,0,42,0,1000,68656c6c6f,05400003e8000c68656c6c6f 5 + ack_minimal,0,100,1,0,01,0c84000000000801 6 + expedited_max_seq,0,255,2,16777215,ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff,1fe8ffffff006bffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 7 + data_all_zeros,0,0,0,0,,00000000000007 8 + data_large_scid,0,200,0,50000,deadbeef,190000c350000bdeadbeef 9 + ack_mid_seq,0,77,1,123456,aabb,09a401e2400009aabb 10 + data_seq_boundary,0,1,0,65536,626f756e64617279,0020010000000f626f756e64617279 11 + expedited_scid_128,0,128,2,999999,657870656469746564207061796c6f6164,10080f423f0018657870656469746564207061796c6f6164 12 + data_binary_payload,0,50,0,7,000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff,06400000070107000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff 13 + version_nonzero,3,10,0,42,7633,614000002a00097633