Self's ruling of 2026-09-22 named six findings against Soul's eighth pass. This lands the code side of all six: - The wait-record seam (CULTMESH_QUIC_DEBUG_RECORD_WAIT) now wraps the whole duration handed to `wait_for`, not the raw `timeout_ms` before it is converted to milliseconds, so arithmetic applied after that conversion can no longer live outside what the seam records. Its "unset" sentinel moves to a separate `debug_wait_recorded` flag so a recorded -1 could never be confused with nothing recorded. - `pollhammer` also runs against a runtime holding a live connection to an unroutable address (RFC 5737 TEST-NET-1), a best-effort stream on it, and a real (never-connected) listener, closing the gap where the wait's predicate was only ever exercised on an empty runtime. - A new `zerotimeout` scenario pins that `timeout_ms <= 0` returns within 50 ms, probing 0, -1 and INT32_MIN. - `closerace`'s settle no longer sleeps a guessed duration: where the development seam exists it waits for `debug_peak_calls` to reach the poller count (reset before any poller thread exists, so nothing can zero a count a poller already raised) before a short fixed margin closes the last gap between being counted and being inside the condition wait. - `kGenerousLateToleranceMs` narrows from 800 ms to 400 ms so the wall-clock scenarios can catch arithmetic applied outside the seam's own argument, which `waitseam`'s equality check structurally cannot see. scripts/mutate-cultmesh.mjs gains a plain `release` configuration on both platforms (CULTMESH_QUIC_DEBUG_ASSERTS=OFF, CMAKE_BUILD_TYPE=Release) running `polltimeout`, `pollbusy`, `pollhammer` and `zerotimeout`, and seven new mutation entries: R1 (the release macro divides the duration by 10), W1-W3 (600 ms added, a 500 ms floor, and a 1000/909 scale, each applied outside the seam's own argument), P1 (the idle predicate also ends on a live connection), and Z1-Z2 (a 500 ms sleep and a 300 ms spin on a non-positive timeout). Two stale comments are corrected: the scenario list above `assertScenarios` now names `pollhammer` among the clock-sensitive entries, and a historical "1300" probe value that no longer matches the test file is removed. The header's section 7 intro now says the seam serves both the quiesce and the wait recording, not quiesce alone. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
371 lines
20 KiB
C
371 lines
20 KiB
C
/* GameCult.Mesh.Quic.Native — C ABI v2, the runtime-neutral CultMesh QUIC bridge.
|
|
*
|
|
* This header is the contract. It is the single place the event layout, the
|
|
* exports, the id and return-code conventions, the object lifetime rules and the
|
|
* threading rules are stated; `cultmesh_quic_native.cpp` includes it, the README
|
|
* points at it, and a host that cannot include it (koffi, P/Invoke) declares the
|
|
* same thing in its own FFI and is checked against this file by a test.
|
|
*
|
|
* It is C. It includes nothing of MsQuic, nothing of Node, nothing of Unity, and
|
|
* nothing platform-specific: a host binds these twelve queue exports plus
|
|
* `cultmesh_quic_last_status` and needs no other build input.
|
|
*
|
|
* The v1 Unity exports (`cultmesh_quic_open`, `_state`, `_poll`, `_error`,
|
|
* `_close`) are deliberately not here. They are a Windows-only shim for a
|
|
* shipped managed connector, not a contract offered to new hosts.
|
|
*/
|
|
|
|
#ifndef CULTMESH_QUIC_NATIVE_H
|
|
#define CULTMESH_QUIC_NATIVE_H
|
|
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
|
|
#if defined(__cplusplus)
|
|
#define CULTMESH_QUIC_STATIC_ASSERT(condition, message) static_assert(condition, message)
|
|
#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
|
|
#define CULTMESH_QUIC_STATIC_ASSERT(condition, message) _Static_assert(condition, message)
|
|
#else
|
|
#define CULTMESH_QUIC_STATIC_ASSERT(condition, message)
|
|
#endif
|
|
|
|
/* `CULTMESH_QUIC_BUILD` is defined by the bridge's own translation unit and by
|
|
* nothing else. A consumer gets plain declarations and links by name, which is
|
|
* what every host here does: all three load the library at runtime. */
|
|
#if defined(CULTMESH_QUIC_BUILD)
|
|
# if defined(_WIN32)
|
|
# define CULTMESH_QUIC_API __declspec(dllexport)
|
|
# else
|
|
# define CULTMESH_QUIC_API __attribute__((visibility("default")))
|
|
# endif
|
|
#else
|
|
# define CULTMESH_QUIC_API
|
|
#endif
|
|
|
|
#if defined(__cplusplus)
|
|
extern "C" {
|
|
#endif
|
|
|
|
/* ---------------------------------------------------------------------------
|
|
* 1. The event
|
|
*
|
|
* Exactly 64 bytes, no implicit padding, field order below is the ABI. A host
|
|
* that reorders a field reads a different struct; the offsets are asserted here
|
|
* so a compiler that pads differently fails the build rather than the wire.
|
|
* --------------------------------------------------------------------------- */
|
|
|
|
struct cultmesh_quic_event {
|
|
uint32_t type; /* offset 0 — one of the cultmesh_quic_event_type values */
|
|
uint32_t stream_kind; /* offset 4 — 1 reliable, 2 latest-only; 0 when not a stream event */
|
|
uint64_t listener_id; /* offset 8 — 0 for an outbound connection */
|
|
uint64_t connection_id; /* offset 16 */
|
|
uint64_t stream_id; /* offset 24 */
|
|
uint64_t code; /* offset 32 — meaning depends on `type`; see below */
|
|
int32_t status; /* offset 40 — the raw platform QUIC status, diagnostics only */
|
|
int32_t payload_length; /* offset 44 — bytes written into the caller's payload buffer */
|
|
uint8_t reserved[16]; /* offset 48 — zero today; do not read meaning into it */
|
|
};
|
|
|
|
CULTMESH_QUIC_STATIC_ASSERT(sizeof(struct cultmesh_quic_event) == 64,
|
|
"the event struct is a 64-byte ABI contract");
|
|
CULTMESH_QUIC_STATIC_ASSERT(offsetof(struct cultmesh_quic_event, type) == 0,
|
|
"event field offsets are an ABI contract");
|
|
CULTMESH_QUIC_STATIC_ASSERT(offsetof(struct cultmesh_quic_event, stream_kind) == 4,
|
|
"event field offsets are an ABI contract");
|
|
CULTMESH_QUIC_STATIC_ASSERT(offsetof(struct cultmesh_quic_event, listener_id) == 8,
|
|
"event field offsets are an ABI contract");
|
|
CULTMESH_QUIC_STATIC_ASSERT(offsetof(struct cultmesh_quic_event, connection_id) == 16,
|
|
"event field offsets are an ABI contract");
|
|
CULTMESH_QUIC_STATIC_ASSERT(offsetof(struct cultmesh_quic_event, stream_id) == 24,
|
|
"event field offsets are an ABI contract");
|
|
CULTMESH_QUIC_STATIC_ASSERT(offsetof(struct cultmesh_quic_event, code) == 32,
|
|
"event field offsets are an ABI contract");
|
|
CULTMESH_QUIC_STATIC_ASSERT(offsetof(struct cultmesh_quic_event, status) == 40,
|
|
"event field offsets are an ABI contract");
|
|
CULTMESH_QUIC_STATIC_ASSERT(offsetof(struct cultmesh_quic_event, payload_length) == 44,
|
|
"event field offsets are an ABI contract");
|
|
CULTMESH_QUIC_STATIC_ASSERT(offsetof(struct cultmesh_quic_event, reserved) == 48,
|
|
"event field offsets are an ABI contract");
|
|
|
|
enum cultmesh_quic_event_type {
|
|
/* A listener accepted an inbound connection. `listener_id`, `connection_id`. */
|
|
CULTMESH_QUIC_EVENT_LISTENER_NEW_CONNECTION = 1,
|
|
/* The handshake completed. `connection_id`. */
|
|
CULTMESH_QUIC_EVENT_CONNECTION_CONNECTED = 2,
|
|
/* Payload: the peer's DER certificate. The handshake is blocked until the
|
|
* host answers with `cultmesh_quic_connection_certificate_complete`. */
|
|
CULTMESH_QUIC_EVENT_CONNECTION_CERTIFICATE_RECEIVED = 3,
|
|
/* The connection is gone, whoever ended it, and its id no longer resolves.
|
|
* Payload: a UTF-8 reason. `code` is the QUIC application error code — the
|
|
* peer's when the peer ended it, and the code the host passed to
|
|
* `cultmesh_quic_connection_shutdown` when the host did. Emitted exactly
|
|
* once per connection id, including for the initiator of a local shutdown:
|
|
* every connection id's end is observable from the host that owns it. */
|
|
CULTMESH_QUIC_EVENT_CONNECTION_SHUTDOWN = 4,
|
|
/* An inbound stream's first byte named its kind. `stream_id`, `stream_kind`. */
|
|
CULTMESH_QUIC_EVENT_STREAM_STARTED = 5,
|
|
/* Payload: one whole encoded frame, the 4-byte length prefix stripped. */
|
|
CULTMESH_QUIC_EVENT_STREAM_FRAME = 6,
|
|
/* One per `cultmesh_quic_stream_send_frame` call that returned 0, and never
|
|
* for the bridge's own kind byte. `code` is a cultmesh_quic_send_result. */
|
|
CULTMESH_QUIC_EVENT_STREAM_SEND_COMPLETE = 7,
|
|
/* The stream is gone. `stream_id`, `stream_kind`. */
|
|
CULTMESH_QUIC_EVENT_STREAM_SHUTDOWN = 8,
|
|
/* The listener stopped. `listener_id`. */
|
|
CULTMESH_QUIC_EVENT_LISTENER_STOPPED = 9
|
|
};
|
|
|
|
/* The `code` of event 7. A canceled send is a send MsQuic released without
|
|
* putting on the wire — the stream was aborted or the connection ended under it.
|
|
* It is reported, not silently dropped. */
|
|
enum cultmesh_quic_send_result {
|
|
CULTMESH_QUIC_SEND_COMPLETED = 0,
|
|
CULTMESH_QUIC_SEND_CANCELED = 1
|
|
};
|
|
|
|
/* The first byte of every CultMesh QUIC stream. */
|
|
enum cultmesh_quic_stream_kind {
|
|
CULTMESH_QUIC_STREAM_RELIABLE = 1,
|
|
CULTMESH_QUIC_STREAM_LATEST_ONLY = 2
|
|
};
|
|
|
|
/* ---------------------------------------------------------------------------
|
|
* 2. Ids
|
|
*
|
|
* Listeners, connections and streams are named by `uint64_t` ids drawn from one
|
|
* per-runtime counter that starts at 1 and never repeats. 0 is never a valid id
|
|
* and is what an out parameter holds when a call fails. An id is the only thing
|
|
* a host ever holds: there is no host-visible pointer to a connection or stream,
|
|
* so a host cannot outlive an object it is naming. An id whose object is gone is
|
|
* a bad call (-1), not undefined behaviour.
|
|
* --------------------------------------------------------------------------- */
|
|
|
|
/* ---------------------------------------------------------------------------
|
|
* 3. Return codes — negative on a bad call, on every platform
|
|
*
|
|
* Every int32_t-returning export returns:
|
|
*
|
|
* 0 the call succeeded
|
|
* 1, 2 `cultmesh_quic_next_event` only; see its comment
|
|
* -1 a bad call: a null handle, a null out parameter,
|
|
* or an id whose object is gone
|
|
* -2 an argument the bridge rejects on its own terms:
|
|
* an unknown stream kind, an oversized frame, a
|
|
* listener host that is not an IP literal
|
|
* <= -1000 MsQuic refused. The bridge code is
|
|
* -(1000 + (raw_status & 0xffff)); the raw
|
|
* platform status is available from
|
|
* `cultmesh_quic_last_status` and a sentence about
|
|
* it from `cultmesh_quic_last_error`.
|
|
*
|
|
* This mapping exists because `QUIC_STATUS` itself is not portable: it is a
|
|
* negative HRESULT on Windows and a positive errno on POSIX, so a host that
|
|
* checked for a negative value saw every Linux failure as success.
|
|
*
|
|
* What the mapping makes portable is the sign, and only the sign. A failure is
|
|
* negative everywhere, and that is what a host may branch on. The magnitude of a
|
|
* <= -1000 code is diagnostic and platform-specific: the same MsQuic failure
|
|
* carries different numbers on different platforms (a TLS error is -12032 on
|
|
* win32-x64 and -1126 on linux-x64; an ALPN already in use is -1009 against
|
|
* -1091), and the 16-bit mask can give two distinct statuses the same code
|
|
* (`QUIC_STATUS_USER_CANCELED` and `QUIC_STATUS_FILE_NOT_FOUND` are both -1002
|
|
* on Windows). Switching on a particular code is therefore not portable and not
|
|
* a reliable identification of the failure. Report it, log it, and read
|
|
* `cultmesh_quic_last_status` and `cultmesh_quic_last_error` beside it.
|
|
* --------------------------------------------------------------------------- */
|
|
|
|
#define CULTMESH_QUIC_RESULT_BAD_CALL (-1)
|
|
#define CULTMESH_QUIC_RESULT_BAD_ARGUMENT (-2)
|
|
#define CULTMESH_QUIC_RESULT_MSQUIC_BASE (-1000)
|
|
|
|
/* ---------------------------------------------------------------------------
|
|
* 4. Object lifetime — resolved from an id, kept alive across the call
|
|
*
|
|
* The bridge's objects are shared-owned. The runtime's maps hold one reference
|
|
* each; an export that names an id takes its own reference under the lock and
|
|
* holds it for the whole MsQuic call, so an object cannot be freed under a host
|
|
* thread that is using it. MsQuic's worker, on shutdown-complete, removes the
|
|
* map entry and drops the map's reference only — it never frees an object a host
|
|
* call is inside.
|
|
*
|
|
* The MsQuic handle follows the object: it is closed exactly once, by whichever
|
|
* holder drops the last reference (or by `cultmesh_quic_runtime_close`), under a
|
|
* one-shot exchange guard. A handle is therefore valid for as long as any holder
|
|
* has a reference to its object, which is the property the host thread needs. A
|
|
* stream holds a reference to its connection, so stream handles are always
|
|
* closed before the connection handle they belong to.
|
|
*
|
|
* `cultmesh_quic_runtime_close` quiesces before it tears anything down. It marks
|
|
* the runtime closing, wakes every blocked `cultmesh_quic_next_event`, and waits
|
|
* for every in-flight export to return before it closes a handle or frees a
|
|
* byte. No host call may begin after `cultmesh_quic_runtime_close` starts. That
|
|
* ordering is the host's, and it is a prohibition rather than a race the bridge
|
|
* arbitrates: once the close returns, the handle names freed memory, and a call
|
|
* made with it has nothing left to refuse it. A call that races the start of the
|
|
* close, while it is still running, is refused with -1 and touches nothing; that
|
|
* narrows one mis-ordering and does not make the prohibition optional.
|
|
* --------------------------------------------------------------------------- */
|
|
|
|
/* ---------------------------------------------------------------------------
|
|
* 5. Threading
|
|
*
|
|
* There are no host callbacks. Every crossing is the host calling in.
|
|
*
|
|
* - MsQuic's worker threads run inside this library only. They push events onto
|
|
* the runtime's queue and wake the one blocking wait. No host code is ever
|
|
* called from a thread the host does not own.
|
|
* - `cultmesh_quic_next_event` is called from host threads, one or many. It is
|
|
* the only blocking export. Concurrent callers each get a distinct event.
|
|
* - Every other export may be called from any host thread at any time, except
|
|
* during or after `cultmesh_quic_runtime_close`.
|
|
* - `cultmesh_quic_runtime_close` is called once, from one thread, and
|
|
* quiesces as described above.
|
|
* --------------------------------------------------------------------------- */
|
|
|
|
/* ---------------------------------------------------------------------------
|
|
* 6. The exports
|
|
* --------------------------------------------------------------------------- */
|
|
|
|
/* Opens a runtime: one MsQuic registration, one event queue. `app_name` may be
|
|
* null. `*out_runtime` is the opaque handle every other export takes. */
|
|
CULTMESH_QUIC_API int32_t cultmesh_quic_runtime_open(
|
|
const char* app_name, void** out_runtime);
|
|
|
|
/* Quiesces, tears down and frees the runtime. Safe to call with null. After it
|
|
* returns, the handle is dead; see section 4 for the ordering rule. */
|
|
CULTMESH_QUIC_API void cultmesh_quic_runtime_close(void* runtime);
|
|
|
|
/* Opens a provider listener on `host`:`port` with a PKCS12 credential. `host`
|
|
* may be null or empty for any address, otherwise it must be an IP literal.
|
|
* `port` 0 binds an ephemeral port, which `*out_bound_port` reports. A failure
|
|
* leaves no listener behind and no id allocated to the host. */
|
|
CULTMESH_QUIC_API int32_t cultmesh_quic_listener_open(
|
|
void* runtime, const char* host, uint16_t port,
|
|
const uint8_t* pkcs12, int32_t pkcs12_length, const char* password,
|
|
uint64_t* out_listener_id, uint16_t* out_bound_port);
|
|
|
|
/* Stops and closes a listener. Its accepted connections are not affected.
|
|
* Emits event 9. Unknown ids are ignored. */
|
|
CULTMESH_QUIC_API void cultmesh_quic_listener_close(
|
|
void* runtime, uint64_t listener_id);
|
|
|
|
/* Starts an outbound connection. Certificate validation is deferred: the host
|
|
* gets event 3 and must answer. */
|
|
CULTMESH_QUIC_API int32_t cultmesh_quic_connection_open(
|
|
void* runtime, const char* host, uint16_t port, uint64_t* out_connection_id);
|
|
|
|
/* Answers event 3. `accept` non-zero continues the handshake; zero fails it with
|
|
* a bad-certificate alert. */
|
|
CULTMESH_QUIC_API int32_t cultmesh_quic_connection_certificate_complete(
|
|
void* runtime, uint64_t connection_id, int32_t accept);
|
|
|
|
/* Ends a connection with a QUIC application error code. The initiator gets its
|
|
* own event 4 carrying `code`, as the peer does. Unknown ids are ignored. */
|
|
CULTMESH_QUIC_API void cultmesh_quic_connection_shutdown(
|
|
void* runtime, uint64_t connection_id, uint64_t code);
|
|
|
|
/* Opens an outbound unidirectional stream and puts `kind` on the wire as its
|
|
* first byte. That byte is the bridge's own and never produces an event 7. */
|
|
CULTMESH_QUIC_API int32_t cultmesh_quic_stream_open(
|
|
void* runtime, uint64_t connection_id, uint8_t kind, uint64_t* out_stream_id);
|
|
|
|
/* Queues one whole encoded frame, length-prefixed by the bridge. A call that
|
|
* returns 0 produces exactly one event 7 later, completed or canceled. */
|
|
CULTMESH_QUIC_API int32_t cultmesh_quic_stream_send_frame(
|
|
void* runtime, uint64_t stream_id,
|
|
const uint8_t* encoded_frame, int32_t length, int32_t fin);
|
|
|
|
/* Aborts a stream with a QUIC application error code. Unknown ids are ignored. */
|
|
CULTMESH_QUIC_API void cultmesh_quic_stream_shutdown(
|
|
void* runtime, uint64_t stream_id, uint64_t code);
|
|
|
|
/* The one blocking crossing.
|
|
*
|
|
* 0 nothing within `timeout_ms`. A poller already blocked here when
|
|
* `cultmesh_quic_runtime_close` starts is woken and returns 0. A poll that
|
|
* begins after the close starts is forbidden by section 4, and is not a
|
|
* case this return code covers.
|
|
* 1 `*out_event` filled and up to `payload_capacity` payload bytes copied;
|
|
* `*out_required` is the payload size
|
|
* 2 `payload_capacity` is too small. Nothing is consumed and nothing is
|
|
* written to `*out_event`; `*out_required` is the size to allocate. Ask
|
|
* again with a big enough buffer and the same event is delivered.
|
|
* -1 a bad call, including a null `payload` with a non-zero
|
|
* `payload_capacity`, which is a lie about the buffer and is refused
|
|
* rather than treated as a discard.
|
|
*
|
|
* `payload` may be null when `payload_capacity` is 0: that is the two-phase
|
|
* poll's first ask. */
|
|
CULTMESH_QUIC_API int32_t cultmesh_quic_next_event(
|
|
void* runtime, int32_t timeout_ms, struct cultmesh_quic_event* out_event,
|
|
uint8_t* payload, int32_t payload_capacity, int32_t* out_required);
|
|
|
|
/* The last error sentence, NUL-terminated, truncated to `capacity`. Returns the
|
|
* number of bytes written, or -1 on a bad call. */
|
|
CULTMESH_QUIC_API int32_t cultmesh_quic_last_error(
|
|
void* runtime, char* destination, int32_t capacity);
|
|
|
|
/* The raw platform `QUIC_STATUS` behind the last <= -1000 return, as its own
|
|
* bits: a negative HRESULT on Windows, a positive errno on POSIX. Diagnostics
|
|
* only; what is portable about the return code is its sign, not its value. 0
|
|
* when nothing has failed. */
|
|
CULTMESH_QUIC_API int32_t cultmesh_quic_last_status(void* runtime);
|
|
|
|
/* ---------------------------------------------------------------------------
|
|
* 7. The development seam
|
|
*
|
|
* Present only in a library configured with `CULTMESH_QUIC_DEBUG_ASSERTS`, which
|
|
* a shipped build never is. It exists for the runtime-lifetime scenarios: the
|
|
* quiesce is a promise about host calls that are inside the library, and the
|
|
* wait scenarios need the same kind of promise about the exact duration the
|
|
* bridge hands its own condition wait. A scenario that can only sleep and hope
|
|
* cannot tell a bridge that counts and waits correctly from one that does not.
|
|
*
|
|
* It is process-wide, not per-runtime, because the numbers outlive the runtime
|
|
* they describe: `cultmesh_quic_runtime_close` frees the runtime, and the count
|
|
* it waited on is read afterwards. The scenarios open one runtime at a time.
|
|
* --------------------------------------------------------------------------- */
|
|
#if defined(CULTMESH_QUIC_DEBUG_ASSERTS)
|
|
|
|
/* Non-zero arms the hold and resets both counters: every call that reaches the
|
|
* blocking wait in `cultmesh_quic_next_event` parks inside the library instead
|
|
* of returning. Zero releases every held call and disarms it. A held call
|
|
* returns 0 and touches the runtime no further. */
|
|
CULTMESH_QUIC_API void cultmesh_quic_debug_hold_calls(int32_t hold);
|
|
|
|
/* The high-water mark of host calls counted inside the library since the hold
|
|
* was armed. Zero means no call was ever counted. */
|
|
CULTMESH_QUIC_API int32_t cultmesh_quic_debug_peak_calls(void);
|
|
|
|
/* How many host calls were counted inside the library when the last
|
|
* `cultmesh_quic_runtime_close` began its wait, or -1 if no close has begun one
|
|
* since the hold was armed. This is the number the wait exists for. */
|
|
CULTMESH_QUIC_API int32_t cultmesh_quic_debug_calls_at_close(void);
|
|
|
|
/* The whole duration, in milliseconds, most recently handed to the condition
|
|
* wait inside `cultmesh_quic_next_event`, recorded at the point the wait is
|
|
* entered rather than observed from outside. A poll whose queue is not empty,
|
|
* or whose `timeout_ms` is not greater than zero, never enters that wait and
|
|
* leaves this unchanged. -1 until the first such wait, or after
|
|
* `cultmesh_quic_debug_reset_last_wait_ms` — tracked by a separate internal
|
|
* flag, not by -1 being stored here, so a wait that ever recorded exactly -1
|
|
* itself could not be confused with nothing having been recorded.
|
|
*
|
|
* This exists because a wall-clock probe cannot tell the host's timeout from
|
|
* any function of it that is the identity at the probe's own value — a floor,
|
|
* a round, a clamp, a later-poll fault, there is always another one that
|
|
* matches. This reads what the bridge actually handed its wait instead of
|
|
* inferring it from how long a call took. */
|
|
CULTMESH_QUIC_API int32_t cultmesh_quic_debug_last_wait_ms(void);
|
|
|
|
/* Resets the value `cultmesh_quic_debug_last_wait_ms` reports to -1, without
|
|
* touching the hold or the two call counters above. */
|
|
CULTMESH_QUIC_API void cultmesh_quic_debug_reset_last_wait_ms(void);
|
|
|
|
#endif /* CULTMESH_QUIC_DEBUG_ASSERTS */
|
|
|
|
#if defined(__cplusplus)
|
|
}
|
|
#endif
|
|
|
|
#endif /* CULTMESH_QUIC_NATIVE_H */
|