Skip to main content

semq_sys/
lib.rs

1// Copyright (c) 2026 The SEMQ Group Inc.
2// Licensed under the PolyForm Noncommercial License 1.0.0. See LICENSE.md for terms.
3
4//! Raw FFI declarations for the SEMQ core.
5//!
6//! Mirrors `include/semq.h` one-to-one: the constants, every enum value,
7//! the plain structs, the opaque handles and all exported functions. Nothing
8//! here is safe; the `semq` crate builds the safe API on top. The
9//! declarations are written by hand so the build needs no libclang; layout
10//! tests pin the structs whose size and offsets the ABI depends on.
11//!
12//! C enums are declared as `u32` aliases with one constant per value. Every
13//! function that can fail returns a [`semq_status_t`] and fills the
14//! [`semq_error_t`] it is given, when non-null.
15
16#![allow(non_camel_case_types)]
17
18use std::marker::{PhantomData, PhantomPinned};
19use std::os::raw::{c_char, c_int};
20
21// --------------------------------------------------------------------------
22// Limits and constants
23// --------------------------------------------------------------------------
24
25/// File format version written and accepted by this core.
26pub const SEMQ_FILE_VERSION: u32 = 2;
27/// Operator rule revision written into every config (`p2`).
28pub const SEMQ_RULE_REVISION: u32 = 0;
29/// Size of the canonical config form in bytes.
30pub const SEMQ_CONFIG_BYTES: u32 = 13;
31/// Size of each identity (`content_digest`, `state_id`) in bytes.
32pub const SEMQ_DIGEST_BYTES: u32 = 32;
33/// Largest accepted vector dimension.
34pub const SEMQ_MAX_DIM: u32 = 65536;
35/// Largest `utf8` id, in bytes. Ids are at least one byte.
36pub const SEMQ_ID_MAX_BYTES: u32 = 4096;
37/// Manifest limits.
38pub const SEMQ_MANIFEST_MAX_PAIRS: u32 = 4096;
39pub const SEMQ_MANIFEST_KEY_MAX: u32 = 256;
40pub const SEMQ_MANIFEST_VALUE_MAX: u32 = 65536;
41pub const SEMQ_MANIFEST_SECTION_MAX: u32 = 16 * 1024 * 1024;
42/// Sentinel for "no row" / "no field" in [`semq_error_t`] and index lookups.
43pub const SEMQ_NONE: u64 = u64::MAX;
44
45/// Operators. Values are pinned: they appear in the canonical config form.
46pub type semq_operator_t = u32;
47pub const SEMQ_ORBIT: semq_operator_t = 0;
48pub const SEMQ_PHASE: semq_operator_t = 1;
49pub const SEMQ_QUANT: semq_operator_t = 2;
50
51/// Identifier kinds. Values are pinned: they appear in files.
52pub type semq_id_kind_t = u32;
53pub const SEMQ_ID_U64: semq_id_kind_t = 0;
54pub const SEMQ_ID_UTF8: semq_id_kind_t = 1;
55
56/// Status codes. Each maps to exactly one host error type.
57pub type semq_status_t = u32;
58pub const SEMQ_OK: semq_status_t = 0;
59/// An argument violates its contract.
60pub const SEMQ_ERR_INVALID_INPUT: semq_status_t = 1;
61/// Two states differ in config or id kind.
62pub const SEMQ_ERR_INCOMPATIBLE: semq_status_t = 2;
63/// A file image is not a valid v2 image.
64pub const SEMQ_ERR_FORMAT: semq_status_t = 3;
65/// A file image's digest does not match.
66pub const SEMQ_ERR_INTEGRITY: semq_status_t = 4;
67/// The operation or FP environment is not supported.
68pub const SEMQ_ERR_UNSUPPORTED: semq_status_t = 5;
69/// An allocation failed.
70pub const SEMQ_ERR_NOMEM: semq_status_t = 6;
71/// A defect in the core; report it.
72pub const SEMQ_ERR_INTERNAL: semq_status_t = 7;
73
74/// Which integrity check failed on load.
75pub type semq_which_t = u32;
76pub const SEMQ_WHICH_NONE: semq_which_t = 0;
77pub const SEMQ_WHICH_CONTENT: semq_which_t = 1;
78pub const SEMQ_WHICH_STATE: semq_which_t = 2;
79
80/// File sections named by `field` in [`SEMQ_ERR_FORMAT`] errors.
81pub type semq_section_t = u32;
82pub const SEMQ_SECTION_FRAMING: semq_section_t = 0;
83pub const SEMQ_SECTION_CONFIG: semq_section_t = 1;
84pub const SEMQ_SECTION_SIZES: semq_section_t = 2;
85pub const SEMQ_SECTION_IDS: semq_section_t = 3;
86pub const SEMQ_SECTION_MANIFEST: semq_section_t = 4;
87pub const SEMQ_SECTION_FOOTER: semq_section_t = 5;
88pub const SEMQ_SECTION_ROWS: semq_section_t = 6;
89
90/// Config fields named by `field` in [`SEMQ_ERR_INVALID_INPUT`] errors raised
91/// by the config constructors.
92pub type semq_config_field_t = u32;
93pub const SEMQ_FIELD_OPERATOR: semq_config_field_t = 0;
94pub const SEMQ_FIELD_DIM: semq_config_field_t = 1;
95/// scale, sectors or bins.
96pub const SEMQ_FIELD_P1: semq_config_field_t = 2;
97pub const SEMQ_FIELD_P2: semq_config_field_t = 3;
98
99/// Which id list of a Diff an accessor refers to.
100pub type semq_list_t = u32;
101pub const SEMQ_LIST_ADDED: semq_list_t = 0;
102pub const SEMQ_LIST_REMOVED: semq_list_t = 1;
103pub const SEMQ_LIST_CHANGED: semq_list_t = 2;
104
105// --------------------------------------------------------------------------
106// Plain structs
107// --------------------------------------------------------------------------
108
109/// Error report. `status` is the returned status; `which` is set only for
110/// [`SEMQ_ERR_INTEGRITY`]; `row` and `field` are [`SEMQ_NONE`] when they do
111/// not apply. `message` is always NUL-terminated and never contains input
112/// data.
113#[repr(C)]
114#[derive(Debug, Clone, Copy)]
115pub struct semq_error_t {
116    pub status: u32,
117    pub which: u32,
118    pub row: u64,
119    pub field: u64,
120    pub message: [c_char; 128],
121}
122
123/// A codec configuration. `op` is a [`semq_operator_t`]; `p1` is `scale`
124/// (orbit), `sectors` (phase) or `bins` (quant); `p2` is the rule revision.
125#[repr(C)]
126#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
127pub struct semq_config_t {
128    pub op: u32,
129    pub dim: u32,
130    pub p1: u32,
131    pub p2: u32,
132}
133
134/// One manifest pair. Keys and values are UTF-8 bytes, not NUL-terminated.
135#[repr(C)]
136#[derive(Debug, Clone, Copy)]
137pub struct semq_pair_t {
138    pub key: *const u8,
139    pub key_len: u32,
140    pub value: *const u8,
141    pub value_len: u32,
142}
143
144/// Identifiers of `n` rows, in input order (any order; the core sorts).
145/// For [`SEMQ_ID_U64`] pass `u64` (n entries). For [`SEMQ_ID_UTF8`] pass
146/// `utf8_offsets` (n + 1 entries, `offsets[0] = 0`, non-decreasing) and
147/// `utf8_bytes` (`offsets[n]` bytes): id `i` is `bytes[offsets[i] .. offsets[i+1])`.
148#[repr(C)]
149#[derive(Debug, Clone, Copy)]
150pub struct semq_ids_t {
151    pub kind: u32,
152    pub n: u64,
153    pub u64: *const u64,
154    pub utf8_offsets: *const u64,
155    pub utf8_bytes: *const u8,
156}
157
158/// One borrowed part of the file image.
159#[repr(C)]
160#[derive(Debug, Clone, Copy)]
161pub struct semq_part_t {
162    pub ptr: *const u8,
163    pub len: u64,
164}
165
166/// A change to one manifest key. `has_before`/`has_after` are 0 when the
167/// key is absent on that side; the pointers are then NULL.
168#[repr(C)]
169#[derive(Debug, Clone, Copy)]
170pub struct semq_manifest_change_t {
171    pub key: *const u8,
172    pub key_len: u32,
173    pub has_before: c_int,
174    pub before: *const u8,
175    pub before_len: u32,
176    pub has_after: c_int,
177    pub after: *const u8,
178    pub after_len: u32,
179}
180
181// --------------------------------------------------------------------------
182// Opaque handles
183// --------------------------------------------------------------------------
184
185/// Opaque codec handle (`struct semq_codec`).
186#[repr(C)]
187pub struct semq_codec_t {
188    _data: [u8; 0],
189    _marker: PhantomData<(*mut u8, PhantomPinned)>,
190}
191
192/// Opaque encoding handle (`struct semq_encoding`).
193#[repr(C)]
194pub struct semq_encoding_t {
195    _data: [u8; 0],
196    _marker: PhantomData<(*mut u8, PhantomPinned)>,
197}
198
199/// Opaque diff handle (`struct semq_diff`).
200#[repr(C)]
201pub struct semq_diff_t {
202    _data: [u8; 0],
203    _marker: PhantomData<(*mut u8, PhantomPinned)>,
204}
205
206/// Opaque floor handle (`struct semq_floor`).
207#[repr(C)]
208pub struct semq_floor_t {
209    _data: [u8; 0],
210    _marker: PhantomData<(*mut u8, PhantomPinned)>,
211}
212
213// --------------------------------------------------------------------------
214// Functions (every SEMQ_API declaration in semq.h, in header order)
215// --------------------------------------------------------------------------
216
217extern "C" {
218    // CodecConfig
219    pub fn semq_config_orbit(
220        dim: u32,
221        scale: u32,
222        out: *mut semq_config_t,
223        err: *mut semq_error_t,
224    ) -> semq_status_t;
225    pub fn semq_config_phase(
226        dim: u32,
227        sectors: u32,
228        out: *mut semq_config_t,
229        err: *mut semq_error_t,
230    ) -> semq_status_t;
231    pub fn semq_config_quant(
232        dim: u32,
233        bins: u32,
234        out: *mut semq_config_t,
235        err: *mut semq_error_t,
236    ) -> semq_status_t;
237    pub fn semq_config_validate(cfg: *const semq_config_t, err: *mut semq_error_t)
238        -> semq_status_t;
239    /// Write the 13-byte canonical form. `cfg` must be valid.
240    pub fn semq_config_to_bytes(cfg: *const semq_config_t, out: *mut u8);
241    /// Parse a 13-byte canonical form.
242    pub fn semq_config_from_bytes(
243        input: *const u8,
244        out: *mut semq_config_t,
245        err: *mut semq_error_t,
246    ) -> semq_status_t;
247    pub fn semq_config_bytes_per_vector(cfg: *const semq_config_t) -> u32;
248    pub fn semq_config_units_per_row(cfg: *const semq_config_t) -> u32;
249    /// quant only: `(float)(2.0 / sqrt((double)dim))`; 0 for other operators.
250    pub fn semq_config_max_magnitude(cfg: *const semq_config_t) -> f32;
251    /// 1 when the two configs have the same canonical bytes.
252    pub fn semq_config_equal(a: *const semq_config_t, b: *const semq_config_t) -> c_int;
253
254    // Codec
255    pub fn semq_codec_create(
256        cfg: *const semq_config_t,
257        out: *mut *mut semq_codec_t,
258        err: *mut semq_error_t,
259    ) -> semq_status_t;
260    pub fn semq_codec_free(codec: *mut semq_codec_t);
261    pub fn semq_codec_config(codec: *const semq_codec_t) -> *const semq_config_t;
262    /// Name of the kernel this codec runs ("scalar", "neon", "avx2", ...).
263    pub fn semq_codec_backend(codec: *const semq_codec_t) -> *const c_char;
264    pub fn semq_codec_encode(
265        codec: *const semq_codec_t,
266        ids: *const semq_ids_t,
267        vectors: *const f32,
268        manifest: *const semq_pair_t,
269        n_pairs: u32,
270        out: *mut *mut semq_encoding_t,
271        err: *mut semq_error_t,
272    ) -> semq_status_t;
273    /// Representatives, `n x dim` float32, row `i` for `ids[i]`.
274    pub fn semq_codec_decode(
275        codec: *const semq_codec_t,
276        enc: *const semq_encoding_t,
277        out: *mut f32,
278        err: *mut semq_error_t,
279    ) -> semq_status_t;
280    /// Symbols, `n x units_per_row` bytes, one per unit.
281    pub fn semq_codec_unpack(
282        codec: *const semq_codec_t,
283        enc: *const semq_encoding_t,
284        out: *mut u8,
285        err: *mut semq_error_t,
286    ) -> semq_status_t;
287
288    // Encoding
289    pub fn semq_encoding_create(
290        cfg: *const semq_config_t,
291        ids: *const semq_ids_t,
292        rows: *const u8,
293        manifest: *const semq_pair_t,
294        n_pairs: u32,
295        out: *mut *mut semq_encoding_t,
296        err: *mut semq_error_t,
297    ) -> semq_status_t;
298    pub fn semq_encoding_free(enc: *mut semq_encoding_t);
299    pub fn semq_encoding_config(enc: *const semq_encoding_t) -> *const semq_config_t;
300    pub fn semq_encoding_id_kind(enc: *const semq_encoding_t) -> u32;
301    pub fn semq_encoding_len(enc: *const semq_encoding_t) -> u64;
302    /// Rows in id order, `n x bytes_per_vector` bytes.
303    pub fn semq_encoding_rows(enc: *const semq_encoding_t, len: *mut u64) -> *const u8;
304    /// Sorted ids. `u64` kinds: `n` values.
305    pub fn semq_encoding_ids_u64(enc: *const semq_encoding_t) -> *const u64;
306    /// `utf8` kinds: `n + 1` offsets.
307    pub fn semq_encoding_ids_utf8_offsets(enc: *const semq_encoding_t) -> *const u64;
308    /// `utf8` kinds: the id bytes.
309    pub fn semq_encoding_ids_utf8_bytes(enc: *const semq_encoding_t, len: *mut u64) -> *const u8;
310    /// Manifest pairs in canonical (bytewise key) order.
311    pub fn semq_encoding_manifest_len(enc: *const semq_encoding_t) -> u32;
312    pub fn semq_encoding_manifest_pair(
313        enc: *const semq_encoding_t,
314        i: u32,
315        out: *mut semq_pair_t,
316        err: *mut semq_error_t,
317    ) -> semq_status_t;
318    pub fn semq_encoding_content_digest(enc: *const semq_encoding_t, out: *mut u8);
319    pub fn semq_encoding_state_id(enc: *const semq_encoding_t, out: *mut u8);
320    /// Row index of an id, or [`SEMQ_NONE`] when absent.
321    pub fn semq_encoding_find_u64(
322        enc: *const semq_encoding_t,
323        id: u64,
324        index: *mut u64,
325        err: *mut semq_error_t,
326    ) -> semq_status_t;
327    pub fn semq_encoding_find_utf8(
328        enc: *const semq_encoding_t,
329        id: *const u8,
330        len: u64,
331        index: *mut u64,
332        err: *mut semq_error_t,
333    ) -> semq_status_t;
334    /// Total size of the file image.
335    pub fn semq_encoding_file_size(enc: *const semq_encoding_t) -> u64;
336    /// The file image as 5 ordered parts owned by the handle. Returns 5.
337    pub fn semq_encoding_save_parts(enc: *const semq_encoding_t, parts: *mut semq_part_t) -> u32;
338    /// Write the whole file image into `out` (`cap` >= file size).
339    pub fn semq_encoding_save(
340        enc: *const semq_encoding_t,
341        out: *mut u8,
342        cap: u64,
343        err: *mut semq_error_t,
344    ) -> semq_status_t;
345    /// Parse a complete file image. The bytes are copied.
346    pub fn semq_encoding_load(
347        buf: *const u8,
348        len: u64,
349        out: *mut *mut semq_encoding_t,
350        err: *mut semq_error_t,
351    ) -> semq_status_t;
352    /// Merge `k` Encodings with the same config, id kind and manifest and
353    /// pairwise-disjoint ids.
354    pub fn semq_encoding_concat(
355        parts: *const *const semq_encoding_t,
356        k: u32,
357        out: *mut *mut semq_encoding_t,
358        err: *mut semq_error_t,
359    ) -> semq_status_t;
360    /// `reference.diff(candidate)`.
361    pub fn semq_encoding_diff(
362        reference: *const semq_encoding_t,
363        candidate: *const semq_encoding_t,
364        out: *mut *mut semq_diff_t,
365        err: *mut semq_error_t,
366    ) -> semq_status_t;
367
368    // Diff
369    /// The Diff keeps both Encodings alive until it is freed.
370    pub fn semq_diff_free(diff: *mut semq_diff_t);
371    pub fn semq_diff_config(diff: *const semq_diff_t) -> *const semq_config_t;
372    pub fn semq_diff_id_kind(diff: *const semq_diff_t) -> u32;
373    pub fn semq_diff_reference_id(diff: *const semq_diff_t, out: *mut u8);
374    pub fn semq_diff_candidate_id(diff: *const semq_diff_t, out: *mut u8);
375    pub fn semq_diff_count(diff: *const semq_diff_t, list: u32) -> u64;
376    pub fn semq_diff_n_unchanged(diff: *const semq_diff_t) -> u64;
377    /// Id `i` of a list, in canonical order. For `u64` kinds `*out_u64` is
378    /// set; for `utf8` kinds `*bytes`/`*len` are set.
379    pub fn semq_diff_id(
380        diff: *const semq_diff_t,
381        list: u32,
382        i: u64,
383        out_u64: *mut u64,
384        bytes: *mut *const u8,
385        len: *mut u64,
386        err: *mut semq_error_t,
387    ) -> semq_status_t;
388    /// Hamming distance of changed id `i` (units whose symbol differs).
389    pub fn semq_diff_hamming(
390        diff: *const semq_diff_t,
391        i: u64,
392        out: *mut u64,
393        err: *mut semq_error_t,
394    ) -> semq_status_t;
395    pub fn semq_diff_manifest_changes(diff: *const semq_diff_t) -> u32;
396    pub fn semq_diff_manifest_change(
397        diff: *const semq_diff_t,
398        i: u32,
399        out: *mut semq_manifest_change_t,
400        err: *mut semq_error_t,
401    ) -> semq_status_t;
402    /// Differing units of an id present on both sides. Writes up to `cap`
403    /// entries and the total count into `*count`. Pass `cap = 0` to query.
404    pub fn semq_diff_units_u64(
405        diff: *const semq_diff_t,
406        id: u64,
407        units: *mut u32,
408        ref_symbols: *mut u8,
409        cand_symbols: *mut u8,
410        cap: u64,
411        count: *mut u64,
412        err: *mut semq_error_t,
413    ) -> semq_status_t;
414    pub fn semq_diff_units_utf8(
415        diff: *const semq_diff_t,
416        id: *const u8,
417        len: u64,
418        units: *mut u32,
419        ref_symbols: *mut u8,
420        cand_symbols: *mut u8,
421        cap: u64,
422        count: *mut u64,
423        err: *mut semq_error_t,
424    ) -> semq_status_t;
425
426    // Floor
427    /// A floor from its fields. `config` must validate, `id_kind` must be
428    /// 0 or 1, `reference_id` is 32 bytes, `nulls >= 1`, `total_rows >= 1`,
429    /// `changed_rows <= total_rows` and `hamming <= units_per_row(config)`;
430    /// otherwise `SEMQ_ERR_INVALID_INPUT`.
431    pub fn semq_floor_create(
432        config: *const semq_config_t,
433        id_kind: u32,
434        reference_id: *const u8,
435        nulls: u64,
436        changed_rows: u64,
437        total_rows: u64,
438        hamming: u64,
439        out: *mut *mut semq_floor_t,
440        err: *mut semq_error_t,
441    ) -> semq_status_t;
442    pub fn semq_floor_free(floor: *mut semq_floor_t);
443    /// Envelope of `k >= 1` null diffs; every input diff is within the
444    /// result. All nulls must share config, id kind and reference state_id
445    /// (`SEMQ_ERR_INCOMPATIBLE`, `field` = the offending index) and each
446    /// must be a valid null (`SEMQ_ERR_INVALID_INPUT`, `field` = index).
447    pub fn semq_floor_measure(
448        nulls: *const *const semq_diff_t,
449        k: u32,
450        out: *mut *mut semq_floor_t,
451        err: *mut semq_error_t,
452    ) -> semq_status_t;
453    /// The config, owned by the handle.
454    pub fn semq_floor_config(floor: *const semq_floor_t) -> *const semq_config_t;
455    /// 0 = u64, 1 = utf8.
456    pub fn semq_floor_id_kind(floor: *const semq_floor_t) -> u32;
457    /// Writes the 32-byte state_id of the reference into `out`.
458    pub fn semq_floor_reference_id(floor: *const semq_floor_t, out: *mut u8);
459    pub fn semq_floor_nulls(floor: *const semq_floor_t) -> u64;
460    pub fn semq_floor_changed_rows(floor: *const semq_floor_t) -> u64;
461    pub fn semq_floor_total_rows(floor: *const semq_floor_t) -> u64;
462    pub fn semq_floor_hamming(floor: *const semq_floor_t) -> u64;
463    /// `*out = 1` iff the diff is within the floor (exact integer
464    /// arithmetic). The floor must match the diff's config, id kind and
465    /// reference state_id (`SEMQ_ERR_INCOMPATIBLE` otherwise).
466    pub fn semq_diff_within(
467        diff: *const semq_diff_t,
468        floor: *const semq_floor_t,
469        out: *mut c_int,
470        err: *mut semq_error_t,
471    ) -> semq_status_t;
472
473    // Build information and utilities
474    /// Core version string, from the repository `VERSION` file.
475    pub fn semq_core_version() -> *const c_char;
476    /// Compile-time build recipe identifier. Not a provenance proof.
477    pub fn semq_build_id() -> *const c_char;
478    /// Effective kernel for an operator on this host ("scalar", "neon", ...).
479    pub fn semq_backend_name(op: u32) -> *const c_char;
480    /// Static name of a status code ("ok", "invalid_input", ...).
481    pub fn semq_status_name(status: u32) -> *const c_char;
482    /// SHA-256 (FIPS 180-4) of `len` bytes.
483    pub fn semq_sha256(data: *const u8, len: u64, out: *mut u8);
484}
485
486#[cfg(test)]
487mod tests {
488    use super::*;
489    use std::ffi::CStr;
490    use std::mem::{align_of, offset_of, size_of};
491
492    /// `semq_error_t` is `u32, u32, u64, u64, char[128]`: 152 bytes.
493    #[test]
494    fn error_layout_matches_the_abi() {
495        assert_eq!(size_of::<semq_error_t>(), 152);
496        assert_eq!(align_of::<semq_error_t>(), 8);
497        assert_eq!(offset_of!(semq_error_t, status), 0);
498        assert_eq!(offset_of!(semq_error_t, which), 4);
499        assert_eq!(offset_of!(semq_error_t, row), 8);
500        assert_eq!(offset_of!(semq_error_t, field), 16);
501        assert_eq!(offset_of!(semq_error_t, message), 24);
502    }
503
504    /// `semq_config_t` is four `u32`: 16 bytes.
505    #[test]
506    fn config_layout_matches_the_abi() {
507        assert_eq!(size_of::<semq_config_t>(), 16);
508        assert_eq!(align_of::<semq_config_t>(), 4);
509        assert_eq!(offset_of!(semq_config_t, op), 0);
510        assert_eq!(offset_of!(semq_config_t, dim), 4);
511        assert_eq!(offset_of!(semq_config_t, p1), 8);
512        assert_eq!(offset_of!(semq_config_t, p2), 12);
513    }
514
515    /// Pointer-bearing structs, on 64-bit hosts (the core's target).
516    #[cfg(target_pointer_width = "64")]
517    #[test]
518    fn pointer_struct_layouts_match_the_abi() {
519        assert_eq!(size_of::<semq_pair_t>(), 32);
520        assert_eq!(offset_of!(semq_pair_t, key), 0);
521        assert_eq!(offset_of!(semq_pair_t, key_len), 8);
522        assert_eq!(offset_of!(semq_pair_t, value), 16);
523        assert_eq!(offset_of!(semq_pair_t, value_len), 24);
524
525        assert_eq!(size_of::<semq_ids_t>(), 40);
526        assert_eq!(offset_of!(semq_ids_t, kind), 0);
527        assert_eq!(offset_of!(semq_ids_t, n), 8);
528        assert_eq!(offset_of!(semq_ids_t, u64), 16);
529        assert_eq!(offset_of!(semq_ids_t, utf8_offsets), 24);
530        assert_eq!(offset_of!(semq_ids_t, utf8_bytes), 32);
531
532        assert_eq!(size_of::<semq_part_t>(), 16);
533        assert_eq!(offset_of!(semq_part_t, ptr), 0);
534        assert_eq!(offset_of!(semq_part_t, len), 8);
535
536        assert_eq!(size_of::<semq_manifest_change_t>(), 48);
537        assert_eq!(offset_of!(semq_manifest_change_t, key), 0);
538        assert_eq!(offset_of!(semq_manifest_change_t, key_len), 8);
539        assert_eq!(offset_of!(semq_manifest_change_t, has_before), 12);
540        assert_eq!(offset_of!(semq_manifest_change_t, before), 16);
541        assert_eq!(offset_of!(semq_manifest_change_t, before_len), 24);
542        assert_eq!(offset_of!(semq_manifest_change_t, has_after), 28);
543        assert_eq!(offset_of!(semq_manifest_change_t, after), 32);
544        assert_eq!(offset_of!(semq_manifest_change_t, after_len), 40);
545    }
546
547    /// The library links and the simplest entry points answer as the header
548    /// documents: a quant(4, 4) config and its canonical form.
549    #[test]
550    fn core_links_and_answers() {
551        let version = unsafe { CStr::from_ptr(semq_core_version()) };
552        assert!(!version.to_bytes().is_empty());
553
554        let mut cfg = semq_config_t {
555            op: 0,
556            dim: 0,
557            p1: 0,
558            p2: 0,
559        };
560        let status = unsafe { semq_config_quant(4, 4, &mut cfg, std::ptr::null_mut()) };
561        assert_eq!(status, SEMQ_OK);
562        assert_eq!(
563            cfg,
564            semq_config_t {
565                op: SEMQ_QUANT,
566                dim: 4,
567                p1: 4,
568                p2: SEMQ_RULE_REVISION
569            }
570        );
571        let mut bytes = [0u8; 13];
572        unsafe { semq_config_to_bytes(&cfg, bytes.as_mut_ptr()) };
573        assert_eq!(bytes, [2, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0]);
574
575        let name = unsafe { CStr::from_ptr(semq_status_name(SEMQ_ERR_INVALID_INPUT)) };
576        assert_eq!(name.to_str().unwrap(), "invalid_input");
577    }
578}