SpaceOS wire protocol codecs for host-guest communication

refactor(space-wire): fix linter issues for naming, docs, tests, and function length

- Rename msg_type type/functions to kind (E330) and drop get_ prefix
from shared_mem getters (E331), updating all callers in space-wire
and space-net
- Add doc comments to all .mli files (E405) and fix superblock doc
style (E410)
- Split monolithic test.ml into per-module test files (E605) and add
.ocamlformat (E500)
- Remove test_space_wire_3d (E610: no corresponding library module)
- Extract helpers from run_guest/run_host in demo.ml to stay under
50-line threshold (E005)

+766 -601
+1
.ocamlformat
··· 1 +
+212 -256
bin/demo.ml
··· 104 104 let event_record_size = Wire.Codec.wire_size Event_log.codec 105 105 let event_log_header_size = 8 106 106 107 - let get_event_write_ptr storage = 107 + let event_write_ptr storage = 108 108 let off = event_log_start * block_size in 109 109 Bytes.get_int64_be storage off |> Int64.to_int 110 110 ··· 113 113 Bytes.set_int64_be storage off (Int64.of_int ptr) 114 114 115 115 let write_event storage (ev : Event_log.t) = 116 - let ptr = get_event_write_ptr storage in 116 + let ptr = event_write_ptr storage in 117 117 let base = (event_log_start * block_size) + event_log_header_size in 118 118 let area_size = (16 * block_size) - event_log_header_size in 119 119 let off = base + (ptr mod area_size) in ··· 150 150 Bytes.blit storage 0 buf 0 ws; 151 151 Wire.Codec.decode Superblock.codec buf 0 152 152 153 - (* Guest fiber *) 154 - let run_guest ~clock ~shm ~storage src dst = 155 - let reader = Eio.Buf_read.of_flow ~max_size:(Msg.frame_size * 100) src in 156 - let heartbeat = ref 0L in 157 - let running = ref true in 158 - let seq = ref 0 in 153 + (* Guest tick actions *) 154 + 155 + let guest_send_tm dst ~tick ~seq = 156 + let payload = 157 + Fmt.str "chan=TEMP val=%d.%d seq=%d" 158 + (20 + (tick mod 5)) 159 + (tick * 37 mod 100) 160 + seq 161 + in 162 + write_frame dst (Msg.v TM ~apid:0x10 payload) 163 + 164 + let guest_write_boot_event ~shm ~storage = 165 + let ev = 166 + Event_log.v 167 + ~timestamp:(Int64.to_int (Shared_mem.heartbeat shm)) 168 + INFO ~event_code:0x0001 "guest boot complete" 169 + in 170 + write_event storage ev; 171 + log_guest "wrote event log: boot complete (code=0x0001)" 172 + 173 + let guest_write_dp ~storage dst = 174 + let dp_data = String.init block_size (fun i -> Char.chr (i land 0xFF)) in 175 + let dp_offset = dp_area_start in 176 + let storage_off = dp_offset * block_size in 177 + Bytes.blit_string dp_data 0 storage storage_off block_size; 178 + let crc = crc32c dp_data in 179 + let frame = 180 + dp_frame ~block_offset:0 ~block_count:1 ~dp_class:1 ~priority:2 181 + ~name:"science/thermal.dat" ~crc32:crc 182 + in 183 + write_frame dst frame; 184 + log_guest "wrote DP: 1 block at offset %d, crc=0x%08x" dp_offset crc 185 + 186 + let guest_write_param ~shm ~storage = 187 + let entry = Param_entry.v ~param_id:42 ~generation:1 "3.14159" in 188 + write_param storage ~slot:1 entry; 189 + log_guest "wrote param: id=42 gen=1 value=\"3.14159\""; 190 + let ev = 191 + Event_log.v 192 + ~timestamp:(Int64.to_int (Shared_mem.heartbeat shm)) 193 + INFO ~event_code:0x0010 "param 42 updated" 194 + in 195 + write_event storage ev; 196 + log_guest "wrote event log: param updated (code=0x0010)" 197 + 198 + let guest_send_bad_frame dst = 199 + let bad_frame = 200 + { 201 + Msg.version = 0x01; 202 + msg_type = 0xFF; 203 + apid = 0x10; 204 + payload_length = 0; 205 + reserved = 0; 206 + payload = String.make Msg.max_payload '\x00'; 207 + } 208 + in 209 + write_frame dst bad_frame; 210 + log_guest "sent invalid type 0xFF (testing ERROR handling)" 211 + 212 + let guest_process_frame (frame : Msg.t) = 213 + match Msg.kind_of_int frame.msg_type with 214 + | Some TC -> log_guest "received TC: %s" (Msg.payload_bytes frame) 215 + | Some PRM_SET -> log_guest "received PRM_SET: %s" (Msg.payload_bytes frame) 216 + | Some ERROR -> 217 + let payload = Msg.payload_bytes frame in 218 + if String.length payload >= Wire.Codec.wire_size Error_payload.codec then begin 219 + let err = 220 + Wire.Codec.decode Error_payload.codec (Bytes.of_string payload) 0 221 + in 222 + log_guest "received ERROR: %a" Error_payload.pp err 223 + end 224 + else log_guest "received ERROR (short payload)" 225 + | Some _ -> log_guest "received %a" Msg.pp frame 226 + | None -> log_guest "received unknown type 0x%02x" frame.msg_type 159 227 160 - (* Read and validate superblock *) 228 + let guest_tick_actions ~shm ~storage ~tick dst = 229 + if tick = 2 then guest_write_boot_event ~shm ~storage; 230 + if tick = 3 then guest_write_dp ~storage dst; 231 + if tick = 4 then guest_write_param ~shm ~storage; 232 + if tick = 9 then begin 233 + let entry = Param_entry.v ~param_id:42 ~generation:2 "2.71828" in 234 + write_param storage ~slot:2 entry; 235 + log_guest "wrote param: id=42 gen=2 value=\"2.71828\"" 236 + end; 237 + if tick = 6 then begin 238 + write_frame dst (Msg.v PRM_RSP ~apid:0x01 "param_id=42 value=3.14159"); 239 + log_guest "sent PRM_RSP for param 42" 240 + end; 241 + if tick = 7 then guest_send_bad_frame dst 242 + 243 + let guest_boot ~storage = 161 244 let sb = read_superblock storage in 162 - if Superblock.check_magic sb && Superblock.check_crc sb then 245 + if Superblock.check_magic sb && Superblock.check_crc sb then begin 163 246 log_guest "superblock OK: tenant=%d blocks=%d dp_start=%d uuid=%S" 164 247 sb.tenant_id sb.total_blocks sb.dp_start 165 - (String.sub sb.uuid 0 (min 15 (String.length sb.uuid))) 248 + (String.sub sb.uuid 0 (min 15 (String.length sb.uuid))); 249 + true 250 + end 166 251 else begin 167 252 log_guest "FATAL: superblock invalid"; 168 - running := false 169 - end; 253 + false 254 + end 170 255 171 - (* Read initial parameters from parameter store *) 256 + (* Guest fiber *) 257 + let run_guest ~clock ~shm ~storage src dst = 258 + let reader = Eio.Buf_read.of_flow ~max_size:(Msg.frame_size * 100) src in 259 + let heartbeat = ref 0L in 260 + let running = ref (guest_boot ~storage) in 261 + let seq = ref 0 in 172 262 let p0 = read_param storage ~slot:0 in 173 263 if Param_entry.check_crc p0 then 174 264 log_guest "param[0]: id=%d gen=%d value=%S" p0.param_id p0.generation 175 265 (Param_entry.value_bytes p0) 176 266 else log_guest "param[0]: no valid entry"; 177 - 178 - (* Send boot EVR *) 179 267 write_frame dst (Msg.v EVR ~apid:0x10 "guest booted successfully"); 180 268 log_guest "sent boot EVR"; 181 - 182 269 let tick = ref 0 in 183 270 while !running do 184 - (* Heartbeat *) 185 271 heartbeat := Int64.add !heartbeat 1L; 186 272 Shared_mem.set_heartbeat shm !heartbeat; 187 273 Shared_mem.set_guest_status shm 0; 188 274 Shared_mem.set_health_string shm 189 275 (Fmt.str "nominal tick=%d seq=%d" !tick !seq); 190 - 191 - (* Read mission time from shared memory *) 192 276 let mt = Shared_mem.read_mission_time shm in 193 277 if !tick mod 4 = 0 && Int64.compare mt.seconds 0L > 0 then 194 278 log_guest "mission time: %Ld.%09d" mt.seconds mt.nanos; 195 - 196 - (* TM every tick *) 197 - let payload = 198 - Fmt.str "chan=TEMP val=%d.%d seq=%d" 199 - (20 + (!tick mod 5)) 200 - (!tick * 37 mod 100) 201 - !seq 202 - in 203 - write_frame dst (Msg.v TM ~apid:0x10 payload); 279 + guest_send_tm dst ~tick:!tick ~seq:!seq; 204 280 incr seq; 205 - 206 - (* Event log at tick 2: write boot event to ring buffer *) 207 - if !tick = 2 then begin 208 - let ev = 209 - Event_log.v 210 - ~timestamp:(Int64.to_int (Shared_mem.get_heartbeat shm)) 211 - INFO ~event_code:0x0001 "guest boot complete" 212 - in 213 - write_event storage ev; 214 - log_guest "wrote event log: boot complete (code=0x0001)" 215 - end; 216 - 217 - (* DP at tick 3: write data product to storage, send notification *) 218 - if !tick = 3 then begin 219 - let dp_data = String.init block_size (fun i -> Char.chr (i land 0xFF)) in 220 - let dp_offset = dp_area_start in 221 - let storage_off = dp_offset * block_size in 222 - Bytes.blit_string dp_data 0 storage storage_off block_size; 223 - let crc = crc32c dp_data in 224 - let frame = 225 - dp_frame ~block_offset:0 ~block_count:1 ~dp_class:1 ~priority:2 226 - ~name:"science/thermal.dat" ~crc32:crc 227 - in 228 - write_frame dst frame; 229 - log_guest "wrote DP: 1 block at offset %d, crc=0x%08x" dp_offset crc 230 - end; 231 - 232 - (* Param store at tick 4: write a parameter, log the event *) 233 - if !tick = 4 then begin 234 - let entry = Param_entry.v ~param_id:42 ~generation:1 "3.14159" in 235 - write_param storage ~slot:1 entry; 236 - log_guest "wrote param: id=42 gen=1 value=\"3.14159\""; 237 - let ev = 238 - Event_log.v 239 - ~timestamp:(Int64.to_int (Shared_mem.get_heartbeat shm)) 240 - INFO ~event_code:0x0010 "param 42 updated" 241 - in 242 - write_event storage ev; 243 - log_guest "wrote event log: param updated (code=0x0010)" 244 - end; 245 - 246 - (* Param store at tick 9: update same parameter with higher generation *) 247 - if !tick = 9 then begin 248 - let entry = Param_entry.v ~param_id:42 ~generation:2 "2.71828" in 249 - write_param storage ~slot:2 entry; 250 - log_guest "wrote param: id=42 gen=2 value=\"2.71828\"" 251 - end; 252 - 253 - (* PRM_RSP at tick 6: send a parameter response *) 254 - if !tick = 6 then begin 255 - let payload = Fmt.str "param_id=42 value=3.14159" in 256 - write_frame dst (Msg.v PRM_RSP ~apid:0x01 payload); 257 - log_guest "sent PRM_RSP for param 42" 258 - end; 259 - 260 - (* Intentionally send a bad frame at tick 7 to trigger ERROR *) 261 - if !tick = 7 then begin 262 - let bad_frame = 263 - { 264 - Msg.version = 0x01; 265 - msg_type = 0xFF; 266 - apid = 0x10; 267 - payload_length = 0; 268 - reserved = 0; 269 - payload = String.make Msg.max_payload '\x00'; 270 - } 271 - in 272 - write_frame dst bad_frame; 273 - log_guest "sent invalid type 0xFF (testing ERROR handling)" 274 - end; 275 - 276 - (* Process all incoming frames *) 277 - let frames = drain_frames clock reader in 278 - List.iter 279 - (fun frame -> 280 - match Msg.msg_type_of_int frame.Msg.msg_type with 281 - | Some TC -> log_guest "received TC: %s" (Msg.payload_bytes frame) 282 - | Some PRM_SET -> 283 - log_guest "received PRM_SET: %s" (Msg.payload_bytes frame) 284 - | Some ERROR -> 285 - let payload = Msg.payload_bytes frame in 286 - if String.length payload >= Wire.Codec.wire_size Error_payload.codec 287 - then begin 288 - let err = 289 - Wire.Codec.decode Error_payload.codec (Bytes.of_string payload) 290 - 0 291 - in 292 - log_guest "received ERROR: %a" Error_payload.pp err 293 - end 294 - else log_guest "received ERROR (short payload)" 295 - | Some _ -> log_guest "received %a" Msg.pp frame 296 - | None -> log_guest "received unknown type 0x%02x" frame.Msg.msg_type) 297 - frames; 298 - 299 - (* Poll command word *) 300 - let cmd = Shared_mem.get_host_cmd shm in 281 + guest_tick_actions ~shm ~storage ~tick:!tick dst; 282 + List.iter guest_process_frame (drain_frames clock reader); 283 + let cmd = Shared_mem.host_cmd shm in 301 284 if cmd land Shared_mem.cmd_shutdown <> 0 then begin 302 285 log_guest "shutdown requested, performing graceful teardown"; 303 - (* Send final EVR *) 304 286 write_frame dst 305 287 (Msg.v EVR ~apid:0x10 306 288 (Fmt.str "shutting down after %d ticks, %d TM sent" !tick !seq)); 307 289 Shared_mem.set_guest_cmd_ack shm Shared_mem.cmd_shutdown; 308 290 running := false 309 291 end; 310 - 311 - if cmd land Shared_mem.cmd_dp_ack <> 0 then begin 312 - log_guest "host acknowledged data product" 313 - (* Clear the DP_ACK bit we noticed *) 314 - end; 315 - 292 + if cmd land Shared_mem.cmd_dp_ack <> 0 then 293 + log_guest "host acknowledged data product"; 316 294 incr tick; 317 295 if !running then Eio.Time.sleep clock 1.0 318 296 done; 319 297 log_guest "exited cleanly" 320 298 299 + (* Host heartbeat check *) 300 + let host_check_heartbeat ~shm ~last_heartbeat ~missed = 301 + let hb = Shared_mem.heartbeat shm in 302 + if Int64.equal hb !last_heartbeat then begin 303 + incr missed; 304 + if !missed >= 5 then begin 305 + log_host "ALERT: heartbeat timeout (%d misses)" !missed; 306 + Shared_mem.set_host_cmd shm Shared_mem.cmd_shutdown 307 + end 308 + end 309 + else begin 310 + if !missed > 0 then log_host "heartbeat recovered after %d misses" !missed; 311 + missed := 0; 312 + last_heartbeat := hb; 313 + Shared_mem.set_heartbeat_ack shm hb 314 + end 315 + 316 + (* Host reads event log and params from storage *) 317 + let host_read_telemetry ~storage = 318 + let ptr = event_write_ptr storage in 319 + let n_events = ptr / event_record_size in 320 + log_host "event log: %d events written" n_events; 321 + for i = 0 to min (n_events - 1) 2 do 322 + let ev = read_event storage ~index:i in 323 + log_host " event[%d]: %a" i Event_log.pp ev 324 + done; 325 + let p = read_param storage ~slot:2 in 326 + if Param_entry.check_crc p then 327 + log_host "param store: id=%d gen=%d value=%S" p.param_id p.generation 328 + (Param_entry.value_bytes p) 329 + else begin 330 + let p1 = read_param storage ~slot:1 in 331 + if Param_entry.check_crc p1 then 332 + log_host "param store: id=%d gen=%d value=%S" p1.param_id p1.generation 333 + (Param_entry.value_bytes p1) 334 + else log_host "param store: no valid entries for id=42" 335 + end 336 + 337 + (* Host validates and dispatches a DP frame *) 338 + let host_handle_dp ~shm ~storage ~dp_count (frame : Msg.t) = 339 + incr dp_count; 340 + let payload = Msg.payload_bytes frame in 341 + if String.length payload >= Wire.Codec.wire_size Dp_payload.codec then begin 342 + let dp = Wire.Codec.decode Dp_payload.codec (Bytes.of_string payload) 0 in 343 + log_host "DP[%d]: %s offset=%d count=%d pri=%d crc=0x%08x" !dp_count 344 + (Dp_payload.name_string dp) 345 + dp.block_offset dp.block_count dp.priority dp.crc32; 346 + let storage_off = (dp_area_start + dp.block_offset) * block_size in 347 + let data = 348 + Bytes.sub_string storage storage_off (dp.block_count * block_size) 349 + in 350 + let computed_crc = crc32c data in 351 + if computed_crc = dp.crc32 then begin 352 + log_host "DP CRC validated OK"; 353 + let cmd = Shared_mem.host_cmd shm in 354 + Shared_mem.set_host_cmd shm (cmd lor Shared_mem.cmd_dp_ack) 355 + end 356 + else 357 + log_host "DP CRC MISMATCH: expected 0x%08x, got 0x%08x" dp.crc32 358 + computed_crc 359 + end 360 + else log_host "DP: short payload (%d bytes)" (String.length payload) 361 + 362 + (* Host processes a single incoming frame *) 363 + let host_process_frame ~shm ~storage ~tm_count ~evr_count ~dp_count dst 364 + (frame : Msg.t) = 365 + if frame.version <> 0x01 then begin 366 + log_host "ERROR: unknown version 0x%02x, sending NACK" frame.version; 367 + write_frame dst 368 + (error_frame Unknown_version ~offending_type:frame.msg_type 369 + ~offending_apid:frame.apid ~offending_pay_len:frame.payload_length) 370 + end 371 + else 372 + match Msg.kind_of_int frame.msg_type with 373 + | None -> 374 + log_host "ERROR: unknown type 0x%02x from apid %d, sending NACK" 375 + frame.msg_type frame.apid; 376 + write_frame dst 377 + (error_frame Unknown_type ~offending_type:frame.msg_type 378 + ~offending_apid:frame.apid ~offending_pay_len:frame.payload_length) 379 + | Some EVR -> 380 + incr evr_count; 381 + log_host "EVR[%d]: %s" !evr_count (Msg.payload_bytes frame) 382 + | Some TM -> 383 + incr tm_count; 384 + if !tm_count <= 3 || !tm_count mod 5 = 0 then 385 + log_host "TM[%d]: %s" !tm_count (Msg.payload_bytes frame) 386 + | Some DP -> host_handle_dp ~shm ~storage ~dp_count frame 387 + | Some PRM_RSP -> log_host "PRM_RSP: %s" (Msg.payload_bytes frame) 388 + | Some HEALTH -> 389 + let hs = Shared_mem.health_string shm in 390 + log_host "HEALTH ping (health=%S)" hs 391 + | Some msg_type -> 392 + log_host "received %a: %s" Msg.pp_kind msg_type 393 + (Msg.payload_bytes frame) 394 + 321 395 (* Host fiber *) 322 396 let run_host ~clock ~shm ~storage src dst = 323 397 let reader = Eio.Buf_read.of_flow ~max_size:(Msg.frame_size * 100) src in ··· 330 404 let dp_count = ref 0 in 331 405 332 406 while !running do 333 - (* Update mission time at 1 Hz *) 334 407 let now = Eio.Time.now clock in 335 408 let seconds = Int64.of_float now in 336 409 let frac = now -. Int64.to_float seconds in 337 410 let nanos = int_of_float (frac *. 1e9) in 338 411 Shared_mem.write_mission_time shm Shared_mem.{ seconds; nanos }; 339 - 340 - (* Echo heartbeat *) 341 - let hb = Shared_mem.get_heartbeat shm in 342 - if Int64.equal hb !last_heartbeat then begin 343 - incr missed; 344 - if !missed >= 5 then begin 345 - log_host "ALERT: heartbeat timeout (%d misses)" !missed; 346 - Shared_mem.set_host_cmd shm Shared_mem.cmd_shutdown 347 - end 348 - end 349 - else begin 350 - if !missed > 0 then log_host "heartbeat recovered after %d misses" !missed; 351 - missed := 0; 352 - last_heartbeat := hb; 353 - Shared_mem.set_heartbeat_ack shm hb 354 - end; 355 - 356 - (* Send TC at tick 5 *) 412 + host_check_heartbeat ~shm ~last_heartbeat ~missed; 357 413 if !tick = 5 then begin 358 414 write_frame dst (Msg.v TC ~apid:0x10 "CMD_DEPLOY target=ANTENNA_1"); 359 415 log_host "sent TC: CMD_DEPLOY target=ANTENNA_1" 360 416 end; 361 - 362 - (* Send PRM_SET at tick 8 *) 363 417 if !tick = 8 then begin 364 418 write_frame dst (Msg.v PRM_SET ~apid:0x01 "param_id=42 value=2.71828"); 365 419 log_host "sent PRM_SET: param 42 = 2.71828" 366 420 end; 367 - 368 - (* Read event log + params at tick 9 *) 369 - if !tick = 9 then begin 370 - let ptr = get_event_write_ptr storage in 371 - let n_events = ptr / event_record_size in 372 - log_host "event log: %d events written" n_events; 373 - for i = 0 to min (n_events - 1) 2 do 374 - let ev = read_event storage ~index:i in 375 - log_host " event[%d]: %a" i Event_log.pp ev 376 - done; 377 - (* Read latest param for id=42 *) 378 - let p = read_param storage ~slot:2 in 379 - if Param_entry.check_crc p then 380 - log_host "param store: id=%d gen=%d value=%S" p.param_id p.generation 381 - (Param_entry.value_bytes p) 382 - else begin 383 - let p1 = read_param storage ~slot:1 in 384 - if Param_entry.check_crc p1 then 385 - log_host "param store: id=%d gen=%d value=%S" p1.param_id 386 - p1.generation 387 - (Param_entry.value_bytes p1) 388 - else log_host "param store: no valid entries for id=42" 389 - end 390 - end; 391 - 392 - (* Shutdown at tick 12 *) 421 + if !tick = 9 then host_read_telemetry ~storage; 393 422 if !tick = 12 then begin 394 423 log_host "initiating shutdown sequence"; 395 424 Shared_mem.set_host_cmd shm Shared_mem.cmd_shutdown 396 425 end; 397 - 398 - (* Process all incoming frames *) 399 - let frames = drain_frames clock reader in 400 426 List.iter 401 - (fun frame -> 402 - let (frame : Msg.t) = frame in 403 - (* Validate version *) 404 - if frame.version <> 0x01 then begin 405 - log_host "ERROR: unknown version 0x%02x, sending NACK" frame.version; 406 - write_frame dst 407 - (error_frame Unknown_version ~offending_type:frame.Msg.msg_type 408 - ~offending_apid:frame.apid 409 - ~offending_pay_len:frame.payload_length) 410 - end 411 - else 412 - match Msg.msg_type_of_int frame.Msg.msg_type with 413 - | None -> 414 - (* Unknown type → ERROR/NACK *) 415 - log_host "ERROR: unknown type 0x%02x from apid %d, sending NACK" 416 - frame.Msg.msg_type frame.apid; 417 - write_frame dst 418 - (error_frame Unknown_type ~offending_type:frame.Msg.msg_type 419 - ~offending_apid:frame.apid 420 - ~offending_pay_len:frame.payload_length) 421 - | Some EVR -> 422 - incr evr_count; 423 - log_host "EVR[%d]: %s" !evr_count (Msg.payload_bytes frame) 424 - | Some TM -> 425 - incr tm_count; 426 - if !tm_count <= 3 || !tm_count mod 5 = 0 then 427 - log_host "TM[%d]: %s" !tm_count (Msg.payload_bytes frame) 428 - | Some DP -> begin 429 - incr dp_count; 430 - let payload = Msg.payload_bytes frame in 431 - if String.length payload >= Wire.Codec.wire_size Dp_payload.codec 432 - then begin 433 - let dp = 434 - Wire.Codec.decode Dp_payload.codec (Bytes.of_string payload) 0 435 - in 436 - log_host "DP[%d]: %s offset=%d count=%d pri=%d crc=0x%08x" 437 - !dp_count 438 - (Dp_payload.name_string dp) 439 - dp.block_offset dp.block_count dp.priority dp.crc32; 440 - (* Validate CRC against storage *) 441 - let storage_off = 442 - (dp_area_start + dp.block_offset) * block_size 443 - in 444 - let data = 445 - Bytes.sub_string storage storage_off 446 - (dp.block_count * block_size) 447 - in 448 - let computed_crc = crc32c data in 449 - if computed_crc = dp.crc32 then begin 450 - log_host "DP CRC validated OK"; 451 - (* Set DP_ACK in command word *) 452 - let cmd = Shared_mem.get_host_cmd shm in 453 - Shared_mem.set_host_cmd shm (cmd lor Shared_mem.cmd_dp_ack) 454 - end 455 - else 456 - log_host "DP CRC MISMATCH: expected 0x%08x, got 0x%08x" 457 - dp.crc32 computed_crc 458 - end 459 - else 460 - log_host "DP: short payload (%d bytes)" (String.length payload) 461 - end 462 - | Some PRM_RSP -> log_host "PRM_RSP: %s" (Msg.payload_bytes frame) 463 - | Some HEALTH -> 464 - let hs = Shared_mem.get_health_string shm in 465 - log_host "HEALTH ping (health=%S)" hs 466 - | Some msg_type -> 467 - log_host "received %a: %s" Msg.pp_msg_type msg_type 468 - (Msg.payload_bytes frame)) 469 - frames; 470 - 471 - (* Check guest ack for shutdown *) 472 - let ack = Shared_mem.get_guest_cmd_ack shm in 427 + (host_process_frame ~shm ~storage ~tm_count ~evr_count ~dp_count dst) 428 + (drain_frames clock reader); 429 + let ack = Shared_mem.guest_cmd_ack shm in 473 430 if ack land Shared_mem.cmd_shutdown <> 0 then begin 474 431 log_host "guest acknowledged shutdown"; 475 - let n_events = get_event_write_ptr storage / event_record_size in 432 + let n_events = event_write_ptr storage / event_record_size in 476 433 log_host "session summary: %d TM, %d EVR, %d DP, %d events logged" 477 434 !tm_count !evr_count !dp_count n_events; 478 435 running := false 479 436 end; 480 - 481 437 incr tick; 482 438 if !running then Eio.Time.sleep clock 1.0 483 439 done;
+8
lib/dp_payload.mli
··· 21 21 } 22 22 23 23 val codec : t Wire.Codec.t 24 + (** Wire codec for the 80-byte DP notification payload. *) 24 25 25 26 val v : 26 27 block_offset:int -> ··· 30 31 name:string -> 31 32 crc32:int -> 32 33 t 34 + (** [v ~block_offset ~block_count ~dp_class ~priority ~name ~crc32] builds a 35 + data product notification. *) 33 36 34 37 val name_string : t -> string 38 + (** [name_string t] returns the name with trailing nulls stripped. *) 39 + 35 40 val pp : t Fmt.t 41 + (** Pretty-print a data product notification. *) 42 + 36 43 val equal : t -> t -> bool 44 + (** Structural equality. *)
+9
lib/error_payload.mli
··· 17 17 | Malformed 18 18 19 19 val error_code_to_int : error_code -> int 20 + (** Convert an error code to its wire representation. *) 21 + 20 22 val error_code_of_int : int -> error_code option 23 + (** Parse an error code from its wire representation. *) 21 24 22 25 type t = { 23 26 error_code : int; ··· 28 31 } 29 32 30 33 val codec : t Wire.Codec.t 34 + (** Wire codec for the 8-byte error payload. *) 31 35 32 36 val v : 33 37 error_code -> ··· 35 39 offending_apid:int -> 36 40 offending_pay_len:int -> 37 41 t 42 + (** [v code ~offending_type ~offending_apid ~offending_pay_len] builds an error 43 + payload. *) 38 44 39 45 val pp : t Fmt.t 46 + (** Pretty-print an error payload. *) 47 + 40 48 val equal : t -> t -> bool 49 + (** Structural equality. *)
+9
lib/event_log.mli
··· 26 26 type severity = DEBUG | INFO | WARNING | ERROR | FATAL 27 27 28 28 val severity_to_int : severity -> int 29 + (** Convert a severity level to its wire representation. *) 30 + 29 31 val severity_of_int : int -> severity option 32 + (** Parse a severity level from its wire representation. *) 33 + 30 34 val pp_severity : severity Fmt.t 35 + (** Pretty-print a severity level. *) 31 36 32 37 (** {1 Type} *) 33 38 ··· 42 47 } 43 48 44 49 val codec : t Wire.Codec.t 50 + (** Wire codec for a 76-byte event log record. *) 45 51 46 52 val v : timestamp:int -> severity -> event_code:int -> string -> t 47 53 (** [v ~timestamp sev ~event_code payload] builds an event log record. *) ··· 51 57 bytes). *) 52 58 53 59 val pp : t Fmt.t 60 + (** Pretty-print an event log record. *) 61 + 54 62 val equal : t -> t -> bool 63 + (** Structural equality. *)
+7 -7
lib/msg.ml
··· 4 4 let header_size = 8 5 5 let max_payload = frame_size - header_size 6 6 7 - type msg_type = 7 + type kind = 8 8 | TM 9 9 | TC 10 10 | EVR ··· 16 16 | LOG 17 17 | ERROR 18 18 19 - let msg_type_to_int = function 19 + let kind_to_int = function 20 20 | TM -> 0x00 21 21 | TC -> 0x01 22 22 | EVR -> 0x02 ··· 28 28 | LOG -> 0x08 29 29 | ERROR -> 0x09 30 30 31 - let msg_type_of_int = function 31 + let kind_of_int = function 32 32 | 0x00 -> Some TM 33 33 | 0x01 -> Some TC 34 34 | 0x02 -> Some EVR ··· 41 41 | 0x09 -> Some ERROR 42 42 | _ -> None 43 43 44 - let pp_msg_type ppf = function 44 + let pp_kind ppf = function 45 45 | TM -> Fmt.string ppf "TM" 46 46 | TC -> Fmt.string ppf "TC" 47 47 | EVR -> Fmt.string ppf "EVR" ··· 82 82 Bytes.blit_string payload 0 padded 0 payload_length; 83 83 { 84 84 version = 0x01; 85 - msg_type = msg_type_to_int typ; 85 + msg_type = kind_to_int typ; 86 86 apid; 87 87 payload_length; 88 88 reserved = 0; ··· 95 95 96 96 let pp ppf t = 97 97 let typ_s = 98 - match msg_type_of_int t.msg_type with 99 - | Some mt -> Fmt.str "%a" pp_msg_type mt 98 + match kind_of_int t.msg_type with 99 + | Some mt -> Fmt.str "%a" pp_kind mt 100 100 | None -> Fmt.str "0x%02x" t.msg_type 101 101 in 102 102 Fmt.pf ppf "@[<h>frame(ver=%d type=%s apid=%d payload=%d)@]" t.version typ_s
+13 -5
lib/msg.mli
··· 24 24 25 25 (** {1 Message types} *) 26 26 27 - type msg_type = 27 + type kind = 28 28 | TM 29 29 | TC 30 30 | EVR ··· 36 36 | LOG 37 37 | ERROR 38 38 39 - val msg_type_to_int : msg_type -> int 40 - val msg_type_of_int : int -> msg_type option 41 - val pp_msg_type : msg_type Fmt.t 39 + val kind_to_int : kind -> int 40 + (** Convert a message type to its wire representation. *) 41 + 42 + val kind_of_int : int -> kind option 43 + (** Parse a message type from its wire representation. *) 44 + 45 + val pp_kind : kind Fmt.t 46 + (** Pretty-print a message type. *) 42 47 43 48 (** {1 Frame} *) 44 49 ··· 54 59 val codec : t Wire.Codec.t 55 60 (** Wire codec for the full 256-byte frame. *) 56 61 57 - val v : msg_type -> apid:int -> string -> t 62 + val v : kind -> apid:int -> string -> t 58 63 (** [v typ ~apid payload] builds a frame. Payload is truncated to 248 bytes. *) 59 64 60 65 val payload_bytes : t -> string ··· 62 67 [payload_length] bytes). *) 63 68 64 69 val pp : t Fmt.t 70 + (** Pretty-print a frame. *) 71 + 65 72 val equal : t -> t -> bool 73 + (** Structural equality. *)
+3
lib/param_entry.mli
··· 42 42 (** [check_crc t] validates the CRC-32C. *) 43 43 44 44 val pp : t Fmt.t 45 + (** Pretty-print a parameter entry. *) 46 + 45 47 val equal : t -> t -> bool 48 + (** Structural equality. *)
+14 -18
lib/shared_mem.ml
··· 13 13 let health_string_len = 256 14 14 15 15 (* uint64 big-endian accessors *) 16 - let get_heartbeat buf = Bytes.get_int64_be buf off_heartbeat 16 + let heartbeat buf = Bytes.get_int64_be buf off_heartbeat 17 17 let set_heartbeat buf v = Bytes.set_int64_be buf off_heartbeat v 18 - let get_heartbeat_ack buf = Bytes.get_int64_be buf off_heartbeat_ack 18 + let heartbeat_ack buf = Bytes.get_int64_be buf off_heartbeat_ack 19 19 let set_heartbeat_ack buf v = Bytes.set_int64_be buf off_heartbeat_ack v 20 20 21 21 (* uint32 big-endian accessors *) 22 - let get_time_version buf = 23 - Int32.to_int (Bytes.get_int32_be buf off_time_version) 22 + let time_version buf = Int32.to_int (Bytes.get_int32_be buf off_time_version) 24 23 25 24 let set_time_version buf v = 26 25 Bytes.set_int32_be buf off_time_version (Int32.of_int v) 27 26 28 - let get_time_seconds buf = Bytes.get_int64_be buf off_time_seconds 27 + let time_seconds buf = Bytes.get_int64_be buf off_time_seconds 29 28 let set_time_seconds buf v = Bytes.set_int64_be buf off_time_seconds v 30 - let get_time_nanos buf = Int32.to_int (Bytes.get_int32_be buf off_time_nanos) 29 + let time_nanos buf = Int32.to_int (Bytes.get_int32_be buf off_time_nanos) 31 30 32 31 let set_time_nanos buf v = 33 32 Bytes.set_int32_be buf off_time_nanos (Int32.of_int v) 34 33 35 - let get_guest_status buf = 36 - Int32.to_int (Bytes.get_int32_be buf off_guest_status) 34 + let guest_status buf = Int32.to_int (Bytes.get_int32_be buf off_guest_status) 37 35 38 36 let set_guest_status buf v = 39 37 Bytes.set_int32_be buf off_guest_status (Int32.of_int v) 40 38 41 - let get_host_cmd buf = Int32.to_int (Bytes.get_int32_be buf off_host_cmd) 39 + let host_cmd buf = Int32.to_int (Bytes.get_int32_be buf off_host_cmd) 42 40 let set_host_cmd buf v = Bytes.set_int32_be buf off_host_cmd (Int32.of_int v) 43 - 44 - let get_guest_cmd_ack buf = 45 - Int32.to_int (Bytes.get_int32_be buf off_guest_cmd_ack) 41 + let guest_cmd_ack buf = Int32.to_int (Bytes.get_int32_be buf off_guest_cmd_ack) 46 42 47 43 let set_guest_cmd_ack buf v = 48 44 Bytes.set_int32_be buf off_guest_cmd_ack (Int32.of_int v) 49 45 50 - let get_health_string buf = 46 + let health_string buf = 51 47 let s = Bytes.sub_string buf off_health_string health_string_len in 52 48 (* Find null terminator *) 53 49 match String.index_opt s '\x00' with ··· 65 61 type mission_time = { seconds : int64; nanos : int } 66 62 67 63 let write_mission_time buf t = 68 - let v = get_time_version buf + 1 in 64 + let v = time_version buf + 1 in 69 65 set_time_version buf v; 70 66 (* odd = write in progress *) 71 67 set_time_seconds buf t.seconds; ··· 75 71 76 72 let read_mission_time buf = 77 73 let rec loop () = 78 - let v1 = get_time_version buf in 74 + let v1 = time_version buf in 79 75 if v1 land 1 <> 0 then loop () (* spin-wait: host is mid-write *) 80 76 else 81 - let seconds = get_time_seconds buf in 82 - let nanos = get_time_nanos buf in 83 - let v2 = get_time_version buf in 77 + let seconds = time_seconds buf in 78 + let nanos = time_nanos buf in 79 + let v2 = time_version buf in 84 80 if v1 <> v2 then loop () (* torn read, retry *) else { seconds; nanos } 85 81 in 86 82 loop ()
+68 -9
lib/shared_mem.mli
··· 19 19 (** {1 Field offsets} *) 20 20 21 21 val off_heartbeat : int 22 + (** Offset of the guest heartbeat counter (8 bytes). *) 23 + 22 24 val off_heartbeat_ack : int 25 + (** Offset of the host heartbeat acknowledgement (8 bytes). *) 26 + 23 27 val off_time_version : int 28 + (** Offset of the time version seqlock (4 bytes). *) 29 + 24 30 val off_time_seconds : int 31 + (** Offset of mission time seconds (8 bytes). *) 32 + 25 33 val off_time_nanos : int 34 + (** Offset of mission time nanoseconds (4 bytes). *) 35 + 26 36 val off_guest_status : int 37 + (** Offset of the guest status word (4 bytes). *) 38 + 27 39 val off_host_cmd : int 40 + (** Offset of the host command word (4 bytes). *) 41 + 28 42 val off_guest_cmd_ack : int 43 + (** Offset of the guest command acknowledgement (4 bytes). *) 44 + 29 45 val off_health_string : int 46 + (** Offset of the guest health string (256 bytes). *) 47 + 30 48 val health_string_len : int 49 + (** Maximum health string length: 256 bytes. *) 31 50 32 51 (** {1 Accessors} 33 52 34 53 All accessors work on a [bytes] buffer of at least [page_size] bytes, using 35 54 big-endian byte order. *) 36 55 37 - val get_heartbeat : bytes -> int64 56 + val heartbeat : bytes -> int64 57 + (** Read the guest heartbeat counter. *) 58 + 38 59 val set_heartbeat : bytes -> int64 -> unit 39 - val get_heartbeat_ack : bytes -> int64 60 + (** Write the guest heartbeat counter. *) 61 + 62 + val heartbeat_ack : bytes -> int64 63 + (** Read the host heartbeat acknowledgement. *) 64 + 40 65 val set_heartbeat_ack : bytes -> int64 -> unit 41 - val get_time_version : bytes -> int 66 + (** Write the host heartbeat acknowledgement. *) 67 + 68 + val time_version : bytes -> int 69 + (** Read the time version seqlock. *) 70 + 42 71 val set_time_version : bytes -> int -> unit 43 - val get_time_seconds : bytes -> int64 72 + (** Write the time version seqlock. *) 73 + 74 + val time_seconds : bytes -> int64 75 + (** Read the mission time seconds. *) 76 + 44 77 val set_time_seconds : bytes -> int64 -> unit 45 - val get_time_nanos : bytes -> int 78 + (** Write the mission time seconds. *) 79 + 80 + val time_nanos : bytes -> int 81 + (** Read the mission time nanoseconds. *) 82 + 46 83 val set_time_nanos : bytes -> int -> unit 47 - val get_guest_status : bytes -> int 84 + (** Write the mission time nanoseconds. *) 85 + 86 + val guest_status : bytes -> int 87 + (** Read the guest status word. *) 88 + 48 89 val set_guest_status : bytes -> int -> unit 49 - val get_host_cmd : bytes -> int 90 + (** Write the guest status word. *) 91 + 92 + val host_cmd : bytes -> int 93 + (** Read the host command word. *) 94 + 50 95 val set_host_cmd : bytes -> int -> unit 51 - val get_guest_cmd_ack : bytes -> int 96 + (** Write the host command word. *) 97 + 98 + val guest_cmd_ack : bytes -> int 99 + (** Read the guest command acknowledgement. *) 100 + 52 101 val set_guest_cmd_ack : bytes -> int -> unit 53 - val get_health_string : bytes -> string 102 + (** Write the guest command acknowledgement. *) 103 + 104 + val health_string : bytes -> string 105 + (** Read the guest health string. *) 106 + 54 107 val set_health_string : bytes -> string -> unit 108 + (** Write the guest health string. *) 55 109 56 110 (** {1 Mission time with seqlock} *) 57 111 ··· 66 120 (** {1 Command word bits} *) 67 121 68 122 val cmd_shutdown : int 123 + (** Shutdown command bit. *) 124 + 69 125 val cmd_param_reload : int 126 + (** Parameter reload command bit. *) 127 + 70 128 val cmd_dp_ack : int 129 + (** Data product acknowledgement command bit. *)
+9 -1
lib/superblock.mli
··· 14 14 v} *) 15 15 16 16 val magic : int 17 - (** [0x53504F53] = "SPOS". *) 17 + (** Magic number: 0x53504F53 ("SPOS"). *) 18 18 19 19 type t = { 20 20 magic : int; ··· 30 30 } 31 31 32 32 val codec : t Wire.Codec.t 33 + (** Wire codec for the 48-byte superblock. *) 33 34 34 35 val v : 35 36 tenant_id:int -> ··· 42 43 (** Build a superblock with computed CRC. *) 43 44 44 45 val check_magic : t -> bool 46 + (** [check_magic t] returns [true] if the magic field matches. *) 47 + 45 48 val check_crc : t -> bool 49 + (** [check_crc t] validates the CRC-32C. *) 50 + 46 51 val pp : t Fmt.t 52 + (** Pretty-print a superblock. *) 53 + 47 54 val equal : t -> t -> bool 55 + (** Structural equality. *)
+23
lib/wire/space_wire_3d.mli
··· 4 4 [Wire.to_3d_file] to produce EverParse-compatible {e .3d} specifications. *) 5 5 6 6 val frame_struct : Wire.struct_ 7 + (** EverParse 3D struct for the frame codec. *) 8 + 7 9 val frame_module : Wire.module_ 10 + (** EverParse 3D module for the frame codec. *) 11 + 8 12 val error_payload_struct : Wire.struct_ 13 + (** EverParse 3D struct for the error payload codec. *) 14 + 9 15 val error_payload_module : Wire.module_ 16 + (** EverParse 3D module for the error payload codec. *) 17 + 10 18 val dp_payload_struct : Wire.struct_ 19 + (** EverParse 3D struct for the DP notification codec. *) 20 + 11 21 val dp_payload_module : Wire.module_ 22 + (** EverParse 3D module for the DP notification codec. *) 23 + 12 24 val superblock_struct : Wire.struct_ 25 + (** EverParse 3D struct for the superblock codec. *) 26 + 13 27 val superblock_module : Wire.module_ 28 + (** EverParse 3D module for the superblock codec. *) 29 + 14 30 val param_entry_struct : Wire.struct_ 31 + (** EverParse 3D struct for the parameter entry codec. *) 32 + 15 33 val param_entry_module : Wire.module_ 34 + (** EverParse 3D module for the parameter entry codec. *) 35 + 16 36 val event_log_struct : Wire.struct_ 37 + (** EverParse 3D struct for the event log codec. *) 38 + 17 39 val event_log_module : Wire.module_ 40 + (** EverParse 3D module for the event log codec. *) 18 41 19 42 val all_structs : (string * Wire.struct_) list 20 43 (** All schema structs, keyed by name. *)
+9
test/dune
··· 1 1 (test 2 2 (name test) 3 + (modules 4 + test 5 + test_msg 6 + test_error_payload 7 + test_dp_payload 8 + test_param_entry 9 + test_event_log 10 + test_superblock 11 + test_shared_mem) 3 12 (libraries space_wire alcotest))
+7 -305
test/test.ml
··· 1 - open Space_wire 2 - 3 - (* Helpers *) 4 - let encode codec v = 5 - let ws = Wire.Codec.wire_size codec in 6 - let buf = Bytes.create ws in 7 - Wire.Codec.encode codec v buf 0; 8 - Bytes.unsafe_to_string buf 9 - 10 - let decode codec s = Wire.Codec.decode codec (Bytes.of_string s) 0 11 - 12 - (* === Frame tests === *) 13 - 14 - let test_frame_roundtrip () = 15 - let frame = Msg.v TM ~apid:0x42 "hello world" in 16 - let encoded = encode Msg.codec frame in 17 - Alcotest.(check int) "frame size" 256 (String.length encoded); 18 - let decoded = decode Msg.codec encoded in 19 - Alcotest.(check bool) "roundtrip" true (Msg.equal frame decoded) 20 - 21 - let test_frame_header_layout () = 22 - let frame = Msg.v TC ~apid:0x123 "test" in 23 - let encoded = encode Msg.codec frame in 24 - (* version=0x01 at offset 0 *) 25 - Alcotest.(check int) "version" 0x01 (Char.code encoded.[0]); 26 - (* type=0x01 (TC) at offset 1 *) 27 - Alcotest.(check int) "type" 0x01 (Char.code encoded.[1]); 28 - (* apid=0x0123 big-endian at offset 2-3 *) 29 - Alcotest.(check int) "apid high" 0x01 (Char.code encoded.[2]); 30 - Alcotest.(check int) "apid low" 0x23 (Char.code encoded.[3]); 31 - (* payload_length=4 at offset 4-5 *) 32 - Alcotest.(check int) "pay_len high" 0x00 (Char.code encoded.[4]); 33 - Alcotest.(check int) "pay_len low" 0x04 (Char.code encoded.[5]); 34 - (* reserved=0 at offset 6-7 *) 35 - Alcotest.(check int) "reserved high" 0x00 (Char.code encoded.[6]); 36 - Alcotest.(check int) "reserved low" 0x00 (Char.code encoded.[7]); 37 - (* payload starts at offset 8 *) 38 - Alcotest.(check char) "payload[0]" 't' encoded.[8] 39 - 40 - let test_frame_all_types () = 41 - let types = 42 - Msg.[ TM; TC; EVR; PRM_GET; PRM_SET; PRM_RSP; DP; HEALTH; LOG; ERROR ] 43 - in 44 - List.iter 45 - (fun typ -> 46 - let frame = Msg.v typ ~apid:1 "" in 47 - let encoded = encode Msg.codec frame in 48 - let decoded = decode Msg.codec encoded in 49 - let typ_int = Msg.msg_type_to_int typ in 50 - Alcotest.(check int) 51 - (Fmt.str "type %a" Msg.pp_msg_type typ) 52 - typ_int decoded.msg_type) 53 - types 54 - 55 - let test_frame_payload_bytes () = 56 - let frame = Msg.v TM ~apid:1 "abc" in 57 - let encoded = encode Msg.codec frame in 58 - let decoded = decode Msg.codec encoded in 59 - Alcotest.(check string) "payload_bytes" "abc" (Msg.payload_bytes decoded) 60 - 61 - let test_frame_max_payload () = 62 - let big = String.make 300 'x' in 63 - let frame = Msg.v TM ~apid:1 big in 64 - Alcotest.(check int) "payload_length capped" 248 frame.payload_length 65 - 66 - (* === Error payload tests === *) 67 - 68 - let test_error_roundtrip () = 69 - let err = 70 - Error_payload.v Unknown_type ~offending_type:0xFF ~offending_apid:0x42 71 - ~offending_pay_len:248 72 - in 73 - let encoded = encode Error_payload.codec err in 74 - Alcotest.(check int) "error size" 8 (String.length encoded); 75 - let decoded = decode Error_payload.codec encoded in 76 - Alcotest.(check bool) "roundtrip" true (Error_payload.equal err decoded) 77 - 78 - let test_error_layout () = 79 - let err = 80 - Error_payload.v Host_busy ~offending_type:0x07 ~offending_apid:0x100 81 - ~offending_pay_len:0x1234 82 - in 83 - let encoded = encode Error_payload.codec err in 84 - Alcotest.(check int) "error_code" 0x05 (Char.code encoded.[0]); 85 - Alcotest.(check int) "offending_type" 0x07 (Char.code encoded.[1]); 86 - (* apid 0x0100 big-endian *) 87 - Alcotest.(check int) "apid high" 0x01 (Char.code encoded.[2]); 88 - Alcotest.(check int) "apid low" 0x00 (Char.code encoded.[3]); 89 - (* pay_len 0x1234 big-endian at offset 4-5 *) 90 - Alcotest.(check int) "pay_len high" 0x12 (Char.code encoded.[4]); 91 - Alcotest.(check int) "pay_len low" 0x34 (Char.code encoded.[5]); 92 - (* reserved = 0 at offset 6-7 *) 93 - Alcotest.(check int) "reserved high" 0x00 (Char.code encoded.[6]); 94 - Alcotest.(check int) "reserved low" 0x00 (Char.code encoded.[7]) 95 - 96 - (* === DP payload tests === *) 97 - 98 - let test_dp_roundtrip () = 99 - let dp = 100 - Dp_payload.v ~block_offset:100 ~block_count:10 ~dp_class:1 ~priority:2 101 - ~name:"science.dat" ~crc32:0xDEADBEEF 102 - in 103 - let encoded = encode Dp_payload.codec dp in 104 - Alcotest.(check int) "dp size" 80 (String.length encoded); 105 - let decoded = decode Dp_payload.codec encoded in 106 - Alcotest.(check int) "block_offset" 100 decoded.block_offset; 107 - Alcotest.(check int) "block_count" 10 decoded.block_count; 108 - Alcotest.(check int) "dp_class" 1 decoded.dp_class; 109 - Alcotest.(check int) "priority" 2 decoded.priority; 110 - Alcotest.(check string) 111 - "name_string" "science.dat" 112 - (Dp_payload.name_string decoded); 113 - Alcotest.(check int) "crc32" 0xDEADBEEF decoded.crc32 114 - 115 - (* === Superblock tests === *) 116 - 117 - let test_superblock_roundtrip () = 118 - let sb = 119 - Superblock.v ~tenant_id:1 ~total_blocks:1024 ~dp_start:33 ~dp_size:991 120 - ~epoch:1000000L ~uuid:(String.make 16 '\xAB') 121 - in 122 - let encoded = encode Superblock.codec sb in 123 - Alcotest.(check int) "superblock size" 48 (String.length encoded); 124 - let decoded = decode Superblock.codec encoded in 125 - Alcotest.(check bool) "roundtrip" true (Superblock.equal sb decoded) 126 - 127 - let test_superblock_magic () = 128 - let sb = 129 - Superblock.v ~tenant_id:1 ~total_blocks:1024 ~dp_start:33 ~dp_size:991 130 - ~epoch:0L ~uuid:(String.make 16 '\x00') 131 - in 132 - Alcotest.(check bool) "magic ok" true (Superblock.check_magic sb); 133 - let bad = { sb with magic = 0 } in 134 - Alcotest.(check bool) "magic bad" false (Superblock.check_magic bad) 135 - 136 - let test_superblock_crc () = 137 - let sb = 138 - Superblock.v ~tenant_id:1 ~total_blocks:1024 ~dp_start:33 ~dp_size:991 139 - ~epoch:0L ~uuid:(String.make 16 '\x00') 140 - in 141 - Alcotest.(check bool) "crc ok" true (Superblock.check_crc sb); 142 - let bad = { sb with tenant_id = 999 } in 143 - Alcotest.(check bool) "crc bad" false (Superblock.check_crc bad) 144 - 145 - let test_superblock_layout () = 146 - let sb = 147 - Superblock.v ~tenant_id:1 ~total_blocks:1024 ~dp_start:33 ~dp_size:991 148 - ~epoch:0L ~uuid:(String.make 16 '\x00') 149 - in 150 - let encoded = encode Superblock.codec sb in 151 - (* magic = 0x53504F53 big-endian *) 152 - Alcotest.(check int) "magic[0]" 0x53 (Char.code encoded.[0]); 153 - Alcotest.(check int) "magic[1]" 0x50 (Char.code encoded.[1]); 154 - Alcotest.(check int) "magic[2]" 0x4F (Char.code encoded.[2]); 155 - Alcotest.(check int) "magic[3]" 0x53 (Char.code encoded.[3]); 156 - (* format_version = 0x01 at offset 4 *) 157 - Alcotest.(check int) "version" 0x01 (Char.code encoded.[4]) 158 - 159 - (* === Parameter entry tests === *) 160 - 161 - let test_param_entry_roundtrip () = 162 - let p = Param_entry.v ~param_id:42 ~generation:7 "hello" in 163 - let encoded = encode Param_entry.codec p in 164 - Alcotest.(check int) "param size" 252 (String.length encoded); 165 - let decoded = decode Param_entry.codec encoded in 166 - Alcotest.(check bool) "roundtrip" true (Param_entry.equal p decoded) 167 - 168 - let test_param_entry_crc () = 169 - let p = Param_entry.v ~param_id:42 ~generation:1 "value" in 170 - Alcotest.(check bool) "crc ok" true (Param_entry.check_crc p); 171 - let bad = { p with Param_entry.param_id = 999 } in 172 - Alcotest.(check bool) "crc bad" false (Param_entry.check_crc bad) 173 - 174 - let test_param_entry_layout () = 175 - let p = Param_entry.v ~param_id:0x01020304 ~generation:3 "AB" in 176 - let encoded = encode Param_entry.codec p in 177 - (* param_id = 0x01020304 big-endian at offset 0-3 *) 178 - Alcotest.(check int) "pid[0]" 0x01 (Char.code encoded.[0]); 179 - Alcotest.(check int) "pid[1]" 0x02 (Char.code encoded.[1]); 180 - Alcotest.(check int) "pid[2]" 0x03 (Char.code encoded.[2]); 181 - Alcotest.(check int) "pid[3]" 0x04 (Char.code encoded.[3]); 182 - (* len=2 at offset 4-5 *) 183 - Alcotest.(check int) "len high" 0x00 (Char.code encoded.[4]); 184 - Alcotest.(check int) "len low" 0x02 (Char.code encoded.[5]); 185 - (* generation=3 at offset 6-7 *) 186 - Alcotest.(check int) "gen high" 0x00 (Char.code encoded.[6]); 187 - Alcotest.(check int) "gen low" 0x03 (Char.code encoded.[7]); 188 - (* value starts at offset 8 *) 189 - Alcotest.(check char) "value[0]" 'A' encoded.[8]; 190 - Alcotest.(check char) "value[1]" 'B' encoded.[9] 191 - 192 - (* === Event log tests === *) 193 - 194 - let test_event_log_roundtrip () = 195 - let ev = Event_log.v ~timestamp:1000 INFO ~event_code:0x42 "boot ok" in 196 - let encoded = encode Event_log.codec ev in 197 - Alcotest.(check int) "event size" 76 (String.length encoded); 198 - let decoded = decode Event_log.codec encoded in 199 - Alcotest.(check bool) "roundtrip" true (Event_log.equal ev decoded) 200 - 201 - let test_event_log_payload_bytes () = 202 - let ev = Event_log.v ~timestamp:0 DEBUG ~event_code:1 "hello" in 203 - let encoded = encode Event_log.codec ev in 204 - let decoded = decode Event_log.codec encoded in 205 - Alcotest.(check string) 206 - "payload_bytes" "hello" 207 - (Event_log.payload_bytes decoded) 208 - 209 - let test_event_log_layout () = 210 - let ev = Event_log.v ~timestamp:0x12345678 WARNING ~event_code:0xABCD "X" in 211 - let encoded = encode Event_log.codec ev in 212 - (* timestamp = 0x12345678 big-endian *) 213 - Alcotest.(check int) "ts[0]" 0x12 (Char.code encoded.[0]); 214 - Alcotest.(check int) "ts[1]" 0x34 (Char.code encoded.[1]); 215 - Alcotest.(check int) "ts[2]" 0x56 (Char.code encoded.[2]); 216 - Alcotest.(check int) "ts[3]" 0x78 (Char.code encoded.[3]); 217 - (* severity=2 (WARNING) at offset 4 *) 218 - Alcotest.(check int) "severity" 0x02 (Char.code encoded.[4]); 219 - (* reserved=0 at offset 5 *) 220 - Alcotest.(check int) "reserved" 0x00 (Char.code encoded.[5]); 221 - (* event_code=0xABCD at offset 6-7 *) 222 - Alcotest.(check int) "code high" 0xAB (Char.code encoded.[6]); 223 - Alcotest.(check int) "code low" 0xCD (Char.code encoded.[7]); 224 - (* payload_len=1 at offset 8-9 *) 225 - Alcotest.(check int) "plen high" 0x00 (Char.code encoded.[8]); 226 - Alcotest.(check int) "plen low" 0x01 (Char.code encoded.[9]) 227 - 228 - (* === Shared memory tests === *) 229 - 230 - let test_shared_mem_heartbeat () = 231 - let buf = Bytes.make Shared_mem.page_size '\x00' in 232 - Shared_mem.set_heartbeat buf 42L; 233 - Alcotest.(check int64) "heartbeat" 42L (Shared_mem.get_heartbeat buf) 234 - 235 - let test_shared_mem_mission_time () = 236 - let buf = Bytes.make Shared_mem.page_size '\x00' in 237 - let t = Shared_mem.{ seconds = 1000L; nanos = 500_000_000 } in 238 - Shared_mem.write_mission_time buf t; 239 - let t' = Shared_mem.read_mission_time buf in 240 - Alcotest.(check int64) "seconds" t.seconds t'.seconds; 241 - Alcotest.(check int) "nanos" t.nanos t'.nanos; 242 - (* time_version should be even after write *) 243 - let v = Shared_mem.get_time_version buf in 244 - Alcotest.(check bool) "version even" true (v land 1 = 0); 245 - Alcotest.(check bool) "version > 0" true (v > 0) 246 - 247 - let test_shared_mem_health_string () = 248 - let buf = Bytes.make Shared_mem.page_size '\x00' in 249 - Shared_mem.set_health_string buf "nominal"; 250 - Alcotest.(check string) "health" "nominal" (Shared_mem.get_health_string buf) 251 - 252 - let test_shared_mem_command_word () = 253 - let buf = Bytes.make Shared_mem.page_size '\x00' in 254 - Shared_mem.set_host_cmd buf Shared_mem.cmd_shutdown; 255 - let cmd = Shared_mem.get_host_cmd buf in 256 - Alcotest.(check bool) 257 - "shutdown set" true 258 - (cmd land Shared_mem.cmd_shutdown <> 0); 259 - Alcotest.(check bool) 260 - "param_reload clear" true 261 - (cmd land Shared_mem.cmd_param_reload = 0) 262 - 263 - (* === Test runner === *) 264 - 265 1 let () = 266 2 Alcotest.run "space-wire" 267 3 [ 268 - ( "frame", 269 - [ 270 - Alcotest.test_case "roundtrip" `Quick test_frame_roundtrip; 271 - Alcotest.test_case "header layout" `Quick test_frame_header_layout; 272 - Alcotest.test_case "all types" `Quick test_frame_all_types; 273 - Alcotest.test_case "payload_bytes" `Quick test_frame_payload_bytes; 274 - Alcotest.test_case "max payload" `Quick test_frame_max_payload; 275 - ] ); 276 - ( "error", 277 - [ 278 - Alcotest.test_case "roundtrip" `Quick test_error_roundtrip; 279 - Alcotest.test_case "layout" `Quick test_error_layout; 280 - ] ); 281 - ("dp", [ Alcotest.test_case "roundtrip" `Quick test_dp_roundtrip ]); 282 - ( "param_entry", 283 - [ 284 - Alcotest.test_case "roundtrip" `Quick test_param_entry_roundtrip; 285 - Alcotest.test_case "crc" `Quick test_param_entry_crc; 286 - Alcotest.test_case "layout" `Quick test_param_entry_layout; 287 - ] ); 288 - ( "event_log", 289 - [ 290 - Alcotest.test_case "roundtrip" `Quick test_event_log_roundtrip; 291 - Alcotest.test_case "payload_bytes" `Quick test_event_log_payload_bytes; 292 - Alcotest.test_case "layout" `Quick test_event_log_layout; 293 - ] ); 294 - ( "superblock", 295 - [ 296 - Alcotest.test_case "roundtrip" `Quick test_superblock_roundtrip; 297 - Alcotest.test_case "magic" `Quick test_superblock_magic; 298 - Alcotest.test_case "crc" `Quick test_superblock_crc; 299 - Alcotest.test_case "layout" `Quick test_superblock_layout; 300 - ] ); 301 - ( "shared_mem", 302 - [ 303 - Alcotest.test_case "heartbeat" `Quick test_shared_mem_heartbeat; 304 - Alcotest.test_case "mission time" `Quick test_shared_mem_mission_time; 305 - Alcotest.test_case "health string" `Quick 306 - test_shared_mem_health_string; 307 - Alcotest.test_case "command word" `Quick test_shared_mem_command_word; 308 - ] ); 4 + Test_msg.suite; 5 + Test_error_payload.suite; 6 + Test_dp_payload.suite; 7 + Test_param_entry.suite; 8 + Test_event_log.suite; 9 + Test_superblock.suite; 10 + Test_shared_mem.suite; 309 11 ]
+29
test/test_dp_payload.ml
··· 1 + open Space_wire 2 + 3 + let encode codec v = 4 + let ws = Wire.Codec.wire_size codec in 5 + let buf = Bytes.create ws in 6 + Wire.Codec.encode codec v buf 0; 7 + Bytes.unsafe_to_string buf 8 + 9 + let decode codec s = Wire.Codec.decode codec (Bytes.of_string s) 0 10 + 11 + let test_dp_roundtrip () = 12 + let dp = 13 + Dp_payload.v ~block_offset:100 ~block_count:10 ~dp_class:1 ~priority:2 14 + ~name:"science.dat" ~crc32:0xDEADBEEF 15 + in 16 + let encoded = encode Dp_payload.codec dp in 17 + Alcotest.(check int) "dp size" 80 (String.length encoded); 18 + let decoded = decode Dp_payload.codec encoded in 19 + Alcotest.(check int) "block_offset" 100 decoded.block_offset; 20 + Alcotest.(check int) "block_count" 10 decoded.block_count; 21 + Alcotest.(check int) "dp_class" 1 decoded.dp_class; 22 + Alcotest.(check int) "priority" 2 decoded.priority; 23 + Alcotest.(check string) 24 + "name_string" "science.dat" 25 + (Dp_payload.name_string decoded); 26 + Alcotest.(check int) "crc32" 0xDEADBEEF decoded.crc32 27 + 28 + let suite = 29 + ("dp_payload", [ Alcotest.test_case "roundtrip" `Quick test_dp_roundtrip ])
+4
test/test_dp_payload.mli
··· 1 + (** Dp_payload tests. *) 2 + 3 + val suite : string * unit Alcotest.test_case list 4 + (** Alcotest suite for {!Space_wire.Dp_payload}. *)
+44
test/test_error_payload.ml
··· 1 + open Space_wire 2 + 3 + let encode codec v = 4 + let ws = Wire.Codec.wire_size codec in 5 + let buf = Bytes.create ws in 6 + Wire.Codec.encode codec v buf 0; 7 + Bytes.unsafe_to_string buf 8 + 9 + let decode codec s = Wire.Codec.decode codec (Bytes.of_string s) 0 10 + 11 + let test_error_roundtrip () = 12 + let err = 13 + Error_payload.v Unknown_type ~offending_type:0xFF ~offending_apid:0x42 14 + ~offending_pay_len:248 15 + in 16 + let encoded = encode Error_payload.codec err in 17 + Alcotest.(check int) "error size" 8 (String.length encoded); 18 + let decoded = decode Error_payload.codec encoded in 19 + Alcotest.(check bool) "roundtrip" true (Error_payload.equal err decoded) 20 + 21 + let test_error_layout () = 22 + let err = 23 + Error_payload.v Host_busy ~offending_type:0x07 ~offending_apid:0x100 24 + ~offending_pay_len:0x1234 25 + in 26 + let encoded = encode Error_payload.codec err in 27 + Alcotest.(check int) "error_code" 0x05 (Char.code encoded.[0]); 28 + Alcotest.(check int) "offending_type" 0x07 (Char.code encoded.[1]); 29 + (* apid 0x0100 big-endian *) 30 + Alcotest.(check int) "apid high" 0x01 (Char.code encoded.[2]); 31 + Alcotest.(check int) "apid low" 0x00 (Char.code encoded.[3]); 32 + (* pay_len 0x1234 big-endian at offset 4-5 *) 33 + Alcotest.(check int) "pay_len high" 0x12 (Char.code encoded.[4]); 34 + Alcotest.(check int) "pay_len low" 0x34 (Char.code encoded.[5]); 35 + (* reserved = 0 at offset 6-7 *) 36 + Alcotest.(check int) "reserved high" 0x00 (Char.code encoded.[6]); 37 + Alcotest.(check int) "reserved low" 0x00 (Char.code encoded.[7]) 38 + 39 + let suite = 40 + ( "error_payload", 41 + [ 42 + Alcotest.test_case "roundtrip" `Quick test_error_roundtrip; 43 + Alcotest.test_case "layout" `Quick test_error_layout; 44 + ] )
+4
test/test_error_payload.mli
··· 1 + (** Error_payload tests. *) 2 + 3 + val suite : string * unit Alcotest.test_case list 4 + (** Alcotest suite for {!Space_wire.Error_payload}. *)
+51
test/test_event_log.ml
··· 1 + open Space_wire 2 + 3 + let encode codec v = 4 + let ws = Wire.Codec.wire_size codec in 5 + let buf = Bytes.create ws in 6 + Wire.Codec.encode codec v buf 0; 7 + Bytes.unsafe_to_string buf 8 + 9 + let decode codec s = Wire.Codec.decode codec (Bytes.of_string s) 0 10 + 11 + let test_event_log_roundtrip () = 12 + let ev = Event_log.v ~timestamp:1000 INFO ~event_code:0x42 "boot ok" in 13 + let encoded = encode Event_log.codec ev in 14 + Alcotest.(check int) "event size" 76 (String.length encoded); 15 + let decoded = decode Event_log.codec encoded in 16 + Alcotest.(check bool) "roundtrip" true (Event_log.equal ev decoded) 17 + 18 + let test_event_log_payload_bytes () = 19 + let ev = Event_log.v ~timestamp:0 DEBUG ~event_code:1 "hello" in 20 + let encoded = encode Event_log.codec ev in 21 + let decoded = decode Event_log.codec encoded in 22 + Alcotest.(check string) 23 + "payload_bytes" "hello" 24 + (Event_log.payload_bytes decoded) 25 + 26 + let test_event_log_layout () = 27 + let ev = Event_log.v ~timestamp:0x12345678 WARNING ~event_code:0xABCD "X" in 28 + let encoded = encode Event_log.codec ev in 29 + (* timestamp = 0x12345678 big-endian *) 30 + Alcotest.(check int) "ts[0]" 0x12 (Char.code encoded.[0]); 31 + Alcotest.(check int) "ts[1]" 0x34 (Char.code encoded.[1]); 32 + Alcotest.(check int) "ts[2]" 0x56 (Char.code encoded.[2]); 33 + Alcotest.(check int) "ts[3]" 0x78 (Char.code encoded.[3]); 34 + (* severity=2 (WARNING) at offset 4 *) 35 + Alcotest.(check int) "severity" 0x02 (Char.code encoded.[4]); 36 + (* reserved=0 at offset 5 *) 37 + Alcotest.(check int) "reserved" 0x00 (Char.code encoded.[5]); 38 + (* event_code=0xABCD at offset 6-7 *) 39 + Alcotest.(check int) "code high" 0xAB (Char.code encoded.[6]); 40 + Alcotest.(check int) "code low" 0xCD (Char.code encoded.[7]); 41 + (* payload_len=1 at offset 8-9 *) 42 + Alcotest.(check int) "plen high" 0x00 (Char.code encoded.[8]); 43 + Alcotest.(check int) "plen low" 0x01 (Char.code encoded.[9]) 44 + 45 + let suite = 46 + ( "event_log", 47 + [ 48 + Alcotest.test_case "roundtrip" `Quick test_event_log_roundtrip; 49 + Alcotest.test_case "payload_bytes" `Quick test_event_log_payload_bytes; 50 + Alcotest.test_case "layout" `Quick test_event_log_layout; 51 + ] )
+4
test/test_event_log.mli
··· 1 + (** Event_log tests. *) 2 + 3 + val suite : string * unit Alcotest.test_case list 4 + (** Alcotest suite for {!Space_wire.Event_log}. *)
+71
test/test_msg.ml
··· 1 + open Space_wire 2 + 3 + let encode codec v = 4 + let ws = Wire.Codec.wire_size codec in 5 + let buf = Bytes.create ws in 6 + Wire.Codec.encode codec v buf 0; 7 + Bytes.unsafe_to_string buf 8 + 9 + let decode codec s = Wire.Codec.decode codec (Bytes.of_string s) 0 10 + 11 + let test_frame_roundtrip () = 12 + let frame = Msg.v TM ~apid:0x42 "hello world" in 13 + let encoded = encode Msg.codec frame in 14 + Alcotest.(check int) "frame size" 256 (String.length encoded); 15 + let decoded = decode Msg.codec encoded in 16 + Alcotest.(check bool) "roundtrip" true (Msg.equal frame decoded) 17 + 18 + let test_frame_header_layout () = 19 + let frame = Msg.v TC ~apid:0x123 "test" in 20 + let encoded = encode Msg.codec frame in 21 + (* version=0x01 at offset 0 *) 22 + Alcotest.(check int) "version" 0x01 (Char.code encoded.[0]); 23 + (* type=0x01 (TC) at offset 1 *) 24 + Alcotest.(check int) "type" 0x01 (Char.code encoded.[1]); 25 + (* apid=0x0123 big-endian at offset 2-3 *) 26 + Alcotest.(check int) "apid high" 0x01 (Char.code encoded.[2]); 27 + Alcotest.(check int) "apid low" 0x23 (Char.code encoded.[3]); 28 + (* payload_length=4 at offset 4-5 *) 29 + Alcotest.(check int) "pay_len high" 0x00 (Char.code encoded.[4]); 30 + Alcotest.(check int) "pay_len low" 0x04 (Char.code encoded.[5]); 31 + (* reserved=0 at offset 6-7 *) 32 + Alcotest.(check int) "reserved high" 0x00 (Char.code encoded.[6]); 33 + Alcotest.(check int) "reserved low" 0x00 (Char.code encoded.[7]); 34 + (* payload starts at offset 8 *) 35 + Alcotest.(check char) "payload[0]" 't' encoded.[8] 36 + 37 + let test_frame_all_types () = 38 + let types = 39 + Msg.[ TM; TC; EVR; PRM_GET; PRM_SET; PRM_RSP; DP; HEALTH; LOG; ERROR ] 40 + in 41 + List.iter 42 + (fun typ -> 43 + let frame = Msg.v typ ~apid:1 "" in 44 + let encoded = encode Msg.codec frame in 45 + let decoded = decode Msg.codec encoded in 46 + let typ_int = Msg.kind_to_int typ in 47 + Alcotest.(check int) 48 + (Fmt.str "type %a" Msg.pp_kind typ) 49 + typ_int decoded.msg_type) 50 + types 51 + 52 + let test_frame_payload_bytes () = 53 + let frame = Msg.v TM ~apid:1 "abc" in 54 + let encoded = encode Msg.codec frame in 55 + let decoded = decode Msg.codec encoded in 56 + Alcotest.(check string) "payload_bytes" "abc" (Msg.payload_bytes decoded) 57 + 58 + let test_frame_max_payload () = 59 + let big = String.make 300 'x' in 60 + let frame = Msg.v TM ~apid:1 big in 61 + Alcotest.(check int) "payload_length capped" 248 frame.payload_length 62 + 63 + let suite = 64 + ( "msg", 65 + [ 66 + Alcotest.test_case "roundtrip" `Quick test_frame_roundtrip; 67 + Alcotest.test_case "header layout" `Quick test_frame_header_layout; 68 + Alcotest.test_case "all types" `Quick test_frame_all_types; 69 + Alcotest.test_case "payload_bytes" `Quick test_frame_payload_bytes; 70 + Alcotest.test_case "max payload" `Quick test_frame_max_payload; 71 + ] )
+4
test/test_msg.mli
··· 1 + (** Msg tests. *) 2 + 3 + val suite : string * unit Alcotest.test_case list 4 + (** Alcotest suite for {!Space_wire.Msg}. *)
+48
test/test_param_entry.ml
··· 1 + open Space_wire 2 + 3 + let encode codec v = 4 + let ws = Wire.Codec.wire_size codec in 5 + let buf = Bytes.create ws in 6 + Wire.Codec.encode codec v buf 0; 7 + Bytes.unsafe_to_string buf 8 + 9 + let decode codec s = Wire.Codec.decode codec (Bytes.of_string s) 0 10 + 11 + let test_param_entry_roundtrip () = 12 + let p = Param_entry.v ~param_id:42 ~generation:7 "hello" in 13 + let encoded = encode Param_entry.codec p in 14 + Alcotest.(check int) "param size" 252 (String.length encoded); 15 + let decoded = decode Param_entry.codec encoded in 16 + Alcotest.(check bool) "roundtrip" true (Param_entry.equal p decoded) 17 + 18 + let test_param_entry_crc () = 19 + let p = Param_entry.v ~param_id:42 ~generation:1 "value" in 20 + Alcotest.(check bool) "crc ok" true (Param_entry.check_crc p); 21 + let bad = { p with Param_entry.param_id = 999 } in 22 + Alcotest.(check bool) "crc bad" false (Param_entry.check_crc bad) 23 + 24 + let test_param_entry_layout () = 25 + let p = Param_entry.v ~param_id:0x01020304 ~generation:3 "AB" in 26 + let encoded = encode Param_entry.codec p in 27 + (* param_id = 0x01020304 big-endian at offset 0-3 *) 28 + Alcotest.(check int) "pid[0]" 0x01 (Char.code encoded.[0]); 29 + Alcotest.(check int) "pid[1]" 0x02 (Char.code encoded.[1]); 30 + Alcotest.(check int) "pid[2]" 0x03 (Char.code encoded.[2]); 31 + Alcotest.(check int) "pid[3]" 0x04 (Char.code encoded.[3]); 32 + (* len=2 at offset 4-5 *) 33 + Alcotest.(check int) "len high" 0x00 (Char.code encoded.[4]); 34 + Alcotest.(check int) "len low" 0x02 (Char.code encoded.[5]); 35 + (* generation=3 at offset 6-7 *) 36 + Alcotest.(check int) "gen high" 0x00 (Char.code encoded.[6]); 37 + Alcotest.(check int) "gen low" 0x03 (Char.code encoded.[7]); 38 + (* value starts at offset 8 *) 39 + Alcotest.(check char) "value[0]" 'A' encoded.[8]; 40 + Alcotest.(check char) "value[1]" 'B' encoded.[9] 41 + 42 + let suite = 43 + ( "param_entry", 44 + [ 45 + Alcotest.test_case "roundtrip" `Quick test_param_entry_roundtrip; 46 + Alcotest.test_case "crc" `Quick test_param_entry_crc; 47 + Alcotest.test_case "layout" `Quick test_param_entry_layout; 48 + ] )
+4
test/test_param_entry.mli
··· 1 + (** Param_entry tests. *) 2 + 3 + val suite : string * unit Alcotest.test_case list 4 + (** Alcotest suite for {!Space_wire.Param_entry}. *)
+43
test/test_shared_mem.ml
··· 1 + open Space_wire 2 + 3 + let test_shared_mem_heartbeat () = 4 + let buf = Bytes.make Shared_mem.page_size '\x00' in 5 + Shared_mem.set_heartbeat buf 42L; 6 + Alcotest.(check int64) "heartbeat" 42L (Shared_mem.heartbeat buf) 7 + 8 + let test_shared_mem_mission_time () = 9 + let buf = Bytes.make Shared_mem.page_size '\x00' in 10 + let t = Shared_mem.{ seconds = 1000L; nanos = 500_000_000 } in 11 + Shared_mem.write_mission_time buf t; 12 + let t' = Shared_mem.read_mission_time buf in 13 + Alcotest.(check int64) "seconds" t.seconds t'.seconds; 14 + Alcotest.(check int) "nanos" t.nanos t'.nanos; 15 + (* time_version should be even after write *) 16 + let v = Shared_mem.time_version buf in 17 + Alcotest.(check bool) "version even" true (v land 1 = 0); 18 + Alcotest.(check bool) "version > 0" true (v > 0) 19 + 20 + let test_shared_mem_health_string () = 21 + let buf = Bytes.make Shared_mem.page_size '\x00' in 22 + Shared_mem.set_health_string buf "nominal"; 23 + Alcotest.(check string) "health" "nominal" (Shared_mem.health_string buf) 24 + 25 + let test_shared_mem_command_word () = 26 + let buf = Bytes.make Shared_mem.page_size '\x00' in 27 + Shared_mem.set_host_cmd buf Shared_mem.cmd_shutdown; 28 + let cmd = Shared_mem.host_cmd buf in 29 + Alcotest.(check bool) 30 + "shutdown set" true 31 + (cmd land Shared_mem.cmd_shutdown <> 0); 32 + Alcotest.(check bool) 33 + "param_reload clear" true 34 + (cmd land Shared_mem.cmd_param_reload = 0) 35 + 36 + let suite = 37 + ( "shared_mem", 38 + [ 39 + Alcotest.test_case "heartbeat" `Quick test_shared_mem_heartbeat; 40 + Alcotest.test_case "mission time" `Quick test_shared_mem_mission_time; 41 + Alcotest.test_case "health string" `Quick test_shared_mem_health_string; 42 + Alcotest.test_case "command word" `Quick test_shared_mem_command_word; 43 + ] )
+4
test/test_shared_mem.mli
··· 1 + (** Shared_mem tests. *) 2 + 3 + val suite : string * unit Alcotest.test_case list 4 + (** Alcotest suite for {!Space_wire.Shared_mem}. *)
+60
test/test_superblock.ml
··· 1 + open Space_wire 2 + 3 + let encode codec v = 4 + let ws = Wire.Codec.wire_size codec in 5 + let buf = Bytes.create ws in 6 + Wire.Codec.encode codec v buf 0; 7 + Bytes.unsafe_to_string buf 8 + 9 + let decode codec s = Wire.Codec.decode codec (Bytes.of_string s) 0 10 + 11 + let test_superblock_roundtrip () = 12 + let sb = 13 + Superblock.v ~tenant_id:1 ~total_blocks:1024 ~dp_start:33 ~dp_size:991 14 + ~epoch:1000000L ~uuid:(String.make 16 '\xAB') 15 + in 16 + let encoded = encode Superblock.codec sb in 17 + Alcotest.(check int) "superblock size" 48 (String.length encoded); 18 + let decoded = decode Superblock.codec encoded in 19 + Alcotest.(check bool) "roundtrip" true (Superblock.equal sb decoded) 20 + 21 + let test_superblock_magic () = 22 + let sb = 23 + Superblock.v ~tenant_id:1 ~total_blocks:1024 ~dp_start:33 ~dp_size:991 24 + ~epoch:0L ~uuid:(String.make 16 '\x00') 25 + in 26 + Alcotest.(check bool) "magic ok" true (Superblock.check_magic sb); 27 + let bad = { sb with magic = 0 } in 28 + Alcotest.(check bool) "magic bad" false (Superblock.check_magic bad) 29 + 30 + let test_superblock_crc () = 31 + let sb = 32 + Superblock.v ~tenant_id:1 ~total_blocks:1024 ~dp_start:33 ~dp_size:991 33 + ~epoch:0L ~uuid:(String.make 16 '\x00') 34 + in 35 + Alcotest.(check bool) "crc ok" true (Superblock.check_crc sb); 36 + let bad = { sb with tenant_id = 999 } in 37 + Alcotest.(check bool) "crc bad" false (Superblock.check_crc bad) 38 + 39 + let test_superblock_layout () = 40 + let sb = 41 + Superblock.v ~tenant_id:1 ~total_blocks:1024 ~dp_start:33 ~dp_size:991 42 + ~epoch:0L ~uuid:(String.make 16 '\x00') 43 + in 44 + let encoded = encode Superblock.codec sb in 45 + (* magic = 0x53504F53 big-endian *) 46 + Alcotest.(check int) "magic[0]" 0x53 (Char.code encoded.[0]); 47 + Alcotest.(check int) "magic[1]" 0x50 (Char.code encoded.[1]); 48 + Alcotest.(check int) "magic[2]" 0x4F (Char.code encoded.[2]); 49 + Alcotest.(check int) "magic[3]" 0x53 (Char.code encoded.[3]); 50 + (* format_version = 0x01 at offset 4 *) 51 + Alcotest.(check int) "version" 0x01 (Char.code encoded.[4]) 52 + 53 + let suite = 54 + ( "superblock", 55 + [ 56 + Alcotest.test_case "roundtrip" `Quick test_superblock_roundtrip; 57 + Alcotest.test_case "magic" `Quick test_superblock_magic; 58 + Alcotest.test_case "crc" `Quick test_superblock_crc; 59 + Alcotest.test_case "layout" `Quick test_superblock_layout; 60 + ] )
+4
test/test_superblock.mli
··· 1 + (** Superblock tests. *) 2 + 3 + val suite : string * unit Alcotest.test_case list 4 + (** Alcotest suite for {!Space_wire.Superblock}. *)