CCSDS 133.0-B Space Packet Protocol for OCaml
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

+213 -223
+48 -47
bench/bench_space_packet.ml
··· 1 - (** Benchmark comparing raw byte operations vs Wire Codec for space packet 2 - header. 1 + (** Benchmark comparing raw byte operations vs Wire Codec for space packet. 3 2 4 - The original Space_packet module operates on full packets (header + data), 5 - not just headers. So we compare Wire Codec against a raw-bytes baseline. *) 3 + Compares Wire Codec against a raw-bytes baseline for encoding and decoding 4 + space packet headers. *) 6 5 7 6 (** {1 Test Data} *) 8 7 9 - let test_headers = 8 + let test_packets = 10 9 Array.init 1000 (fun i -> 11 - let b = Bytes.create 6 in 12 - let packet_id = ((i mod 2) lsl 12) lor (i mod 2048) in 13 - Bytes.set_uint16_be b 0 packet_id; 14 - let seq_ctrl = 0xC000 lor (i mod 16384) in 15 - Bytes.set_uint16_be b 2 seq_ctrl; 16 - Bytes.set_uint16_be b 4 (i mod 256); 17 - b) 18 - 19 - let decode_exn b off = 20 - match Space_packet.decode_packed_header b off with 21 - | Ok v -> v 22 - | Error e -> Fmt.failwith "decode: %a" Wire.pp_parse_error e 10 + let data = String.make ((i mod 256) + 1) '\x00' in 11 + Space_packet.v_exn 12 + ~packet_type:(if i mod 2 = 0 then Telemetry else Telecommand) 13 + ~apid:(i mod 2048) ~sequence_flags:Unsegmented 14 + ~sequence_count:(i mod 16384) data) 23 15 24 - let test_wire_headers = Array.map (fun b -> decode_exn b 0) test_headers 16 + let test_encoded = 17 + Array.map 18 + (fun pkt -> Bytes.unsafe_of_string (Space_packet.encode pkt)) 19 + test_packets 25 20 26 21 (** {1 Raw Baseline (no record allocation)} *) 27 22 28 23 let baseline_decode () = 29 - for i = 0 to Array.length test_headers - 1 do 30 - let b = test_headers.(i) in 24 + for i = 0 to Array.length test_encoded - 1 do 25 + let b = test_encoded.(i) in 31 26 let packet_id = Bytes.get_uint16_be b 0 in 32 27 let seq_ctrl = Bytes.get_uint16_be b 2 in 33 28 let data_len = Bytes.get_uint16_be b 4 in ··· 41 36 done 42 37 43 38 let baseline_encode () = 44 - for i = 0 to Array.length test_wire_headers - 1 do 45 - let t = test_wire_headers.(i) in 46 - let b = Bytes.create 6 in 39 + for i = 0 to Array.length test_packets - 1 do 40 + let t = test_packets.(i) in 41 + let b = Bytes.create (6 + String.length (Space_packet.data t)) in 47 42 let packet_id = 48 - (t.Space_packet.version lsl 13) 49 - lor (Space_packet.packet_type_to_int t.packet_type lsl 12) 50 - lor ((if t.secondary_header then 1 else 0) lsl 11) 51 - lor t.apid 43 + (Space_packet.version t lsl 13) 44 + lor (Space_packet.packet_type_to_int (Space_packet.packet_type t) lsl 12) 45 + lor ((if Space_packet.secondary_header_flag t then 1 else 0) lsl 11) 46 + lor Space_packet.apid t 52 47 in 53 48 Bytes.set_uint16_be b 0 packet_id; 54 49 let seq_ctrl = 55 - (Space_packet.sequence_flags_to_int t.sequence_flags lsl 14) 56 - lor t.sequence_count 50 + (Space_packet.sequence_flags_to_int (Space_packet.sequence_flags t) lsl 14) 51 + lor Space_packet.sequence_count t 57 52 in 58 53 Bytes.set_uint16_be b 2 seq_ctrl; 59 - Bytes.set_uint16_be b 4 t.data_length; 54 + Bytes.set_uint16_be b 4 (Space_packet.data_length t); 55 + Bytes.blit_string (Space_packet.data t) 0 b 6 56 + (String.length (Space_packet.data t)); 60 57 let _ = b in 61 58 () 62 59 done 63 60 64 61 let baseline_roundtrip () = 65 - for i = 0 to Array.length test_headers - 1 do 66 - let b = test_headers.(i) in 62 + for i = 0 to Array.length test_encoded - 1 do 63 + let b = test_encoded.(i) in 67 64 let w0 = Bytes.get_uint16_be b 0 in 68 65 let w1 = Bytes.get_uint16_be b 2 in 69 66 let w2 = Bytes.get_uint16_be b 4 in 70 - let out = Bytes.create 6 in 67 + let out = Bytes.create (Bytes.length b) in 71 68 Bytes.set_uint16_be out 0 w0; 72 69 Bytes.set_uint16_be out 2 w1; 73 70 Bytes.set_uint16_be out 4 w2; 71 + Bytes.blit b 6 out 6 (Bytes.length b - 6); 74 72 let _ = out in 75 73 () 76 74 done ··· 78 76 (** {1 Wire Codec} *) 79 77 80 78 let wire_decode () = 81 - for i = 0 to Array.length test_headers - 1 do 82 - let _ = Space_packet.decode_packed_header test_headers.(i) 0 in 79 + for i = 0 to Array.length test_encoded - 1 do 80 + let _ = Wire.Codec.decode Space_packet.codec test_encoded.(i) 0 in 83 81 () 84 82 done 85 83 86 84 let wire_encode () = 87 - for i = 0 to Array.length test_wire_headers - 1 do 88 - let buf = Bytes.create 6 in 89 - Space_packet.encode_packed_header test_wire_headers.(i) buf 0; 85 + for i = 0 to Array.length test_packets - 1 do 86 + let t = test_packets.(i) in 87 + let buf = Bytes.create (6 + String.length (Space_packet.data t)) in 88 + Wire.Codec.encode Space_packet.codec t buf 0; 90 89 let _ = buf in 91 90 () 92 91 done 93 92 94 93 let wire_roundtrip () = 95 - for i = 0 to Array.length test_headers - 1 do 96 - let t = decode_exn test_headers.(i) 0 in 97 - let buf = Bytes.create 6 in 98 - Space_packet.encode_packed_header t buf 0; 99 - let _ = buf in 100 - () 94 + for i = 0 to Array.length test_encoded - 1 do 95 + match Wire.Codec.decode Space_packet.codec test_encoded.(i) 0 with 96 + | Ok t -> 97 + let buf = Bytes.create (6 + String.length (Space_packet.data t)) in 98 + Wire.Codec.encode Space_packet.codec t buf 0; 99 + let _ = buf in 100 + () 101 + | Error e -> Fmt.failwith "decode: %a" Wire.pp_parse_error e 101 102 done 102 103 103 104 (** {1 Timing} *) ··· 122 123 words_per_iter 123 124 124 125 let () = 125 - Fmt.pr "Space Packet Header Benchmark: baseline vs wire@."; 126 - Fmt.pr "===============================================@."; 127 - Fmt.pr "(processing 1000 headers per iteration)@.@."; 126 + Fmt.pr "Space Packet Benchmark: baseline vs wire@."; 127 + Fmt.pr "=========================================@."; 128 + Fmt.pr "(processing 1000 packets per iteration)@.@."; 128 129 129 130 Fmt.pr "Raw byte operations (baseline, no record alloc):@."; 130 131 time_it "decode" 1000 baseline_decode;
+1 -3
c/gen.ml
··· 1 - let () = 2 - Wire_3d.main ~package:"space-packet" 3 - [ Wire.Everparse.schema Space_packet.codec ] 1 + let () = Wire_3d.main ~package:"space-packet" [ Space_packet.header_schema ]
+135 -143
lib/space_packet.ml
··· 80 80 | Error (`Data_too_large len) -> 81 81 Fmt.invalid_arg "Space_packet: data too large (%d bytes)" len 82 82 83 - (* {1 Encoding/Decoding} 84 - 85 - Primary header layout (6 octets): 86 - Octet 0: version (3b) | type (1b) | shf (1b) | apid high (3b) 87 - Octet 1: apid low (8b) 88 - Octet 2: seq_flags (2b) | seq_count high (6b) 89 - Octet 3: seq_count low (8b) 90 - Octet 4: data_length high (8b) 91 - Octet 5: data_length low (8b) 92 - *) 93 - 94 83 let packet_type_to_int = function Telemetry -> 0 | Telecommand -> 1 95 84 96 85 let packet_type_of_int = function ··· 111 100 | 3 -> Unsegmented 112 101 | _ -> Unsegmented (* Should not happen with 2-bit field *) 113 102 114 - (* {1 Streaming I/O} *) 115 - 116 - let write w t = 117 - let data_len = String.length t.data - 1 in 118 - let header = Bytes.create 6 in 119 - (* Octet 0: version (3b) | type (1b) | shf (1b) | apid high (3b) *) 120 - let octet0 = 121 - (t.version lsl 5) 122 - lor (packet_type_to_int t.packet_type lsl 4) 123 - lor ((if t.secondary_header then 1 else 0) lsl 3) 124 - lor (t.apid lsr 8) 125 - in 126 - Bytes.set_uint8 header 0 octet0; 127 - Bytes.set_uint8 header 1 (t.apid land 0xFF); 128 - let octet2 = 129 - (sequence_flags_to_int t.sequence_flags lsl 6) lor (t.sequence_count lsr 8) 130 - in 131 - Bytes.set_uint8 header 2 octet2; 132 - Bytes.set_uint8 header 3 (t.sequence_count land 0xFF); 133 - Bytes.set_uint8 header 4 (data_len lsr 8); 134 - Bytes.set_uint8 header 5 (data_len land 0xFF); 135 - Writer.write_bytes w header; 136 - Writer.write_string w t.data 137 - 138 - let read r = 139 - let sniff = Reader.sniff 6 r in 140 - if String.length sniff < 6 then Error `Truncated 141 - else 142 - let octet0 = Char.code sniff.[0] in 143 - let version = octet0 lsr 5 in 144 - if version <> 0 then Error (`Invalid_version version) 145 - else 146 - let octet4 = Char.code sniff.[4] in 147 - let octet5 = Char.code sniff.[5] in 148 - let data_length = (octet4 lsl 8) lor octet5 in 149 - let total_len = 6 + data_length + 1 in 150 - let packet_str = Reader.sniff total_len r in 151 - if String.length packet_str < total_len then Error `Truncated 152 - else begin 153 - Reader.skip total_len r; 154 - let octet1 = Char.code packet_str.[1] in 155 - let octet2 = Char.code packet_str.[2] in 156 - let octet3 = Char.code packet_str.[3] in 157 - let packet_type = packet_type_of_int ((octet0 lsr 4) land 1) in 158 - let secondary_header = (octet0 lsr 3) land 1 = 1 in 159 - let apid = ((octet0 land 0x07) lsl 8) lor octet1 in 160 - let sequence_flags = sequence_flags_of_int (octet2 lsr 6) in 161 - let sequence_count = ((octet2 land 0x3F) lsl 8) lor octet3 in 162 - let data = String.sub packet_str 6 (data_length + 1) in 163 - Ok 164 - { 165 - version; 166 - packet_type; 167 - secondary_header; 168 - apid; 169 - sequence_flags; 170 - sequence_count; 171 - data; 172 - } 173 - end 174 - 175 - (* {1 Encoding/Decoding} *) 176 - 177 - let encode t = 178 - let buf = Buffer.create (6 + String.length t.data) in 179 - let w = Writer.of_buffer buf in 180 - write w t; 181 - Writer.write_eod w; 182 - Buffer.contents buf 183 - 184 - let decode s = 185 - let r = Reader.of_string s in 186 - read r 187 - 188 103 (* {1 Comparison} *) 189 104 190 105 let equal a b = ··· 240 155 pp_packet_type t.packet_type t.apid pp_sequence_flags t.sequence_flags 241 156 t.sequence_count t.secondary_header (String.length t.data) 242 157 243 - (* {1 Packed Header Wire Representation} *) 158 + (* {1 Wire Codec} *) 244 159 245 - type packed_header = { 246 - version : int; 247 - packet_type : packet_type; 248 - secondary_header : bool; 249 - apid : int; 250 - sequence_flags : sequence_flags; 251 - sequence_count : int; 252 - data_length : int; 253 - } 160 + let bits n = Wire.bits ~width:n Wire.U16be 161 + let w_bool = Wire.bool (bits 1) 162 + let w_ptype = Wire.lookup [ Telemetry; Telecommand ] (bits 1) 254 163 255 - let equal_packed_header a b = 256 - a.version = b.version 257 - && a.packet_type = b.packet_type 258 - && a.secondary_header = b.secondary_header 259 - && a.apid = b.apid 260 - && a.sequence_flags = b.sequence_flags 261 - && a.sequence_count = b.sequence_count 262 - && a.data_length = b.data_length 164 + let w_seq_flags = 165 + Wire.lookup 166 + [ Continuation; First_segment; Last_segment; Unsegmented ] 167 + (bits 2) 263 168 264 - (* Wire Codec *) 169 + let f_version = Wire.Field.v "version" (bits 3) 170 + let f_type = Wire.Field.v "type" w_ptype 171 + let f_shf = Wire.Field.v "shf" w_bool 172 + let f_apid = Wire.Field.v "apid" (bits 11) 173 + let f_seq_flags = Wire.Field.v "seq_flags" w_seq_flags 174 + let f_seq_count = Wire.Field.v "seq_count" (bits 14) 175 + let f_data_length = Wire.Field.v "data_length" Wire.uint16be 176 + 177 + let f_data = 178 + Wire.Field.v "data" 179 + (Wire.byte_array 180 + ~size:Wire.Expr.(Wire.Field.ref f_data_length + Wire.int 1)) 181 + 182 + let bf_version = Wire.Codec.(f_version $ fun t -> t.version) 183 + let bf_type = Wire.Codec.(f_type $ fun t -> t.packet_type) 184 + let bf_shf = Wire.Codec.(f_shf $ fun t -> t.secondary_header) 185 + let bf_apid = Wire.Codec.(f_apid $ fun t -> t.apid) 186 + let bf_seq_flags = Wire.Codec.(f_seq_flags $ fun t -> t.sequence_flags) 187 + let bf_seq_count = Wire.Codec.(f_seq_count $ fun t -> t.sequence_count) 188 + 189 + let bf_data_length = 190 + Wire.Codec.(f_data_length $ fun t -> String.length t.data - 1) 191 + 192 + let bf_data = Wire.Codec.(f_data $ fun t -> t.data) 193 + 265 194 let codec = 266 - let bits n = Wire.bits ~width:n Wire.U16be in 267 - let bool = Wire.bool (bits 1) in 268 - let ptype = Wire.lookup [ Telemetry; Telecommand ] (bits 1) in 269 - let seq_flags = 270 - Wire.lookup 271 - [ Continuation; First_segment; Last_segment; Unsegmented ] 272 - (bits 2) 273 - in 274 - let f_version = Wire.Field.v "version" (bits 3) in 275 - let f_type = Wire.Field.v "type" ptype in 276 - let f_shf = Wire.Field.v "shf" bool in 277 - let f_apid = Wire.Field.v "apid" (bits 11) in 278 - let f_seq_flags = Wire.Field.v "seq_flags" seq_flags in 279 - let f_seq_count = Wire.Field.v "seq_count" (bits 14) in 280 - let f_data_length = Wire.Field.v "data_length" Wire.uint16be in 281 - Wire.Codec.v "SpacePacketHeader" 282 - (fun version ptype shf apid seq_flags seq_count data_length -> 195 + Wire.Codec.v "SpacePacket" 196 + (fun version ptype shf apid seq_flags seq_count _data_length data -> 283 197 { 284 198 version; 285 199 packet_type = ptype; ··· 287 201 apid; 288 202 sequence_flags = seq_flags; 289 203 sequence_count = seq_count; 290 - data_length; 204 + data; 291 205 }) 292 206 Wire.Codec. 293 207 [ 294 - (f_version $ fun t -> t.version); 295 - (f_type $ fun t -> t.packet_type); 296 - (f_shf $ fun t -> t.secondary_header); 297 - (f_apid $ fun t -> t.apid); 298 - (f_seq_flags $ fun t -> t.sequence_flags); 299 - (f_seq_count $ fun t -> t.sequence_count); 300 - (f_data_length $ fun t -> t.data_length); 208 + bf_version; 209 + bf_type; 210 + bf_shf; 211 + bf_apid; 212 + bf_seq_flags; 213 + bf_seq_count; 214 + bf_data_length; 215 + bf_data; 216 + ] 217 + 218 + let header_size = Wire.Codec.min_wire_size codec 219 + 220 + (* Header-only codec for EverParse 3D schema generation. The full codec is 221 + variable-size (due to the data field), which EverParse.schema cannot handle. 222 + We keep a fixed-size header projection for C code generation. *) 223 + 224 + type _header = { 225 + _version : int; 226 + _packet_type : packet_type; 227 + _secondary_header : bool; 228 + _apid : int; 229 + _sequence_flags : sequence_flags; 230 + _sequence_count : int; 231 + _data_length : int; 232 + } 233 + 234 + let _header_codec = 235 + Wire.Codec.v "SpacePacketHeader" 236 + (fun _version _packet_type _secondary_header _apid _sequence_flags 237 + _sequence_count _data_length -> 238 + { 239 + _version; 240 + _packet_type; 241 + _secondary_header; 242 + _apid; 243 + _sequence_flags; 244 + _sequence_count; 245 + _data_length; 246 + }) 247 + Wire.Codec. 248 + [ 249 + (f_version $ fun t -> t._version); 250 + (f_type $ fun t -> t._packet_type); 251 + (f_shf $ fun t -> t._secondary_header); 252 + (f_apid $ fun t -> t._apid); 253 + (f_seq_flags $ fun t -> t._sequence_flags); 254 + (f_seq_count $ fun t -> t._sequence_count); 255 + (f_data_length $ fun t -> t._data_length); 301 256 ] 302 257 303 - let struct_ = Wire.Everparse.struct_of_codec codec 258 + let struct_ = Wire.Everparse.struct_of_codec _header_codec 304 259 305 260 let module_ = 306 261 Wire.Everparse.Raw.module_ 307 262 ~doc:"CCSDS Space Packet Primary Header (133.0-B-2)" 308 263 [ Wire.Everparse.Raw.typedef ~entrypoint:true struct_ ] 309 264 310 - (* Wire Parse/Encode *) 311 - let decode_packed_header = Wire.Codec.decode codec 312 - let encode_packed_header = Wire.Codec.encode codec 265 + let header_schema = Wire.Everparse.schema _header_codec 266 + 267 + (* {1 Encoding/Decoding} 268 + 269 + Uses the Wire codec for the full space packet (header + data). *) 270 + 271 + let encode t = 272 + let total = header_size + String.length t.data in 273 + let buf = Bytes.create total in 274 + Wire.Codec.encode codec t buf 0; 275 + Bytes.unsafe_to_string buf 276 + 277 + let decode s = 278 + let len = String.length s in 279 + if len < header_size then Error `Truncated 280 + else 281 + let buf = Bytes.unsafe_of_string s in 282 + let total = Wire.Codec.wire_size_at codec buf 0 in 283 + if len < total then Error `Truncated 284 + else 285 + match Wire.Codec.decode codec buf 0 with 286 + | Error (Wire.Invalid_tag v) -> Error (`Invalid_version v) 287 + | Error _ -> Error `Truncated 288 + | Ok t -> 289 + if t.version <> 0 then Error (`Invalid_version t.version) else Ok t 290 + 291 + (* {1 Streaming I/O} *) 292 + 293 + let write w t = 294 + let total = header_size + String.length t.data in 295 + let buf = Bytes.create total in 296 + Wire.Codec.encode codec t buf 0; 297 + Writer.write_bytes w buf 313 298 314 - let to_packed_header (p : t) : packed_header = 315 - { 316 - version = p.version; 317 - packet_type = p.packet_type; 318 - secondary_header = p.secondary_header; 319 - apid = p.apid; 320 - sequence_flags = p.sequence_flags; 321 - sequence_count = p.sequence_count; 322 - data_length = data_length p; 323 - } 299 + let read r = 300 + let sniff = Reader.sniff header_size r in 301 + if String.length sniff < header_size then Error `Truncated 302 + else 303 + let buf = Bytes.unsafe_of_string sniff in 304 + let total = Wire.Codec.wire_size_at codec buf 0 in 305 + let packet_str = Reader.sniff total r in 306 + if String.length packet_str < total then Error `Truncated 307 + else begin 308 + Reader.skip total r; 309 + let buf = Bytes.unsafe_of_string packet_str in 310 + match Wire.Codec.decode codec buf 0 with 311 + | Error (Wire.Invalid_tag v) -> Error (`Invalid_version v) 312 + | Error _ -> Error `Truncated 313 + | Ok t -> 314 + if t.version <> 0 then Error (`Invalid_version t.version) else Ok t 315 + end
+29 -30
lib/space_packet.mli
··· 187 187 val sequence_flags_of_int : int -> sequence_flags 188 188 (** Convert an integer wire value to sequence flags. *) 189 189 190 - (** {1 Packed Header Wire Representation} *) 190 + (** {1 Wire Codec} *) 191 191 192 - type packed_header = { 193 - version : int; (** Packet version (3 bits). *) 194 - packet_type : packet_type; (** Packet type: TM or TC (1 bit). *) 195 - secondary_header : bool; (** Secondary header present (1 bit). *) 196 - apid : int; (** Application Process Identifier (11 bits). *) 197 - sequence_flags : sequence_flags; (** Sequence flags (2 bits). *) 198 - sequence_count : int; (** Sequence count (14 bits). *) 199 - data_length : int; (** Data length field (16 bits). *) 200 - } 201 - (** Primary header with typed fields (6 bytes wire). *) 192 + val codec : t Wire.Codec.t 193 + (** Wire codec for the full space packet (header + data). The codec models the 194 + complete packet: the 6-byte primary header followed by a variable-length 195 + data field whose size is determined by the [data_length] header field. *) 202 196 203 - val to_packed_header : t -> packed_header 204 - (** [to_packed_header p] extracts the primary header fields into a packed 205 - representation. *) 197 + val header_size : int 198 + (** Fixed wire size of the primary header (6 bytes). *) 206 199 207 - val equal_packed_header : packed_header -> packed_header -> bool 200 + (** {2 Field Bindings} 208 201 209 - (** {1 Wire Codec} *) 202 + Bound field handles for zero-copy access to individual fields in a buffer 203 + via {!Wire.Codec.get} and {!Wire.Codec.set}. For example: 204 + {[ 205 + let get_apid = Wire.Staged.unstage (Wire.Codec.get codec bf_apid) 206 + let apid = get_apid buf off 207 + ]} *) 210 208 211 - val codec : packed_header Wire.Codec.t 212 - (** Wire codec for packed header encoding and decoding. *) 209 + val bf_version : (int, t) Wire.Codec.field 210 + val bf_type : (packet_type, t) Wire.Codec.field 211 + val bf_shf : (bool, t) Wire.Codec.field 212 + val bf_apid : (int, t) Wire.Codec.field 213 + val bf_seq_flags : (sequence_flags, t) Wire.Codec.field 214 + val bf_seq_count : (int, t) Wire.Codec.field 215 + val bf_data_length : (int, t) Wire.Codec.field 216 + val bf_data : (string, t) Wire.Codec.field 213 217 214 218 val struct_ : Wire.Everparse.struct_ 215 - (** Wire struct descriptor for the packed header. *) 219 + (** Wire struct descriptor for the primary header (fixed-size, for EverParse 3D 220 + code generation). *) 216 221 217 222 val module_ : Wire.Everparse.module_ 218 - (** Wire module descriptor for the space packet. *) 223 + (** Wire module descriptor for the primary header (for EverParse 3D code 224 + generation). *) 219 225 220 - (** {1 Wire Parse/Encode} *) 221 - 222 - val decode_packed_header : 223 - bytes -> int -> (packed_header, Wire.parse_error) result 224 - (** [decode_packed_header buf off] decodes a packed header from bytes at offset. 225 - *) 226 - 227 - val encode_packed_header : packed_header -> bytes -> int -> unit 228 - (** [encode_packed_header hdr buf off] encodes a packed header into bytes at 229 - offset. *) 226 + val header_schema : Wire.Everparse.t 227 + (** EverParse schema for the primary header. Use this for C code generation via 228 + {!Wire.Everparse.write_3d}. *)