CCSDS Synchronization and Channel Coding (131.0-B, 231.0-B)
0
fork

Configure Feed

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

Thread ~sw through monopam to fix Switch finished! bug

Pass Eio switch from top-level Eio_main.run down to all
Git.Repository.open_repo calls instead of creating tiny
per-call Switch.run scopes that close prematurely.

+272
+1
test/interop/python/.gitignore
··· 1 + scripts/.venv/
+19
test/interop/python/dune
··· 1 + (test 2 + (name test) 3 + (libraries scc 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))))
+125
test/interop/python/scripts/generate.py
··· 1 + #!/usr/bin/env python3 2 + """Generate SCC interop traces for ocaml-scc. 3 + 4 + Oracle: Python (no deps needed for ASM pattern + randomizer LFSR + CLTU BCH) 5 + Install: no dependencies 6 + 7 + Tests: 8 + - ASM marker: 0x1ACFFC1D 9 + - Randomizer: CCSDS LFSR pseudo-random sequence (first 255 bytes) 10 + - CLTU BCH: BCH(63,56) parity for known inputs 11 + 12 + Traces are committed to git. Only re-run when changing inputs 13 + or upgrading the oracle. 14 + """ 15 + import os, sys, struct 16 + 17 + TRACE_DIR = os.path.join(os.path.dirname(__file__), "..", "traces") 18 + 19 + 20 + # --- ASM marker --- 21 + ASM_MARKER = bytes([0x1A, 0xCF, 0xFC, 0x1D]) 22 + 23 + 24 + # --- CCSDS Randomizer (LFSR) --- 25 + # Polynomial: h(x) = x^8 + x^7 + x^5 + x^3 + 1 26 + # Initial state: 0xFF 27 + # LFSR shifts right; output from bit 0; feedback enters at bit 7 28 + # Feedback is XOR of bits 0, 3, 5, 7 29 + # Output bits are packed MSB-first into bytes 30 + def ccsds_randomizer_sequence(n_bytes): 31 + state = 0xFF 32 + result = [] 33 + for _ in range(n_bytes): 34 + byte = 0 35 + for _ in range(8): 36 + out = state & 1 37 + xor_all = (state ^ (state >> 3) ^ (state >> 5) ^ (state >> 7)) & 1 38 + state = (state >> 1) | (xor_all << 7) 39 + byte = (byte << 1) | out 40 + result.append(byte) 41 + return bytes(result) 42 + 43 + 44 + # --- CLTU BCH(63,56) --- 45 + # Generator polynomial: g(x) = x^7 + x^6 + x^2 + 1 = 0x45 46 + # Processes 56 data bits (7 bytes) MSB-first through LFSR 47 + # Parity bits are complemented, placed in MSB 7 bits with filler bit (0) in LSB 48 + BCH_POLY = 0x45 49 + 50 + def bch_parity(data_7bytes): 51 + """Compute BCH(63,56) parity for 7 data bytes. 52 + Returns the parity byte (complemented, MSB-aligned with filler bit).""" 53 + sr = 0 54 + for byte_val in data_7bytes: 55 + for j in range(7, -1, -1): 56 + din = (byte_val >> j) & 1 57 + fb = ((sr >> 6) ^ din) & 1 58 + sr = ((sr << 1) ^ (BCH_POLY if fb else 0)) & 0x7F 59 + # Complement parity bits, shift to MSB 7 bits, filler bit (0) in LSB 60 + return ((~sr & 0x7F) << 1) & 0xFF 61 + 62 + 63 + def cltu_encode(frame): 64 + """Encode a TC frame as CLTU with BCH codeblocks.""" 65 + start_seq = b'\xEB\x90' 66 + tail_seq = b'\xC5\xC5\xC5\xC5\xC5\xC5\xC5\x79' 67 + fill_byte = 0x55 68 + codeblock_data = 7 69 + 70 + # Split frame into 7-byte chunks 71 + codeblocks = [] 72 + for i in range(0, len(frame), codeblock_data): 73 + chunk = frame[i:i + codeblock_data] 74 + # Pad with fill bytes if needed 75 + if len(chunk) < codeblock_data: 76 + chunk = chunk + bytes([fill_byte] * (codeblock_data - len(chunk))) 77 + parity = bch_parity(chunk) 78 + codeblocks.append(chunk + bytes([parity])) 79 + 80 + return start_seq + b''.join(codeblocks) + tail_seq 81 + 82 + 83 + # --- Generate traces --- 84 + os.makedirs(TRACE_DIR, exist_ok=True) 85 + with open(os.path.join(TRACE_DIR, "vectors.csv"), "w") as f: 86 + f.write("# SCC interop test vectors\n") 87 + f.write("# Oracle: Python (CCSDS spec reference implementation)\n") 88 + f.write("# Format: type,name,params...\n") 89 + f.write("#\n") 90 + f.write("# asm,name,marker_hex\n") 91 + f.write("# randomizer,name,length,sequence_hex\n") 92 + f.write("# bch,name,data_hex,parity_hex\n") 93 + f.write("# cltu,name,frame_hex,cltu_hex\n") 94 + 95 + # ASM marker 96 + f.write(f"asm,marker,{ASM_MARKER.hex()}\n") 97 + 98 + # Randomizer sequence 99 + for n in [20, 255]: 100 + seq = ccsds_randomizer_sequence(n) 101 + f.write(f"randomizer,pn_{n},{n},{seq.hex()}\n") 102 + 103 + # BCH parity for known inputs 104 + bch_inputs = [ 105 + ("zeros", bytes(7)), 106 + ("ones", bytes([0x01] * 7)), 107 + ("ascending", bytes(range(7))), 108 + ("all_ff", bytes([0xFF] * 7)), 109 + ("hello", b"Hello, " ), 110 + ] 111 + for name, data in bch_inputs: 112 + parity = bch_parity(data) 113 + f.write(f"bch,{name},{data.hex()},{parity:02x}\n") 114 + 115 + # CLTU encode for known frames 116 + cltu_frames = [ 117 + ("short_frame", bytes(range(7))), 118 + ("two_codeblocks", bytes(range(10))), 119 + ("hello_frame", b"Hello, CCSDS!"), 120 + ] 121 + for name, frame in cltu_frames: 122 + cltu = cltu_encode(frame) 123 + f.write(f"cltu,{name},{frame.hex()},{cltu.hex()}\n") 124 + 125 + print(f"Wrote {TRACE_DIR}/vectors.csv")
+108
test/interop/python/test.ml
··· 1 + (** Python interop tests for ocaml-scc. 2 + 3 + Traces generated by: Python (CCSDS spec reference implementation) 4 + Regenerate: REGEN_TRACES=1 dune build @regen-traces *) 5 + 6 + let trace path = Filename.concat "traces" path 7 + 8 + (* Parse CSV trace: skip comments (#), split on comma *) 9 + let parse_csv path = 10 + let ic = open_in (trace path) in 11 + let lines = ref [] in 12 + (try 13 + while true do 14 + let line = input_line ic in 15 + if String.length line > 0 && line.[0] <> '#' then 16 + lines := String.split_on_char ',' line :: !lines 17 + done 18 + with End_of_file -> ()); 19 + close_in ic; 20 + List.rev !lines 21 + 22 + let bytes_of_hex hex = 23 + let len = String.length hex / 2 in 24 + Bytes.init len (fun i -> 25 + Char.chr (int_of_string ("0x" ^ String.sub hex (i * 2) 2))) 26 + 27 + let hex_of_bytes b = 28 + let buf = Buffer.create (Bytes.length b * 2) in 29 + Bytes.iter 30 + (fun c -> Buffer.add_string buf (Printf.sprintf "%02x" (Char.code c))) 31 + b; 32 + Buffer.contents buf 33 + 34 + (* --- ASM tests --- *) 35 + 36 + let test_asm_marker expected_hex () = 37 + let expected = bytes_of_hex expected_hex in 38 + let marker = Scc.Sync.Asm.marker in 39 + if marker <> expected then 40 + Alcotest.failf "ASM marker mismatch\n expected: %s\n got: %s" 41 + expected_hex (hex_of_bytes marker) 42 + 43 + (* --- Randomizer tests --- *) 44 + 45 + let test_randomizer_sequence expected_len expected_hex () = 46 + let expected = bytes_of_hex expected_hex in 47 + let got = Scc.Coding.Randomizer.sequence expected_len in 48 + if got <> expected then 49 + Alcotest.failf 50 + "randomizer sequence (%d bytes) mismatch\n expected: %s\n got: %s" 51 + expected_len expected_hex (hex_of_bytes got) 52 + 53 + (* --- BCH parity tests --- *) 54 + 55 + let test_bch_parity data_hex expected_parity_hex () = 56 + let data = bytes_of_hex data_hex in 57 + let expected = int_of_string ("0x" ^ expected_parity_hex) in 58 + let got = Char.code (Scc.Sync.Cltu.bch_parity data 0) in 59 + if got <> expected then 60 + Alcotest.failf 61 + "BCH parity mismatch for %s\n expected: %02x\n got: %02x" data_hex 62 + expected got 63 + 64 + (* --- CLTU encode tests --- *) 65 + 66 + let test_cltu_encode frame_hex expected_hex () = 67 + let frame = bytes_of_hex frame_hex in 68 + let expected = bytes_of_hex expected_hex in 69 + let got = Scc.Sync.Cltu.encode frame in 70 + if got <> expected then 71 + Alcotest.failf "CLTU encode mismatch\n expected: %s\n got: %s" 72 + expected_hex (hex_of_bytes got) 73 + 74 + let () = 75 + let rows = parse_csv "vectors.csv" in 76 + let asm_tests = ref [] in 77 + let randomizer_tests = ref [] in 78 + let bch_tests = ref [] in 79 + let cltu_tests = ref [] in 80 + List.iter 81 + (fun row -> 82 + match row with 83 + | [ "asm"; name; marker_hex ] -> 84 + asm_tests := 85 + Alcotest.test_case name `Quick (test_asm_marker marker_hex) 86 + :: !asm_tests 87 + | [ "randomizer"; name; len_s; seq_hex ] -> 88 + randomizer_tests := 89 + Alcotest.test_case name `Quick 90 + (test_randomizer_sequence (int_of_string len_s) seq_hex) 91 + :: !randomizer_tests 92 + | [ "bch"; name; data_hex; parity_hex ] -> 93 + bch_tests := 94 + Alcotest.test_case name `Quick (test_bch_parity data_hex parity_hex) 95 + :: !bch_tests 96 + | [ "cltu"; name; frame_hex; cltu_hex ] -> 97 + cltu_tests := 98 + Alcotest.test_case name `Quick (test_cltu_encode frame_hex cltu_hex) 99 + :: !cltu_tests 100 + | _ -> Alcotest.failf "bad CSV row: %s" (String.concat "," row)) 101 + rows; 102 + Alcotest.run "scc-interop" 103 + [ 104 + ("asm", List.rev !asm_tests); 105 + ("randomizer", List.rev !randomizer_tests); 106 + ("bch", List.rev !bch_tests); 107 + ("cltu", List.rev !cltu_tests); 108 + ]
+19
test/interop/python/traces/vectors.csv
··· 1 + # SCC interop test vectors 2 + # Oracle: Python (CCSDS spec reference implementation) 3 + # Format: type,name,params... 4 + # 5 + # asm,name,marker_hex 6 + # randomizer,name,length,sequence_hex 7 + # bch,name,data_hex,parity_hex 8 + # cltu,name,frame_hex,cltu_hex 9 + asm,marker,1acffc1d 10 + randomizer,pn_20,20,ff480ec09a0d70bc8e2c93ada7b746ce5a977dcc 11 + randomizer,pn_255,255,ff480ec09a0d70bc8e2c93ada7b746ce5a977dcc32a2bf3e0a10f18894cdeab1fe901d81341ae1791c59275b4f6e8d9cb52efb9865457e7c1421e311299bd563fd203b026835c2f238b24eb69edd1b396a5df730ca8afcf82843c6225337aac7fa407604d06b85e471649d6d3dba3672d4bbee619515f9f050878c44a66f558ff480ec09a0d70bc8e2c93ada7b746ce5a977dcc32a2bf3e0a10f18894cdeab1fe901d81341ae1791c59275b4f6e8d9cb52efb9865457e7c1421e311299bd563fd203b026835c2f238b24eb69edd1b396a5df730ca8afcf82843c6225337aac7fa407604d06b85e471649d6d3dba3672d4bbee619515f9f050878c44a66f558 12 + bch,zeros,00000000000000,fe 13 + bch,ones,01010101010101,42 14 + bch,ascending,00010203040506,c6 15 + bch,all_ff,ffffffffffffff,86 16 + bch,hello,48656c6c6f2c20,8a 17 + cltu,short_frame,00010203040506,eb9000010203040506c6c5c5c5c5c5c5c579 18 + cltu,two_codeblocks,00010203040506070809,eb9000010203040506c607080955555555f2c5c5c5c5c5c5c579 19 + cltu,hello_frame,48656c6c6f2c20434353445321,eb9048656c6c6f2c208a43435344532155b6c5c5c5c5c5c5c579