SpaceWire (ECSS-E-ST-50-12C) and RMAP (ECSS-E-ST-50-52C)
0
fork

Configure Feed

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

irmin: value-oriented Heap + Schema + Cursor with 4 backends

+676
+11
test/interop/python/dune
··· 1 + (rule 2 + (deps 3 + (source_tree scripts)) 4 + (targets vectors_packets.csv vectors_instructions.csv vectors_crc.csv) 5 + (action 6 + (run python3 scripts/generate.py vectors))) 7 + 8 + (test 9 + (name test) 10 + (libraries spacewire csvt alcotest) 11 + (deps vectors_packets.csv vectors_instructions.csv vectors_crc.csv))
+301
test/interop/python/scripts/generate.py
··· 1 + #!/usr/bin/env python3 2 + """Generate SpaceWire / RMAP interop traces for ocaml-spacewire. 3 + 4 + Oracle: Python (no deps needed for bitfield packing from the specs) 5 + Install: no dependencies 6 + 7 + Tests SpaceWire packet encoding (ECSS-E-ST-50-12C), RMAP instruction byte 8 + encoding and RMAP CRC-8 (ECSS-E-ST-50-52C). 9 + 10 + SpaceWire Packet wire format: 11 + [addr_count : 1 byte] 12 + [addr_bytes : addr_count bytes] 13 + [eop_marker : 1 byte] (0x00 = EOP, 0x01 = EEP) 14 + [cargo : remaining bytes] 15 + 16 + RMAP Instruction byte (1 byte): 17 + Bit 7: reserved (0) 18 + Bit 6: command (1) / reply (0) 19 + Bit 5: write (1) / read (0) 20 + Bit 4: verify (1) / no verify (0) 21 + Bit 3: ack (1) / no ack (0) 22 + Bit 2: increment (1) / no increment (0) 23 + Bits 1-0: reply address length (0-3, in 4-byte units) 24 + 25 + RMAP CRC-8: Polynomial x^8 + x^2 + x + 1 (0x07). 26 + Per ECSS-E-ST-50-52C Annex A. 27 + 28 + Traces are committed to git. Only re-run when changing inputs 29 + or upgrading the oracle. 30 + """ 31 + import csv 32 + import os 33 + import sys 34 + 35 + 36 + # --------------------------------------------------------------------------- 37 + # SpaceWire packet encoding 38 + # --------------------------------------------------------------------------- 39 + 40 + def encode_packet(address, cargo, eop): 41 + """Encode a SpaceWire packet to bytes. 42 + 43 + address: list of int (0-255) 44 + cargo: bytes 45 + eop: "EOP" or "EEP" 46 + """ 47 + addr_count = len(address) 48 + eop_byte = 0x00 if eop == "EOP" else 0x01 49 + buf = bytes([addr_count]) + bytes(address) + bytes([eop_byte]) + cargo 50 + return buf 51 + 52 + 53 + # --------------------------------------------------------------------------- 54 + # RMAP CRC-8 55 + # --------------------------------------------------------------------------- 56 + 57 + def rmap_crc8_table(): 58 + """Build the CRC-8 lookup table for polynomial 0x07.""" 59 + table = [] 60 + for i in range(256): 61 + crc = i 62 + for _ in range(8): 63 + if crc & 0x80: 64 + crc = ((crc << 1) ^ 0x07) & 0xFF 65 + else: 66 + crc = (crc << 1) & 0xFF 67 + table.append(crc) 68 + return table 69 + 70 + CRC_TABLE = rmap_crc8_table() 71 + 72 + 73 + def rmap_crc8(data): 74 + """Compute RMAP CRC-8 over bytes.""" 75 + crc = 0 76 + for b in data: 77 + crc = CRC_TABLE[crc ^ b] 78 + return crc 79 + 80 + 81 + # --------------------------------------------------------------------------- 82 + # RMAP instruction byte 83 + # --------------------------------------------------------------------------- 84 + 85 + def encode_instruction(is_command, is_write, is_verify, is_ack, is_increment, 86 + reply_addr_len): 87 + """Encode the RMAP instruction byte. 88 + 89 + reply_addr_len: 0-3 (in 4-byte units) 90 + """ 91 + byte = 0 92 + # bit 7: reserved (0) 93 + if is_command: 94 + byte |= (1 << 6) 95 + if is_write: 96 + byte |= (1 << 5) 97 + if is_verify: 98 + byte |= (1 << 4) 99 + if is_ack: 100 + byte |= (1 << 3) 101 + if is_increment: 102 + byte |= (1 << 2) 103 + byte |= (reply_addr_len & 0x03) 104 + return byte 105 + 106 + 107 + # --------------------------------------------------------------------------- 108 + # Test vectors 109 + # --------------------------------------------------------------------------- 110 + 111 + # 1. Packet encoding vectors 112 + packet_vectors = [ 113 + { 114 + "name": "no_addr_eop", 115 + "address": [], 116 + "cargo": b"\xDE\xAD", 117 + "eop": "EOP", 118 + }, 119 + { 120 + "name": "no_addr_eep", 121 + "address": [], 122 + "cargo": b"\xBE\xEF", 123 + "eop": "EEP", 124 + }, 125 + { 126 + "name": "single_addr", 127 + "address": [42], 128 + "cargo": b"\x01\x02\x03", 129 + "eop": "EOP", 130 + }, 131 + { 132 + "name": "three_addr_eop", 133 + "address": [5, 17, 200], 134 + "cargo": b"\xCA\xFE\xBA\xBE", 135 + "eop": "EOP", 136 + }, 137 + { 138 + "name": "three_addr_eep", 139 + "address": [0, 128, 255], 140 + "cargo": b"\x42", 141 + "eop": "EEP", 142 + }, 143 + { 144 + "name": "single_byte_cargo", 145 + "address": [1], 146 + "cargo": b"\xFF", 147 + "eop": "EOP", 148 + }, 149 + { 150 + "name": "binary_cargo_256", 151 + "address": [10], 152 + "cargo": bytes(range(256)), 153 + "eop": "EOP", 154 + }, 155 + { 156 + "name": "max_addr_byte", 157 + "address": [255], 158 + "cargo": b"\xAA\xBB", 159 + "eop": "EEP", 160 + }, 161 + ] 162 + 163 + # 2. RMAP instruction byte vectors 164 + instruction_vectors = [ 165 + { 166 + "name": "read_no_ack_no_inc", 167 + "is_command": True, "is_write": False, "is_verify": False, 168 + "is_ack": False, "is_increment": False, "reply_addr_len": 0, 169 + }, 170 + { 171 + "name": "read_ack_inc", 172 + "is_command": True, "is_write": False, "is_verify": False, 173 + "is_ack": True, "is_increment": True, "reply_addr_len": 0, 174 + }, 175 + { 176 + "name": "write_ack_inc", 177 + "is_command": True, "is_write": True, "is_verify": False, 178 + "is_ack": True, "is_increment": True, "reply_addr_len": 0, 179 + }, 180 + { 181 + "name": "write_verify_ack_inc", 182 + "is_command": True, "is_write": True, "is_verify": True, 183 + "is_ack": True, "is_increment": True, "reply_addr_len": 0, 184 + }, 185 + { 186 + "name": "rmw_ack_inc", 187 + "is_command": True, "is_write": False, "is_verify": True, 188 + "is_ack": True, "is_increment": True, "reply_addr_len": 0, 189 + }, 190 + { 191 + "name": "read_reply", 192 + "is_command": False, "is_write": False, "is_verify": False, 193 + "is_ack": True, "is_increment": True, "reply_addr_len": 0, 194 + }, 195 + { 196 + "name": "write_reply", 197 + "is_command": False, "is_write": True, "is_verify": False, 198 + "is_ack": True, "is_increment": True, "reply_addr_len": 0, 199 + }, 200 + { 201 + "name": "cmd_reply_addr_1", 202 + "is_command": True, "is_write": False, "is_verify": False, 203 + "is_ack": True, "is_increment": True, "reply_addr_len": 1, 204 + }, 205 + { 206 + "name": "cmd_reply_addr_3", 207 + "is_command": True, "is_write": True, "is_verify": True, 208 + "is_ack": True, "is_increment": True, "reply_addr_len": 3, 209 + }, 210 + { 211 + "name": "all_zero", 212 + "is_command": False, "is_write": False, "is_verify": False, 213 + "is_ack": False, "is_increment": False, "reply_addr_len": 0, 214 + }, 215 + { 216 + "name": "cmd_write_no_ack_no_inc", 217 + "is_command": True, "is_write": True, "is_verify": False, 218 + "is_ack": False, "is_increment": False, "reply_addr_len": 2, 219 + }, 220 + ] 221 + 222 + # 3. RMAP CRC-8 vectors 223 + # Known test vectors from ECSS-E-ST-50-52C Annex A Table A-1 224 + crc_vectors = [ 225 + {"name": "empty", "data": b""}, 226 + {"name": "single_zero", "data": b"\x00"}, 227 + {"name": "single_ff", "data": b"\xFF"}, 228 + {"name": "single_01", "data": b"\x01"}, 229 + {"name": "two_bytes", "data": b"\x01\x02"}, 230 + {"name": "three_bytes", "data": b"\x01\x02\x03"}, 231 + {"name": "ascending_8", "data": bytes(range(8))}, 232 + {"name": "ascending_16", "data": bytes(range(16))}, 233 + {"name": "all_ff_4", "data": b"\xFF\xFF\xFF\xFF"}, 234 + {"name": "all_ff_8", "data": b"\xFF" * 8}, 235 + {"name": "alternating", "data": b"\xAA\x55\xAA\x55"}, 236 + {"name": "rmap_header_like", "data": b"\xFE\x01\x6C\x00\x67\x00\x00"}, 237 + {"name": "long_payload", "data": bytes(range(256))}, 238 + ] 239 + 240 + 241 + # --------------------------------------------------------------------------- 242 + # Trace writing 243 + # --------------------------------------------------------------------------- 244 + 245 + def write_traces(base_path): 246 + os.makedirs(os.path.dirname(base_path) or ".", exist_ok=True) 247 + 248 + # Packet vectors 249 + pkt_path = base_path + "_packets.csv" 250 + with open(pkt_path, "w", newline="") as fh: 251 + w = csv.writer(fh) 252 + w.writerow(["name", "address", "cargo_hex", "eop", "encoded_hex"]) 253 + for v in packet_vectors: 254 + addr_str = ";".join(str(a) for a in v["address"]) 255 + cargo_hex = v["cargo"].hex() 256 + encoded = encode_packet(v["address"], v["cargo"], v["eop"]) 257 + w.writerow([ 258 + v["name"], addr_str, cargo_hex, v["eop"], encoded.hex() 259 + ]) 260 + print(f"Generated {len(packet_vectors)} packet traces -> {pkt_path}") 261 + 262 + # Instruction byte vectors 263 + instr_path = base_path + "_instructions.csv" 264 + with open(instr_path, "w", newline="") as fh: 265 + w = csv.writer(fh) 266 + w.writerow(["name", "is_command", "is_write", "is_verify", 267 + "is_ack", "is_increment", "reply_addr_len", "byte_hex"]) 268 + for v in instruction_vectors: 269 + byte = encode_instruction( 270 + v["is_command"], v["is_write"], v["is_verify"], 271 + v["is_ack"], v["is_increment"], v["reply_addr_len"] 272 + ) 273 + w.writerow([ 274 + v["name"], 275 + 1 if v["is_command"] else 0, 276 + 1 if v["is_write"] else 0, 277 + 1 if v["is_verify"] else 0, 278 + 1 if v["is_ack"] else 0, 279 + 1 if v["is_increment"] else 0, 280 + v["reply_addr_len"], 281 + "%02x" % byte, 282 + ]) 283 + print(f"Generated {len(instruction_vectors)} instruction traces -> {instr_path}") 284 + 285 + # CRC-8 vectors 286 + crc_path = base_path + "_crc.csv" 287 + with open(crc_path, "w", newline="") as fh: 288 + w = csv.writer(fh) 289 + w.writerow(["name", "data_hex", "crc_hex"]) 290 + for v in crc_vectors: 291 + crc = rmap_crc8(v["data"]) 292 + w.writerow([v["name"], v["data"].hex(), "%02x" % crc]) 293 + print(f"Generated {len(crc_vectors)} CRC traces -> {crc_path}") 294 + 295 + 296 + if __name__ == "__main__": 297 + if len(sys.argv) > 1: 298 + base = sys.argv[1] 299 + else: 300 + base = os.path.join(os.path.dirname(__file__), "..", "vectors") 301 + write_traces(base)
+364
test/interop/python/test.ml
··· 1 + (** Python interop tests for ocaml-spacewire. 2 + 3 + Tests SpaceWire packet encoding and RMAP instruction byte / CRC-8 against a 4 + Python reference implementation of ECSS-E-ST-50-12C and ECSS-E-ST-50-52C. 5 + 6 + Traces generated by: Python (ECSS-E-ST-50-12C / ECSS-E-ST-50-52C reference) 7 + Regenerate: dune build ocaml-spacewire/test/interop/python (runs 8 + automatically) *) 9 + 10 + (* {1 Helpers} *) 11 + 12 + let hex_to_string hex = 13 + let len = String.length hex / 2 in 14 + String.init len (fun i -> 15 + let digit c = 16 + if c >= '0' && c <= '9' then Char.code c - Char.code '0' 17 + else if c >= 'a' && c <= 'f' then Char.code c - Char.code 'a' + 10 18 + else Char.code c - Char.code 'A' + 10 19 + in 20 + Char.chr ((digit hex.[i * 2] lsl 4) lor digit hex.[(i * 2) + 1])) 21 + 22 + let string_to_hex s = 23 + let buf = Buffer.create (String.length s * 2) in 24 + String.iter 25 + (fun c -> Buffer.add_string buf (Printf.sprintf "%02x" (Char.code c))) 26 + s; 27 + Buffer.contents buf 28 + 29 + let parse_address s = 30 + if String.length s = 0 then [] 31 + else String.split_on_char ';' s |> List.map int_of_string 32 + 33 + (* {1 Packet Vectors} *) 34 + 35 + type packet_vector = { 36 + name : string; 37 + address : string; 38 + cargo_hex : string; 39 + eop : string; 40 + encoded_hex : string; 41 + } 42 + 43 + let packet_codec = 44 + Csvt.( 45 + Row.( 46 + obj (fun name address cargo_hex eop encoded_hex -> 47 + { name; address; cargo_hex; eop; encoded_hex }) 48 + |> col "name" string ~enc:(fun r -> r.name) 49 + |> col "address" string ~enc:(fun r -> r.address) 50 + |> col "cargo_hex" string ~enc:(fun r -> r.cargo_hex) 51 + |> col "eop" string ~enc:(fun r -> r.eop) 52 + |> col "encoded_hex" string ~enc:(fun r -> r.encoded_hex) 53 + |> finish)) 54 + 55 + let load_packet_vectors () = 56 + match Csvt.decode_file packet_codec "vectors_packets.csv" with 57 + | Ok rows -> rows 58 + | Error e -> Alcotest.failf "CSV decode: %a" Csvt.pp_error e 59 + 60 + let eop_of_string = function 61 + | "EOP" -> Spacewire.EOP 62 + | "EEP" -> Spacewire.EEP 63 + | s -> Alcotest.failf "bad eop: %s" s 64 + 65 + let test_packet_encode vectors () = 66 + List.iter 67 + (fun (v : packet_vector) -> 68 + let address = parse_address v.address in 69 + let cargo = hex_to_string v.cargo_hex in 70 + let eop = eop_of_string v.eop in 71 + let p = Spacewire.packet_exn ~address ~cargo eop in 72 + let encoded = Spacewire.encode_packet p in 73 + let got_hex = string_to_hex encoded in 74 + Alcotest.(check string) (v.name ^ ": encode") v.encoded_hex got_hex) 75 + vectors 76 + 77 + let test_packet_decode vectors () = 78 + List.iter 79 + (fun (v : packet_vector) -> 80 + let buf = hex_to_string v.encoded_hex in 81 + match Spacewire.decode_packet buf with 82 + | Error `Truncated -> 83 + Alcotest.failf "%s: decode returned Truncated" v.name 84 + | Error `Cargo_empty -> 85 + Alcotest.failf "%s: decode returned Cargo_empty" v.name 86 + | Ok p -> 87 + let expected_addr = parse_address v.address in 88 + Alcotest.(check (list int)) 89 + (v.name ^ ": address") expected_addr (Spacewire.address p); 90 + let expected_cargo = hex_to_string v.cargo_hex in 91 + Alcotest.(check string) 92 + (v.name ^ ": cargo") expected_cargo (Spacewire.cargo p); 93 + let expected_eop = eop_of_string v.eop in 94 + Alcotest.(check bool) 95 + (v.name ^ ": eop") true 96 + (Spacewire.eop p = expected_eop)) 97 + vectors 98 + 99 + let test_packet_roundtrip vectors () = 100 + List.iter 101 + (fun (v : packet_vector) -> 102 + let address = parse_address v.address in 103 + let cargo = hex_to_string v.cargo_hex in 104 + let eop = eop_of_string v.eop in 105 + let p = Spacewire.packet_exn ~address ~cargo eop in 106 + let encoded = Spacewire.encode_packet p in 107 + match Spacewire.decode_packet encoded with 108 + | Error _ -> Alcotest.failf "%s: roundtrip decode failed" v.name 109 + | Ok decoded -> 110 + Alcotest.(check bool) 111 + (v.name ^ ": roundtrip") true 112 + (Spacewire.equal_packet p decoded)) 113 + vectors 114 + 115 + (* {1 RMAP Instruction Vectors} *) 116 + 117 + type instruction_vector = { 118 + instr_name : string; 119 + is_command : bool; 120 + is_write : bool; 121 + is_verify : bool; 122 + is_ack : bool; 123 + is_increment : bool; 124 + reply_addr_len : int; 125 + byte_hex : string; 126 + } 127 + 128 + let instruction_codec = 129 + Csvt.( 130 + Row.( 131 + obj 132 + (fun 133 + instr_name 134 + is_command 135 + is_write 136 + is_verify 137 + is_ack 138 + is_increment 139 + reply_addr_len 140 + byte_hex 141 + -> 142 + { 143 + instr_name; 144 + is_command; 145 + is_write; 146 + is_verify; 147 + is_ack; 148 + is_increment; 149 + reply_addr_len; 150 + byte_hex; 151 + }) 152 + |> col "name" string ~enc:(fun r -> r.instr_name) 153 + |> col "is_command" bool ~enc:(fun r -> r.is_command) 154 + |> col "is_write" bool ~enc:(fun r -> r.is_write) 155 + |> col "is_verify" bool ~enc:(fun r -> r.is_verify) 156 + |> col "is_ack" bool ~enc:(fun r -> r.is_ack) 157 + |> col "is_increment" bool ~enc:(fun r -> r.is_increment) 158 + |> col "reply_addr_len" int ~enc:(fun r -> r.reply_addr_len) 159 + |> col "byte_hex" string ~enc:(fun r -> r.byte_hex) 160 + |> finish)) 161 + 162 + let load_instruction_vectors () = 163 + match Csvt.decode_file instruction_codec "vectors_instructions.csv" with 164 + | Ok rows -> rows 165 + | Error e -> Alcotest.failf "CSV decode: %a" Csvt.pp_error e 166 + 167 + (* Build an rmap_command with the given flags, just for instruction encoding. 168 + We use dummy values for fields that don't affect the instruction byte. *) 169 + let make_test_command ~is_write ~is_verify ~is_ack ~is_increment ~reply_addr_len 170 + = 171 + let command : Spacewire.rmap_command_code = 172 + match (is_write, is_verify) with 173 + | false, false -> Read 174 + | true, false -> Write 175 + | true, true -> Write_verify 176 + | false, true -> Read_modify_write 177 + in 178 + let ack : Spacewire.rmap_ack = if is_ack then Ack else No_ack in 179 + let increment : Spacewire.rmap_increment = 180 + if is_increment then Increment else No_increment 181 + in 182 + (* reply_addr_len is in 4-byte units; create that many dummy bytes *) 183 + let reply_address = List.init (reply_addr_len * 4) (fun _ -> 1) in 184 + Spacewire.rmap_command_exn ~command ~ack ~increment ~key:0 ~reply_address 185 + ~initiator_logical_address:0 ~transaction_id:0 ~address:0L ~data_length:0 () 186 + 187 + let test_instruction_encode_via_command vectors () = 188 + (* For command instructions (is_command=true), we can test via 189 + encode_rmap_command and check byte 2 of the output. *) 190 + List.iter 191 + (fun (v : instruction_vector) -> 192 + if v.is_command then begin 193 + let cmd = 194 + make_test_command ~is_write:v.is_write ~is_verify:v.is_verify 195 + ~is_ack:v.is_ack ~is_increment:v.is_increment 196 + ~reply_addr_len:v.reply_addr_len 197 + in 198 + let encoded = 199 + Spacewire.encode_rmap_command ~target_logical_address:0 cmd 200 + in 201 + (* Byte 2 is the instruction byte *) 202 + let got = Char.code encoded.[2] in 203 + let expected = int_of_string ("0x" ^ v.byte_hex) in 204 + Alcotest.(check int) (v.instr_name ^ ": instruction byte") expected got 205 + end) 206 + vectors 207 + 208 + let test_instruction_encode_via_reply vectors () = 209 + (* For reply instructions (is_command=false), we test via 210 + encode_rmap_reply and check byte 2 of the output. 211 + Note: encode_rmap_reply hardcodes ack=true, increment=true, 212 + reply_addr_len=0, so we only check vectors matching those. *) 213 + List.iter 214 + (fun (v : instruction_vector) -> 215 + if 216 + (not v.is_command) && v.is_ack && v.is_increment && v.reply_addr_len = 0 217 + then begin 218 + let command : Spacewire.rmap_command_code = 219 + match (v.is_write, v.is_verify) with 220 + | false, false -> Read 221 + | true, false -> Write 222 + | true, true -> Write_verify 223 + | false, true -> Read_modify_write 224 + in 225 + let rpl = 226 + Spacewire.rmap_reply ~command ~status:Success 227 + ~initiator_logical_address:0 ~transaction_id:0 () 228 + in 229 + let encoded = 230 + Spacewire.encode_rmap_reply ~target_logical_address:0 rpl 231 + in 232 + let got = Char.code encoded.[2] in 233 + let expected = int_of_string ("0x" ^ v.byte_hex) in 234 + Alcotest.(check int) 235 + (v.instr_name ^ ": reply instruction byte") 236 + expected got 237 + end) 238 + vectors 239 + 240 + let test_instruction_decode_via_command vectors () = 241 + (* For command instructions, encode then decode and verify fields match. *) 242 + List.iter 243 + (fun (v : instruction_vector) -> 244 + if v.is_command then begin 245 + let cmd = 246 + make_test_command ~is_write:v.is_write ~is_verify:v.is_verify 247 + ~is_ack:v.is_ack ~is_increment:v.is_increment 248 + ~reply_addr_len:v.reply_addr_len 249 + in 250 + let encoded = 251 + Spacewire.encode_rmap_command ~target_logical_address:0xFE cmd 252 + in 253 + match Spacewire.decode_rmap_command encoded with 254 + | Error _ -> 255 + Alcotest.failf "%s: decode_rmap_command failed" v.instr_name 256 + | Ok (target, decoded) -> 257 + Alcotest.(check int) (v.instr_name ^ ": target") 0xFE target; 258 + let expected_code : Spacewire.rmap_command_code = 259 + match (v.is_write, v.is_verify) with 260 + | false, false -> Read 261 + | true, false -> Write 262 + | true, true -> Write_verify 263 + | false, true -> Read_modify_write 264 + in 265 + Alcotest.(check bool) 266 + (v.instr_name ^ ": cmd_code") 267 + true 268 + (Spacewire.rmap_cmd_code decoded = expected_code); 269 + let expected_ack : Spacewire.rmap_ack = 270 + if v.is_ack then Ack else No_ack 271 + in 272 + Alcotest.(check bool) 273 + (v.instr_name ^ ": ack") true 274 + (Spacewire.rmap_cmd_ack decoded = expected_ack); 275 + let expected_inc : Spacewire.rmap_increment = 276 + if v.is_increment then Increment else No_increment 277 + in 278 + Alcotest.(check bool) 279 + (v.instr_name ^ ": increment") 280 + true 281 + (Spacewire.rmap_cmd_increment decoded = expected_inc) 282 + end) 283 + vectors 284 + 285 + (* {1 RMAP CRC-8 Vectors} *) 286 + 287 + type crc_vector = { crc_name : string; data_hex : string; crc_hex : string } 288 + 289 + let crc_codec = 290 + Csvt.( 291 + Row.( 292 + obj (fun crc_name data_hex crc_hex -> { crc_name; data_hex; crc_hex }) 293 + |> col "name" string ~enc:(fun r -> r.crc_name) 294 + |> col "data_hex" string ~enc:(fun r -> r.data_hex) 295 + |> col "crc_hex" string ~enc:(fun r -> r.crc_hex) 296 + |> finish)) 297 + 298 + let load_crc_vectors () = 299 + match Csvt.decode_file crc_codec "vectors_crc.csv" with 300 + | Ok rows -> rows 301 + | Error e -> Alcotest.failf "CSV decode: %a" Csvt.pp_error e 302 + 303 + let test_crc vectors () = 304 + List.iter 305 + (fun (v : crc_vector) -> 306 + let data = hex_to_string v.data_hex in 307 + let got = Spacewire.rmap_crc data in 308 + let expected = int_of_string ("0x" ^ v.crc_hex) in 309 + Alcotest.(check int) (v.crc_name ^ ": crc8") expected got) 310 + vectors 311 + 312 + let test_crc_bytes vectors () = 313 + List.iter 314 + (fun (v : crc_vector) -> 315 + let data = hex_to_string v.data_hex in 316 + let buf = Bytes.of_string data in 317 + let got = Spacewire.rmap_crc_bytes buf 0 (Bytes.length buf) in 318 + let expected = int_of_string ("0x" ^ v.crc_hex) in 319 + Alcotest.(check int) (v.crc_name ^ ": crc8_bytes") expected got) 320 + vectors 321 + 322 + (* {1 Runner} *) 323 + 324 + let () = 325 + let pkt_vectors = load_packet_vectors () in 326 + let instr_vectors = load_instruction_vectors () in 327 + let crc_vectors = load_crc_vectors () in 328 + Alcotest.run "spacewire-interop-python" 329 + [ 330 + ( "packet-encode", 331 + [ 332 + Alcotest.test_case "encode matches reference" `Quick 333 + (test_packet_encode pkt_vectors); 334 + ] ); 335 + ( "packet-decode", 336 + [ 337 + Alcotest.test_case "decode reference packets" `Quick 338 + (test_packet_decode pkt_vectors); 339 + ] ); 340 + ( "packet-roundtrip", 341 + [ 342 + Alcotest.test_case "encode-decode roundtrip" `Quick 343 + (test_packet_roundtrip pkt_vectors); 344 + ] ); 345 + ( "rmap-instruction-encode", 346 + [ 347 + Alcotest.test_case "command instruction byte" `Quick 348 + (test_instruction_encode_via_command instr_vectors); 349 + Alcotest.test_case "reply instruction byte" `Quick 350 + (test_instruction_encode_via_reply instr_vectors); 351 + ] ); 352 + ( "rmap-instruction-decode", 353 + [ 354 + Alcotest.test_case "command roundtrip decode" `Quick 355 + (test_instruction_decode_via_command instr_vectors); 356 + ] ); 357 + ( "rmap-crc8", 358 + [ 359 + Alcotest.test_case "rmap_crc matches reference" `Quick 360 + (test_crc crc_vectors); 361 + Alcotest.test_case "rmap_crc_bytes matches reference" `Quick 362 + (test_crc_bytes crc_vectors); 363 + ] ); 364 + ]