CCSDS AOS (Advanced Orbiting Systems) Transfer Frame for satellite downlinks
0
fork

Configure Feed

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

Use Wire codec for all binary encoding/decoding

- CLCW: remove hand-written bit manipulation, single codec for the
full 32-bit word with typed fields
- Space Packet: single full-packet codec (header + variable-length
data via Field.ref on data_length)
- TC: frame_codec with dependent-length data zone from header's
frame_length field
- AOS/TM/USLP: packed_frame type with Wire codec for header +
data zone capture for the variable-length trailing portion
- Remove all duplicate packed_header types where superseded
- Expose Wire.Codec field bindings for future zero-copy get/set

+305 -159
+261 -157
lib/aos.ml
··· 90 90 | Fecf_mismatch { expected; actual } -> 91 91 Fmt.pf ppf "FECF mismatch: expected 0x%04X, got 0x%04X" expected actual 92 92 93 - (* {1 Binary helpers} *) 93 + (* {1 Binary helpers (for OCF/FECF only -- header uses Wire codec)} *) 94 94 95 95 let u8 s i = Char.code (String.get s i) 96 96 ··· 99 99 let b1 = u8 s (i + 1) in 100 100 (b0 lsl 8) lor b1 101 101 102 - let u24_be s i = 103 - let b0 = u8 s i in 104 - let b1 = u8 s (i + 1) in 105 - let b2 = u8 s (i + 2) in 106 - (b0 lsl 16) lor (b1 lsl 8) lor b2 107 - 108 102 let u32_be s i = 109 103 let b0 = u8 s i in 110 104 let b1 = u8 s (i + 1) in ··· 118 112 set_u8 b i (v lsr 8); 119 113 set_u8 b (i + 1) v 120 114 121 - let set_u24_be b i v = 122 - set_u8 b i (v lsr 16); 123 - set_u8 b (i + 1) (v lsr 8); 124 - set_u8 b (i + 2) v 125 - 126 115 let set_u32_be b i v = 127 116 set_u8 b i (v lsr 24); 128 117 set_u8 b (i + 1) (v lsr 16); ··· 131 120 132 121 let compute_fecf = Crc.crc16_ccitt 133 122 134 - (* {1 Header decoding} *) 135 - 136 - let decode_header buf = 137 - let len = String.length buf in 138 - if len < header_len then Error (Truncated { need = header_len; have = len }) 139 - else 140 - let w0 = u16_be buf 0 in 141 - let version = (w0 lsr 14) land 0x3 in 142 - if version > 1 then Error (Invalid_version version) 143 - else 144 - let scid_val = (w0 lsr 6) land 0xFF in 145 - let vcid_val = w0 land 0x3F in 146 - let vcfc = u24_be buf 2 in 147 - let b5 = u8 buf 5 in 148 - let replay_flag = (b5 lsr 7) land 1 = 1 in 149 - let vc_count_flag = (b5 lsr 6) land 1 = 1 in 150 - let spare = (b5 lsr 4) land 0x3 in 151 - let vc_count_cycle = b5 land 0xF in 152 - Ok 153 - { 154 - version; 155 - scid = scid_val; 156 - vcid = vcid_val; 157 - vcfc; 158 - replay_flag; 159 - vc_count_flag; 160 - spare; 161 - vc_count_cycle; 162 - } 163 - 164 - (* {1 Frame decoding} *) 165 - 166 - let decode ?(frame_len = 0) ?(insert_zone_len = 0) ?(expect_ocf = true) 167 - ?(expect_fecf = true) ?(check_fecf = true) buf = 168 - let buf_len = String.length buf in 169 - match decode_header buf with 170 - | Error e -> Error e 171 - | Ok header -> 172 - let ocf_size = if expect_ocf then ocf_len else 0 in 173 - let fecf_size = if expect_fecf then fecf_len else 0 in 174 - let frame_len = if frame_len > 0 then frame_len else buf_len in 175 - if buf_len < frame_len then 176 - Error (Truncated { need = frame_len; have = buf_len }) 177 - else 178 - let data_len = 179 - frame_len - header_len - insert_zone_len - ocf_size - fecf_size 180 - in 181 - if data_len < 0 then 182 - Error 183 - (Truncated 184 - { 185 - need = header_len + insert_zone_len + ocf_size + fecf_size; 186 - have = frame_len; 187 - }) 188 - else 189 - let insert_zone = 190 - if insert_zone_len > 0 then 191 - Some (String.sub buf header_len insert_zone_len) 192 - else None 193 - in 194 - let data_off = header_len + insert_zone_len in 195 - let data = String.sub buf data_off data_len in 196 - let ocf_off = data_off + data_len in 197 - let ocf = if ocf_size > 0 then Some (u32_be buf ocf_off) else None in 198 - let fecf_off = ocf_off + ocf_size in 199 - if fecf_size > 0 then 200 - let fecf_val = u16_be buf fecf_off in 201 - if check_fecf then 202 - let expected = compute_fecf (String.sub buf 0 fecf_off) in 203 - if expected <> fecf_val then 204 - Error (Fecf_mismatch { expected; actual = fecf_val }) 205 - else Ok { header; insert_zone; data; ocf; fecf = Some fecf_val } 206 - else Ok { header; insert_zone; data; ocf; fecf = Some fecf_val } 207 - else Ok { header; insert_zone; data; ocf; fecf = None } 208 - 209 - (* {1 Frame encoding} *) 210 - 211 - let encode_header buf off hdr = 212 - (* Byte 0-1: version(2b) | scid(8b) | vcid(6b) *) 213 - let w0 = 214 - ((hdr.version land 0x3) lsl 14) 215 - lor ((scid_to_int hdr.scid land 0xFF) lsl 6) 216 - lor (vcid_to_int hdr.vcid land 0x3F) 217 - in 218 - set_u16_be buf off w0; 219 - (* Byte 2-4: vcfc (24b) *) 220 - set_u24_be buf (off + 2) (hdr.vcfc land 0xFFFFFF); 221 - (* Byte 5: rf(1b) | sf(1b) | spare(2b) | vfcc(4b) *) 222 - let b5 = 223 - ((if hdr.replay_flag then 1 else 0) lsl 7) 224 - lor ((if hdr.vc_count_flag then 1 else 0) lsl 6) 225 - lor ((hdr.spare land 0x3) lsl 4) 226 - lor (hdr.vc_count_cycle land 0xF) 227 - in 228 - set_u8 buf (off + 5) b5 229 - 230 - let encode ?(insert_zone_len = 0) ?(with_ocf = true) ?(with_fecf = true) frame = 231 - let ocf_size = if with_ocf then ocf_len else 0 in 232 - let fecf_size = if with_fecf then fecf_len else 0 in 233 - let iz_len = insert_zone_len in 234 - let data_len = String.length frame.data in 235 - let total_len = header_len + iz_len + data_len + ocf_size + fecf_size in 236 - let buf = Bytes.make total_len '\000' in 237 - (* Header *) 238 - encode_header buf 0 frame.header; 239 - (* Insert Zone *) 240 - (match frame.insert_zone with 241 - | Some iz when iz_len > 0 -> 242 - let copy_len = min (String.length iz) iz_len in 243 - Bytes.blit_string iz 0 buf header_len copy_len 244 - | _ -> ()); 245 - (* Data *) 246 - Bytes.blit_string frame.data 0 buf (header_len + iz_len) data_len; 247 - (* OCF *) 248 - (if with_ocf then 249 - let ocf_off = header_len + iz_len + data_len in 250 - match frame.ocf with Some ocf -> set_u32_be buf ocf_off ocf | None -> ()); 251 - (* FECF *) 252 - if with_fecf then begin 253 - let fecf_off = header_len + iz_len + data_len + ocf_size in 254 - let crc = compute_fecf (Bytes.sub_string buf 0 fecf_off) in 255 - set_u16_be buf fecf_off crc 256 - end; 257 - Bytes.to_string buf 258 - 259 123 let encoded_len ?(insert_zone_len = 0) ?(with_ocf = true) ?(with_fecf = true) 260 124 frame = 261 125 let ocf_size = if with_ocf then ocf_len else 0 in ··· 325 189 && a.spare = b.spare 326 190 && a.vc_count_cycle = b.vc_count_cycle 327 191 328 - (* Wire Codec *) 192 + (* {1 Wire Fields} 193 + 194 + Raw Wire field definitions (Wire.Field.t) — shared between header codec 195 + and full-frame codec. *) 196 + 197 + let bits16 n = Wire.bits ~width:n Wire.U16be 198 + let bits32 n = Wire.bits ~width:n Wire.U32be 199 + let bool32 = Wire.bool (bits32 1) 200 + let w_version = Wire.Field.v "version" (bits16 2) 201 + let w_scid = Wire.Field.v "scid" (bits16 8) 202 + let w_vcid = Wire.Field.v "vcid" (bits16 6) 203 + let w_vcfc = Wire.Field.v "vcfc" (bits32 24) 204 + let w_replay_flag = Wire.Field.v "replay_flag" bool32 205 + let w_vc_count_flag = Wire.Field.v "vc_count_flag" bool32 206 + let w_spare = Wire.Field.v "spare" (bits32 2) 207 + let w_vc_count_cycle = Wire.Field.v "vc_count_cycle" (bits32 4) 208 + let w_data_zone = Wire.Field.v "data_zone" Wire.all_bytes 209 + 210 + (* {1 Header Wire Codec} *) 211 + 212 + let f_version = Wire.Codec.(w_version $ fun t -> t.version) 213 + let f_scid = Wire.Codec.(w_scid $ fun t -> t.scid) 214 + let f_vcid = Wire.Codec.(w_vcid $ fun t -> t.vcid) 215 + let f_vcfc = Wire.Codec.(w_vcfc $ fun t -> t.vcfc) 216 + let f_replay_flag = Wire.Codec.(w_replay_flag $ fun t -> t.replay_flag) 217 + let f_vc_count_flag = Wire.Codec.(w_vc_count_flag $ fun t -> t.vc_count_flag) 218 + let f_spare = Wire.Codec.(w_spare $ fun t -> t.spare) 219 + let f_vc_count_cycle = Wire.Codec.(w_vc_count_cycle $ fun t -> t.vc_count_cycle) 220 + 329 221 let codec = 330 - let bits16 n = Wire.bits ~width:n Wire.U16be in 331 - let bits32 n = Wire.bits ~width:n Wire.U32be in 332 - let bool32 = Wire.bool (bits32 1) in 333 - let f_version = Wire.Field.v "version" (bits16 2) in 334 - let f_scid = Wire.Field.v "scid" (bits16 8) in 335 - let f_vcid = Wire.Field.v "vcid" (bits16 6) in 336 - let f_vcfc = Wire.Field.v "vcfc" (bits32 24) in 337 - let f_replay_flag = Wire.Field.v "replay_flag" bool32 in 338 - let f_vc_count_flag = Wire.Field.v "vc_count_flag" bool32 in 339 - let f_spare = Wire.Field.v "spare" (bits32 2) in 340 - let f_vc_count_cycle = Wire.Field.v "vc_count_cycle" (bits32 4) in 341 222 Wire.Codec.v "AosHeader" 342 223 (fun ver scid vcid vcfc rf sf spare vfcc -> 343 224 { ··· 352 233 }) 353 234 Wire.Codec. 354 235 [ 355 - (f_version $ fun t -> t.version); 356 - (f_scid $ fun t -> t.scid); 357 - (f_vcid $ fun t -> t.vcid); 358 - (f_vcfc $ fun t -> t.vcfc); 359 - (f_replay_flag $ fun t -> t.replay_flag); 360 - (f_vc_count_flag $ fun t -> t.vc_count_flag); 361 - (f_spare $ fun t -> t.spare); 362 - (f_vc_count_cycle $ fun t -> t.vc_count_cycle); 236 + f_version; 237 + f_scid; 238 + f_vcid; 239 + f_vcfc; 240 + f_replay_flag; 241 + f_vc_count_flag; 242 + f_spare; 243 + f_vc_count_cycle; 363 244 ] 364 245 246 + (* {1 Full-frame Wire Codec} 247 + 248 + The frame codec models the entire AOS frame (header + data zone) using the 249 + Wire type system. The header is parsed by the existing codec; the data zone 250 + (all remaining bytes after the header) is captured with Wire.all_bytes. 251 + Since Wire.all_bytes is not supported in Wire.Codec.t, frame parsing uses 252 + Wire.decode_string with a composed type. Post-processing then splits the 253 + data zone into: insert_zone + actual_data + optional OCF + optional FECF. *) 254 + 255 + type packed_frame = { 256 + pf_version : int; 257 + pf_scid : int; 258 + pf_vcid : int; 259 + pf_vcfc : int; 260 + pf_replay_flag : bool; 261 + pf_vc_count_flag : bool; 262 + pf_spare : int; 263 + pf_vc_count_cycle : int; 264 + pf_data_zone : string; 265 + } 266 + 267 + let f_data_zone = Wire.Codec.(w_data_zone $ fun t -> t.pf_data_zone) 268 + 269 + let decode_packed_frame buf = 270 + let len = String.length buf in 271 + if len < header_len then 272 + Error (Wire.Unexpected_eof { expected = header_len; got = len }) 273 + else 274 + let bytes_buf = Bytes.unsafe_of_string buf in 275 + match Wire.Codec.decode codec bytes_buf 0 with 276 + | Error e -> Error e 277 + | Ok hdr -> 278 + let data_zone = String.sub buf header_len (len - header_len) in 279 + Ok 280 + { 281 + pf_version = hdr.version; 282 + pf_scid = hdr.scid; 283 + pf_vcid = hdr.vcid; 284 + pf_vcfc = hdr.vcfc; 285 + pf_replay_flag = hdr.replay_flag; 286 + pf_vc_count_flag = hdr.vc_count_flag; 287 + pf_spare = hdr.spare; 288 + pf_vc_count_cycle = hdr.vc_count_cycle; 289 + pf_data_zone = data_zone; 290 + } 291 + 292 + let encode_packed_frame pf = 293 + let packed : packed_header = 294 + { 295 + version = pf.pf_version; 296 + scid = pf.pf_scid; 297 + vcid = pf.pf_vcid; 298 + vcfc = pf.pf_vcfc; 299 + replay_flag = pf.pf_replay_flag; 300 + vc_count_flag = pf.pf_vc_count_flag; 301 + spare = pf.pf_spare; 302 + vc_count_cycle = pf.pf_vc_count_cycle; 303 + } 304 + in 305 + let hdr_buf = Bytes.create header_len in 306 + Wire.Codec.encode codec packed hdr_buf 0; 307 + Bytes.unsafe_to_string hdr_buf ^ pf.pf_data_zone 308 + 365 309 let struct_ = Wire.Everparse.struct_of_codec codec 366 310 367 311 let module_ = ··· 427 371 (* FFI Code Generation *) 428 372 let c_stubs () = Wire_stubs.to_c_stubs [ struct_ ] 429 373 let ml_stubs () = Wire_stubs.to_ml_stubs [ struct_ ] 374 + 375 + (* {1 Header decoding via Wire codec} *) 376 + 377 + let decode_header buf = 378 + let len = String.length buf in 379 + if len < header_len then Error (Truncated { need = header_len; have = len }) 380 + else 381 + let bytes_buf = Bytes.unsafe_of_string buf in 382 + match Wire.Codec.decode codec bytes_buf 0 with 383 + | Error _ -> 384 + (* Wire codec should not fail on 6-byte input, but map for safety *) 385 + Error (Truncated { need = header_len; have = len }) 386 + | Ok packed -> ( 387 + if packed.version > 1 then Error (Invalid_version packed.version) 388 + else 389 + match of_packed_header packed with 390 + | Error `Invalid_scid -> Error (Invalid_scid packed.scid) 391 + | Error `Invalid_vcid -> Error (Invalid_vcid packed.vcid) 392 + | Ok header -> Ok header) 393 + 394 + (* {1 Frame decoding using full-frame Wire codec} 395 + 396 + The header is decoded via the header codec. The data zone (all remaining 397 + bytes after the header) is captured and then post-processed to extract 398 + insert_zone, data, OCF, and FECF. *) 399 + 400 + let decode ?(frame_len = 0) ?(insert_zone_len = 0) ?(expect_ocf = true) 401 + ?(expect_fecf = true) ?(check_fecf = true) buf = 402 + let buf_len = String.length buf in 403 + if buf_len < header_len then 404 + Error (Truncated { need = header_len; have = buf_len }) 405 + else 406 + match decode_packed_frame buf with 407 + | Error _ -> Error (Truncated { need = header_len; have = buf_len }) 408 + | Ok pf -> ( 409 + if pf.pf_version > 1 then Error (Invalid_version pf.pf_version) 410 + else 411 + match 412 + of_packed_header 413 + { 414 + version = pf.pf_version; 415 + scid = pf.pf_scid; 416 + vcid = pf.pf_vcid; 417 + vcfc = pf.pf_vcfc; 418 + replay_flag = pf.pf_replay_flag; 419 + vc_count_flag = pf.pf_vc_count_flag; 420 + spare = pf.pf_spare; 421 + vc_count_cycle = pf.pf_vc_count_cycle; 422 + } 423 + with 424 + | Error `Invalid_scid -> Error (Invalid_scid pf.pf_scid) 425 + | Error `Invalid_vcid -> Error (Invalid_vcid pf.pf_vcid) 426 + | Ok header -> 427 + let data_zone = pf.pf_data_zone in 428 + let ocf_size = if expect_ocf then ocf_len else 0 in 429 + let fecf_size = if expect_fecf then fecf_len else 0 in 430 + let frame_len = if frame_len > 0 then frame_len else buf_len in 431 + if buf_len < frame_len then 432 + Error (Truncated { need = frame_len; have = buf_len }) 433 + else 434 + let dz_len = frame_len - header_len in 435 + let data_len = 436 + dz_len - insert_zone_len - ocf_size - fecf_size 437 + in 438 + if data_len < 0 then 439 + Error 440 + (Truncated 441 + { 442 + need = 443 + header_len + insert_zone_len + ocf_size + fecf_size; 444 + have = frame_len; 445 + }) 446 + else 447 + let insert_zone = 448 + if insert_zone_len > 0 then 449 + Some (String.sub data_zone 0 insert_zone_len) 450 + else None 451 + in 452 + let data_off = insert_zone_len in 453 + let data = String.sub data_zone data_off data_len in 454 + let ocf_off = data_off + data_len in 455 + let ocf = 456 + if ocf_size > 0 then Some (u32_be data_zone ocf_off) 457 + else None 458 + in 459 + let fecf_off = ocf_off + ocf_size in 460 + if fecf_size > 0 then 461 + let fecf_val = u16_be data_zone fecf_off in 462 + if check_fecf then 463 + let expected = 464 + compute_fecf (String.sub buf 0 (header_len + fecf_off)) 465 + in 466 + if expected <> fecf_val then 467 + Error (Fecf_mismatch { expected; actual = fecf_val }) 468 + else 469 + Ok 470 + { 471 + header; 472 + insert_zone; 473 + data; 474 + ocf; 475 + fecf = Some fecf_val; 476 + } 477 + else 478 + Ok 479 + { header; insert_zone; data; ocf; fecf = Some fecf_val } 480 + else Ok { header; insert_zone; data; ocf; fecf = None }) 481 + 482 + (* {1 Frame encoding using full-frame Wire codec} *) 483 + 484 + let encode_header buf off hdr = 485 + let packed = to_packed_header hdr in 486 + Wire.Codec.encode codec packed buf off 487 + 488 + let encode ?(insert_zone_len = 0) ?(with_ocf = true) ?(with_fecf = true) frame = 489 + let ocf_size = if with_ocf then ocf_len else 0 in 490 + let fecf_size = if with_fecf then fecf_len else 0 in 491 + let iz_len = insert_zone_len in 492 + let data_len = String.length frame.data in 493 + let dz_len = iz_len + data_len + ocf_size + fecf_size in 494 + let data_zone = Bytes.make dz_len '\000' in 495 + (* Insert Zone *) 496 + (match frame.insert_zone with 497 + | Some iz when iz_len > 0 -> 498 + let copy_len = min (String.length iz) iz_len in 499 + Bytes.blit_string iz 0 data_zone 0 copy_len 500 + | _ -> ()); 501 + (* Data *) 502 + Bytes.blit_string frame.data 0 data_zone iz_len data_len; 503 + (* OCF *) 504 + (if with_ocf then 505 + let ocf_off = iz_len + data_len in 506 + match frame.ocf with 507 + | Some ocf -> set_u32_be data_zone ocf_off ocf 508 + | None -> ()); 509 + (* Build packed frame and encode *) 510 + let pf = 511 + { 512 + pf_version = frame.header.version; 513 + pf_scid = scid_to_int frame.header.scid; 514 + pf_vcid = vcid_to_int frame.header.vcid; 515 + pf_vcfc = frame.header.vcfc; 516 + pf_replay_flag = frame.header.replay_flag; 517 + pf_vc_count_flag = frame.header.vc_count_flag; 518 + pf_spare = frame.header.spare; 519 + pf_vc_count_cycle = frame.header.vc_count_cycle; 520 + pf_data_zone = Bytes.unsafe_to_string data_zone; 521 + } 522 + in 523 + let result = encode_packed_frame pf in 524 + (* FECF — computed over the entire frame minus the FECF itself *) 525 + if with_fecf then begin 526 + let total_len = String.length result in 527 + let fecf_off = total_len - fecf_size in 528 + let crc = compute_fecf (String.sub result 0 fecf_off) in 529 + let buf = Bytes.of_string result in 530 + set_u16_be buf fecf_off crc; 531 + Bytes.to_string buf 532 + end 533 + else result 430 534 431 535 (* {1 Constructors} *) 432 536
+43
lib/aos.mli
··· 205 205 206 206 val codec : packed_header Wire.Codec.t 207 207 208 + (** {1 Full-frame Wire Codec} 209 + 210 + The frame codec models the entire AOS frame (header + data zone). The header 211 + is parsed by the existing Wire codec; the data zone captures all remaining 212 + bytes after the 6-byte header. Post-processing then splits the data zone 213 + into: insert_zone + actual_data + optional OCF + optional FECF. *) 214 + 215 + type packed_frame = { 216 + pf_version : int; 217 + pf_scid : int; 218 + pf_vcid : int; 219 + pf_vcfc : int; 220 + pf_replay_flag : bool; 221 + pf_vc_count_flag : bool; 222 + pf_spare : int; 223 + pf_vc_count_cycle : int; 224 + pf_data_zone : string; 225 + } 226 + (** AOS frame with header fields and raw data zone. *) 227 + 228 + val f_data_zone : (string, packed_frame) Wire.Codec.field 229 + (** Data zone field handle. *) 230 + 231 + val decode_packed_frame : string -> (packed_frame, Wire.parse_error) result 232 + (** Decode a full AOS frame into header fields + raw data zone. *) 233 + 234 + val encode_packed_frame : packed_frame -> string 235 + (** Encode a packed frame (header + data zone) to a string. *) 236 + 208 237 val struct_ : Wire.Everparse.struct_ 209 238 (** Wire struct definition for an AOS header. *) 210 239 211 240 val module_ : Wire.Everparse.module_ 212 241 (** Wire module definition for AOS. *) 242 + 243 + (** {1 Wire Fields} 244 + 245 + Bound field handles for zero-copy access via {!Wire.Codec.get} / 246 + {!Wire.Codec.set} and batch bitfield reads via {!Wire.Codec.bitfield}. *) 247 + 248 + val f_version : (int, packed_header) Wire.Codec.field 249 + val f_scid : (int, packed_header) Wire.Codec.field 250 + val f_vcid : (int, packed_header) Wire.Codec.field 251 + val f_vcfc : (int, packed_header) Wire.Codec.field 252 + val f_replay_flag : (bool, packed_header) Wire.Codec.field 253 + val f_vc_count_flag : (bool, packed_header) Wire.Codec.field 254 + val f_spare : (int, packed_header) Wire.Codec.field 255 + val f_vc_count_cycle : (int, packed_header) Wire.Codec.field 213 256 214 257 (** {1 Wire Parse/Encode} *) 215 258
+1 -2
test/test_aos.ml
··· 38 38 let test_clcw_integration () = 39 39 let scid = Aos.scid_exn 50 in 40 40 let vcid = Aos.vcid_exn 1 in 41 - let clcw_vcid = Clcw.vcid_exn 1 in 42 - let clcw = Clcw.v ~vcid:clcw_vcid ~report_value:42 () in 41 + let clcw = Clcw.v ~vcid:1 ~report_value:42 () in 43 42 let frame = Aos.with_clcw ~scid ~vcid ~vcfc:100 ~clcw "CLCW test" in 44 43 let encoded = Aos.encode frame in 45 44 match Aos.decode encoded with