diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,6 @@
+# Changelog
+
+## 0.1.0.0 -- 2026-04-28
+
+* Initial release
+* Support for linked and dynamic loading via `Library` type
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,190 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to the Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   Copyright 2026 Daniel Goertzen
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,77 @@
+# s2n-tls-ffi
+
+Low-level FFI bindings to the [s2n-tls](https://github.com/aws/s2n-tls) library.
+
+This package provides raw FFI bindings which higher-level packages can build upon.
+
+### Using a Linked s2n-tls Library
+
+Call `withS2nTlsFfi` with `Linked` to use the linked-in s2n-tls library.
+
+```haskell
+import S2nTls.Ffi
+
+main :: IO ()
+main = withS2nTlsFfi Linked $ \ffi -> do
+    -- use ffi...
+```
+
+### Using a Dynamically Loaded s2n-tls library
+
+Call `withS2nTlsFfi` with `Dynamic path` to load s2n-tls at runtime. This enables runtime selection of different library builds - for example, switching between FIPS and non-FIPS crypto backends without recompiling your application.
+
+```haskell
+import S2nTls.Ffi
+
+main :: IO ()
+main = do
+    let lib = if needFips then Dynamic "libs2n-fips.so" else Dynamic "libs2n.so"
+    withS2nTlsFfi lib $ \ffi -> do
+        -- use ffi...
+```
+
+## Error Handling via C Wrappers
+
+s2n-tls stores error information in thread-local storage (TLS). When a function fails, the error code and debug message are available via `s2n_errno_location()` and `s2n_strerror_debug()`. However, Haskell's FFI provides no guarantee that a subsequent call will execute on the same OS thread, making direct access to thread-local error state unreliable.
+
+To solve this, most s2n functions are called through thin C wrappers that:
+
+1. Call the underlying s2n function
+2. If it fails, immediately read the error code and debug string from TLS
+3. Copy this information into an output struct with an owned 256-byte buffer
+4. Return to Haskell with the error information safely captured
+
+This ensures error information is captured in the same C stack frame before control returns to Haskell. The `S2nTlsFfi` record exposes these wrapped functions, which return `Either S2nError result` for functions that can fail.
+
+Note that `s2n_send` and `s2n_recv` have special behavior: if the error was `S2N_ERR_T_BLOCKED`, debug info is not captured because this is a frequent, expected case that doesn't need debug overhead.
+
+### Direct Error Functions
+
+The `s2n_strerror` and `s2n_strerror_name` functions are **not** subject to thread-local storage limitations - they take an error code as input and return static strings. These can be called directly without wrappers if you need to format error messages yourself.
+
+## Missing Symbol Behavior
+
+Symbol loading is forgiving - missing symbols don't cause failure at load time. This allows the bindings to work with different versions of s2n-tls that may not export all functions.
+
+- The `missingSymbols` field of `S2nTlsFfi` lists symbol names that couldn't be loaded
+- Calling a function for a missing symbol throws a `MissingSymbol` exception
+- Check `missingSymbols` at startup if your application requires specific functions
+
+## Tests
+
+The `memory-safety` test suite invokes all FFI functions to detect segfaults and memory errors. It does not verify correct behavior - only that calling each function doesn't crash. Each function name is printed before invocation, so if a crash occurs you can identify the culprit.
+
+Run with:
+
+```
+cabal test memory-safety
+```
+
+## Related Packages
+
+- [s2n-tls](https://github.com/goertzenator/s2n-tls) - High-level Haskell bindings built on this package
+- [warp-s2n-tls](https://github.com/goertzenator/warp-s2n-tls) - TLS support for Warp using s2n-tls
+
+## License
+
+Apache-2.0
diff --git a/cbits/s2n_wrapper.c b/cbits/s2n_wrapper.c
new file mode 100644
--- /dev/null
+++ b/cbits/s2n_wrapper.c
@@ -0,0 +1,3185 @@
+/*
+ * s2n_wrapper.c - C wrappers for s2n-tls functions
+ *
+ * These wrappers capture error information from thread-local storage
+ * immediately after s2n function calls, making error handling safe
+ * for Haskell FFI where TLS can be unreliable.
+ */
+
+#include "s2n_wrapper.h"
+#include <stddef.h>
+#include <stdio.h>
+
+/* ============================================================================
+ * Initialization & Cleanup
+ * ============================================================================ */
+
+int s2n_wrap_init(
+    int (*fn)(void),
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn();
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_cleanup(
+    int (*fn)(void),
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn();
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_cleanup_final(
+    int (*fn)(void),
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn();
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_crypto_disable_init(
+    int (*fn)(void),
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn();
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_disable_atexit(
+    int (*fn)(void),
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn();
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_get_fips_mode(
+    int (*fn)(int *),
+    int *fips_mode,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(fips_mode);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+/* ============================================================================
+ * Stack Traces
+ * ============================================================================ */
+
+int s2n_wrap_stack_traces_enabled_set(
+    int (*fn)(int),
+    int enabled,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(enabled);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_calculate_stacktrace(
+    int (*fn)(void),
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn();
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_free_stacktrace(
+    int (*fn)(void),
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn();
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_get_stacktrace(
+    int (*fn)(void *),
+    void *stacktrace,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(stacktrace);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+/* ============================================================================
+ * Config Management
+ * ============================================================================ */
+
+void *s2n_wrap_config_new(
+    void *(*fn)(void),
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    void *result = fn();
+    if (result == NULL)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+void *s2n_wrap_config_new_minimal(
+    void *(*fn)(void),
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    void *result = fn();
+    if (result == NULL)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_free(
+    int (*fn)(void *),
+    void *config,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_free_dhparams(
+    int (*fn)(void *),
+    void *config,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_free_cert_chain_and_key(
+    int (*fn)(void *),
+    void *config,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_set_wall_clock(
+    int (*fn)(void *, void *, void *),
+    void *config, void *clock_fn, void *ctx,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, clock_fn, ctx);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_set_monotonic_clock(
+    int (*fn)(void *, void *, void *),
+    void *config, void *clock_fn, void *ctx,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, clock_fn, ctx);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+/* ============================================================================
+ * Cache Callbacks
+ * ============================================================================ */
+
+int s2n_wrap_config_set_cache_store_callback(
+    int (*fn)(void *, void *, void *),
+    void *config, void *callback, void *ctx,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, callback, ctx);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_set_cache_retrieve_callback(
+    int (*fn)(void *, void *, void *),
+    void *config, void *callback, void *ctx,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, callback, ctx);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_set_cache_delete_callback(
+    int (*fn)(void *, void *, void *),
+    void *config, void *callback, void *ctx,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, callback, ctx);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+/* ============================================================================
+ * Memory & Random Callbacks
+ * ============================================================================ */
+
+int s2n_wrap_mem_set_callbacks(
+    int (*fn)(void *, void *, void *, void *),
+    void *init_cb, void *cleanup_cb, void *malloc_cb, void *free_cb,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(init_cb, cleanup_cb, malloc_cb, free_cb);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_rand_set_callbacks(
+    int (*fn)(void *, void *, void *, void *),
+    void *init_cb, void *cleanup_cb, void *seed_cb, void *mix_cb,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(init_cb, cleanup_cb, seed_cb, mix_cb);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+/* ============================================================================
+ * Certificate Chain Management
+ * ============================================================================ */
+
+void *s2n_wrap_cert_chain_and_key_new(
+    void *(*fn)(void),
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    void *result = fn();
+    if (result == NULL)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_cert_chain_and_key_load_pem(
+    int (*fn)(void *, const char *, const char *),
+    void *chain_and_key, const char *chain_pem, const char *private_key_pem,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(chain_and_key, chain_pem, private_key_pem);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_cert_chain_and_key_load_pem_bytes(
+    int (*fn)(void *, const uint8_t *, uint32_t, const uint8_t *, uint32_t),
+    void *chain_and_key, const uint8_t *chain_pem, uint32_t chain_pem_len,
+    const uint8_t *private_key_pem, uint32_t private_key_pem_len,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(chain_and_key, chain_pem, chain_pem_len, private_key_pem, private_key_pem_len);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_cert_chain_and_key_load_public_pem_bytes(
+    int (*fn)(void *, const uint8_t *, uint32_t),
+    void *chain_and_key, const uint8_t *chain_pem, uint32_t chain_pem_len,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(chain_and_key, chain_pem, chain_pem_len);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_cert_chain_and_key_free(
+    int (*fn)(void *),
+    void *chain_and_key,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(chain_and_key);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_cert_chain_and_key_set_ctx(
+    int (*fn)(void *, void *),
+    void *chain_and_key, void *ctx,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(chain_and_key, ctx);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+void *s2n_wrap_cert_chain_and_key_get_ctx(
+    void *(*fn)(void *),
+    void *chain_and_key,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    void *result = fn(chain_and_key);
+    if (result == NULL)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+void *s2n_wrap_cert_chain_and_key_get_private_key(
+    void *(*fn)(void *),
+    void *chain_and_key,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    void *result = fn(chain_and_key);
+    if (result == NULL)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_cert_chain_and_key_set_ocsp_data(
+    int (*fn)(void *, const uint8_t *, uint32_t),
+    void *chain_and_key, const uint8_t *data, uint32_t length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(chain_and_key, data, length);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_cert_chain_and_key_set_sct_list(
+    int (*fn)(void *, const uint8_t *, uint32_t),
+    void *chain_and_key, const uint8_t *data, uint32_t length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(chain_and_key, data, length);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_set_cert_tiebreak_callback(
+    int (*fn)(void *, void *),
+    void *config, void *callback,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, callback);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_add_cert_chain_and_key(
+    int (*fn)(void *, const char *, const char *),
+    void *config, const char *chain_pem, const char *private_key_pem,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, chain_pem, private_key_pem);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_add_cert_chain_and_key_to_store(
+    int (*fn)(void *, void *),
+    void *config, void *chain_and_key,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, chain_and_key);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_set_cert_chain_and_key_defaults(
+    int (*fn)(void *, void **, uint32_t),
+    void *config, void **chain_and_key_array, uint32_t count,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, chain_and_key_array, count);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+/* ============================================================================
+ * Trust Store
+ * ============================================================================ */
+
+int s2n_wrap_config_set_verification_ca_location(
+    int (*fn)(void *, const char *, const char *),
+    void *config, const char *ca_pem_filename, const char *ca_dir,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, ca_pem_filename, ca_dir);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_add_pem_to_trust_store(
+    int (*fn)(void *, const char *),
+    void *config, const char *pem,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, pem);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_wipe_trust_store(
+    int (*fn)(void *),
+    void *config,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_load_system_certs(
+    int (*fn)(void *),
+    void *config,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_set_cert_authorities_from_trust_store(
+    int (*fn)(void *),
+    void *config,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+/* ============================================================================
+ * Verification & Validation
+ * ============================================================================ */
+
+int s2n_wrap_config_set_verify_after_sign(
+    int (*fn)(void *, int),
+    void *config, int mode,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, mode);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_set_check_stapled_ocsp_response(
+    int (*fn)(void *, uint8_t),
+    void *config, uint8_t check,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, check);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_disable_x509_time_verification(
+    int (*fn)(void *),
+    void *config,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_disable_x509_verification(
+    int (*fn)(void *),
+    void *config,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_set_max_cert_chain_depth(
+    int (*fn)(void *, uint16_t),
+    void *config, uint16_t depth,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, depth);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_set_verify_host_callback(
+    int (*fn)(void *, void *, void *),
+    void *config, void *callback, void *ctx,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, callback, ctx);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+/* ============================================================================
+ * DH Parameters
+ * ============================================================================ */
+
+int s2n_wrap_config_add_dhparams(
+    int (*fn)(void *, const char *),
+    void *config, const char *dhparams_pem,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, dhparams_pem);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+/* ============================================================================
+ * Security Policies & Preferences
+ * ============================================================================ */
+
+int s2n_wrap_config_set_cipher_preferences(
+    int (*fn)(void *, const char *),
+    void *config, const char *version,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, version);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_append_protocol_preference(
+    int (*fn)(void *, const uint8_t *, uint8_t),
+    void *config, const uint8_t *protocol, uint8_t protocol_len,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, protocol, protocol_len);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_set_protocol_preferences(
+    int (*fn)(void *, const char **, int),
+    void *config, const char **protocols, int protocol_count,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, protocols, protocol_count);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_set_status_request_type(
+    int (*fn)(void *, int),
+    void *config, int type,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, type);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_set_ct_support_level(
+    int (*fn)(void *, int),
+    void *config, int level,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, level);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_set_alert_behavior(
+    int (*fn)(void *, int),
+    void *config, int behavior,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, behavior);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+/* ============================================================================
+ * Extension Data
+ * ============================================================================ */
+
+int s2n_wrap_config_set_extension_data(
+    int (*fn)(void *, int, const uint8_t *, uint32_t),
+    void *config, int extension_type, const uint8_t *data, uint32_t length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, extension_type, data, length);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_send_max_fragment_length(
+    int (*fn)(void *, int),
+    void *config, int mfl_code,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, mfl_code);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_accept_max_fragment_length(
+    int (*fn)(void *),
+    void *config,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+/* ============================================================================
+ * Session & Ticket Configuration
+ * ============================================================================ */
+
+int s2n_wrap_config_set_session_state_lifetime(
+    int (*fn)(void *, uint64_t),
+    void *config, uint64_t lifetime,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, lifetime);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_set_session_tickets_onoff(
+    int (*fn)(void *, uint8_t),
+    void *config, uint8_t enabled,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, enabled);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_set_session_cache_onoff(
+    int (*fn)(void *, uint8_t),
+    void *config, uint8_t enabled,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, enabled);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_set_ticket_encrypt_decrypt_key_lifetime(
+    int (*fn)(void *, uint64_t),
+    void *config, uint64_t lifetime,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, lifetime);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_set_ticket_decrypt_key_lifetime(
+    int (*fn)(void *, uint64_t),
+    void *config, uint64_t lifetime,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, lifetime);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_add_ticket_crypto_key(
+    int (*fn)(void *, const uint8_t *, uint32_t, const uint8_t *, uint32_t, uint64_t),
+    void *config, const uint8_t *name, uint32_t name_len,
+    const uint8_t *key, uint32_t key_len, uint64_t intro_time_in_seconds_from_epoch,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, name, name_len, key, key_len, intro_time_in_seconds_from_epoch);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_require_ticket_forward_secrecy(
+    int (*fn)(void *, int),
+    void *config, int enabled,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, enabled);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+/* ============================================================================
+ * Buffer & I/O Configuration
+ * ============================================================================ */
+
+int s2n_wrap_config_set_send_buffer_size(
+    int (*fn)(void *, uint32_t),
+    void *config, uint32_t size,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, size);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_set_recv_multi_record(
+    int (*fn)(void *, int),
+    void *config, int enabled,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, enabled);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+/* ============================================================================
+ * Miscellaneous Config
+ * ============================================================================ */
+
+int s2n_wrap_config_set_ctx(
+    int (*fn)(void *, void *),
+    void *config, void *ctx,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, ctx);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_get_ctx(
+    int (*fn)(void *, void **),
+    void *config, void **ctx,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, ctx);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_set_client_hello_cb(
+    int (*fn)(void *, void *, void *),
+    void *config, void *callback, void *ctx,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, callback, ctx);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_set_client_hello_cb_mode(
+    int (*fn)(void *, int),
+    void *config, int mode,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, mode);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_set_max_blinding_delay(
+    int (*fn)(void *, uint32_t),
+    void *config, uint32_t max_delay,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, max_delay);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_get_client_auth_type(
+    int (*fn)(void *, int *),
+    void *config, int *auth_type,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, auth_type);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_set_client_auth_type(
+    int (*fn)(void *, int),
+    void *config, int auth_type,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, auth_type);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_set_initial_ticket_count(
+    int (*fn)(void *, uint8_t),
+    void *config, uint8_t count,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, count);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_set_psk_mode(
+    int (*fn)(void *, int),
+    void *config, int mode,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, mode);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_set_psk_selection_callback(
+    int (*fn)(void *, void *, void *),
+    void *config, void *callback, void *ctx,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, callback, ctx);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_set_async_pkey_callback(
+    int (*fn)(void *, void *),
+    void *config, void *callback,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, callback);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_set_async_pkey_validation_mode(
+    int (*fn)(void *, int),
+    void *config, int mode,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, mode);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_set_session_ticket_cb(
+    int (*fn)(void *, void *, void *),
+    void *config, void *callback, void *ctx,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, callback, ctx);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_set_key_log_cb(
+    int (*fn)(void *, void *, void *),
+    void *config, void *callback, void *ctx,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, callback, ctx);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_enable_cert_req_dss_legacy_compat(
+    int (*fn)(void *),
+    void *config,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_set_server_max_early_data_size(
+    int (*fn)(void *, uint32_t),
+    void *config, uint32_t size,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, size);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_set_early_data_cb(
+    int (*fn)(void *, void *),
+    void *config, void *callback,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, callback);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_get_supported_groups(
+    int (*fn)(void *, uint16_t *, uint16_t, uint16_t *),
+    void *config, uint16_t *groups, uint16_t groups_count, uint16_t *groups_count_out,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, groups, groups_count, groups_count_out);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_config_set_serialization_version(
+    int (*fn)(void *, int),
+    void *config, int version,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(config, version);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+/* ============================================================================
+ * Connection Creation & Management
+ * ============================================================================ */
+
+void *s2n_wrap_connection_new(
+    void *(*fn)(int),
+    int mode,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    void *result = fn(mode);
+    if (result == NULL)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_set_config(
+    int (*fn)(void *, void *),
+    void *conn, void *config,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, config);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_set_ctx(
+    int (*fn)(void *, void *),
+    void *conn, void *ctx,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, ctx);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+void *s2n_wrap_connection_get_ctx(
+    void *(*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    void *result = fn(conn);
+    if (result == NULL)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_client_hello_cb_done(
+    int (*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_server_name_extension_used(
+    int (*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+/* ============================================================================
+ * Client Hello Access
+ * ============================================================================ */
+
+void *s2n_wrap_connection_get_client_hello(
+    void *(*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    void *result = fn(conn);
+    if (result == NULL)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+void *s2n_wrap_client_hello_parse_message(
+    void *(*fn)(const uint8_t *, uint32_t),
+    const uint8_t *data, uint32_t length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    void *result = fn(data, length);
+    if (result == NULL)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_client_hello_free(
+    int (*fn)(void **),
+    void **client_hello,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(client_hello);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+ssize_t s2n_wrap_client_hello_get_raw_message_length(
+    ssize_t (*fn)(void *),
+    void *client_hello,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    ssize_t result = fn(client_hello);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+ssize_t s2n_wrap_client_hello_get_raw_message(
+    ssize_t (*fn)(void *, uint8_t *, uint32_t),
+    void *client_hello, uint8_t *out, uint32_t max_length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    ssize_t result = fn(client_hello, out, max_length);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+ssize_t s2n_wrap_client_hello_get_cipher_suites_length(
+    ssize_t (*fn)(void *),
+    void *client_hello,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    ssize_t result = fn(client_hello);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+ssize_t s2n_wrap_client_hello_get_cipher_suites(
+    ssize_t (*fn)(void *, uint8_t *, uint32_t),
+    void *client_hello, uint8_t *out, uint32_t max_length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    ssize_t result = fn(client_hello, out, max_length);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+ssize_t s2n_wrap_client_hello_get_extensions_length(
+    ssize_t (*fn)(void *),
+    void *client_hello,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    ssize_t result = fn(client_hello);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+ssize_t s2n_wrap_client_hello_get_extensions(
+    ssize_t (*fn)(void *, uint8_t *, uint32_t),
+    void *client_hello, uint8_t *out, uint32_t max_length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    ssize_t result = fn(client_hello, out, max_length);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+ssize_t s2n_wrap_client_hello_get_extension_length(
+    ssize_t (*fn)(void *, int),
+    void *client_hello, int extension_type,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    ssize_t result = fn(client_hello, extension_type);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+ssize_t s2n_wrap_client_hello_get_extension_by_id(
+    ssize_t (*fn)(void *, int, uint8_t *, uint32_t),
+    void *client_hello, int extension_type, uint8_t *out, uint32_t max_length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    ssize_t result = fn(client_hello, extension_type, out, max_length);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_client_hello_has_extension(
+    int (*fn)(void *, uint16_t, int *),
+    void *client_hello, uint16_t extension_iana, int *exists,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(client_hello, extension_iana, exists);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_client_hello_get_session_id_length(
+    int (*fn)(void *, uint32_t *),
+    void *client_hello, uint32_t *length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(client_hello, length);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_client_hello_get_session_id(
+    int (*fn)(void *, uint8_t *, uint32_t *, uint32_t),
+    void *client_hello, uint8_t *out, uint32_t *out_length, uint32_t max_length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(client_hello, out, out_length, max_length);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_client_hello_get_compression_methods_length(
+    int (*fn)(void *, uint32_t *),
+    void *client_hello, uint32_t *length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(client_hello, length);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_client_hello_get_compression_methods(
+    int (*fn)(void *, uint8_t *, uint32_t, uint32_t *),
+    void *client_hello, uint8_t *out, uint32_t max_length, uint32_t *out_length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(client_hello, out, max_length, out_length);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_client_hello_get_legacy_protocol_version(
+    int (*fn)(void *, uint8_t *),
+    void *client_hello, uint8_t *version,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(client_hello, version);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_client_hello_get_supported_groups(
+    int (*fn)(void *, uint16_t *, uint16_t, uint16_t *),
+    void *client_hello, uint16_t *groups, uint16_t groups_count, uint16_t *groups_count_out,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(client_hello, groups, groups_count, groups_count_out);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_client_hello_get_server_name_length(
+    int (*fn)(void *, uint16_t *),
+    void *client_hello, uint16_t *length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(client_hello, length);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_client_hello_get_server_name(
+    int (*fn)(void *, uint8_t *, uint16_t, uint16_t *),
+    void *client_hello, uint8_t *out, uint16_t max_length, uint16_t *out_length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(client_hello, out, max_length, out_length);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_client_hello_get_legacy_record_version(
+    int (*fn)(void *, uint8_t *),
+    void *client_hello, uint8_t *version,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(client_hello, version);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+/* ============================================================================
+ * File Descriptor & I/O
+ * ============================================================================ */
+
+int s2n_wrap_connection_set_fd(
+    int (*fn)(void *, int),
+    void *conn, int fd,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, fd);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_set_read_fd(
+    int (*fn)(void *, int),
+    void *conn, int fd,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, fd);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_set_write_fd(
+    int (*fn)(void *, int),
+    void *conn, int fd,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, fd);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_get_read_fd(
+    int (*fn)(void *, int *),
+    void *conn, int *fd,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, fd);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_get_write_fd(
+    int (*fn)(void *, int *),
+    void *conn, int *fd,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, fd);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_use_corked_io(
+    int (*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_set_recv_ctx(
+    int (*fn)(void *, void *),
+    void *conn, void *ctx,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, ctx);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_set_send_ctx(
+    int (*fn)(void *, void *),
+    void *conn, void *ctx,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, ctx);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_set_recv_cb(
+    int (*fn)(void *, void *),
+    void *conn, void *callback,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, callback);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_set_send_cb(
+    int (*fn)(void *, void *),
+    void *conn, void *callback,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, callback);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+/* ============================================================================
+ * Connection Preferences
+ * ============================================================================ */
+
+int s2n_wrap_connection_prefer_throughput(
+    int (*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_prefer_low_latency(
+    int (*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_set_recv_buffering(
+    int (*fn)(void *, int),
+    void *conn, int enabled,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, enabled);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_set_dynamic_buffers(
+    int (*fn)(void *, int),
+    void *conn, int enabled,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, enabled);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_set_dynamic_record_threshold(
+    int (*fn)(void *, uint32_t, uint16_t),
+    void *conn, uint32_t resize_threshold, uint16_t timeout_threshold,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, resize_threshold, timeout_threshold);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+/* ============================================================================
+ * Host Verification
+ * ============================================================================ */
+
+int s2n_wrap_connection_set_verify_host_callback(
+    int (*fn)(void *, void *, void *),
+    void *conn, void *callback, void *ctx,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, callback, ctx);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+/* ============================================================================
+ * Blinding & Security
+ * ============================================================================ */
+
+int s2n_wrap_connection_set_blinding(
+    int (*fn)(void *, int),
+    void *conn, int blinding,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, blinding);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+/* ============================================================================
+ * Cipher & Protocol Configuration
+ * ============================================================================ */
+
+int s2n_wrap_connection_set_cipher_preferences(
+    int (*fn)(void *, const char *),
+    void *conn, const char *version,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, version);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_request_key_update(
+    int (*fn)(void *, int),
+    void *conn, int peer_request,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, peer_request);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_append_protocol_preference(
+    int (*fn)(void *, const uint8_t *, uint8_t),
+    void *conn, const uint8_t *protocol, uint8_t protocol_len,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, protocol, protocol_len);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_set_protocol_preferences(
+    int (*fn)(void *, const char **, int),
+    void *conn, const char **protocols, int protocol_count,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, protocols, protocol_count);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+/* ============================================================================
+ * Server Name (SNI)
+ * ============================================================================ */
+
+int s2n_wrap_set_server_name(
+    int (*fn)(void *, const char *),
+    void *conn, const char *server_name,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, server_name);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+const char *s2n_wrap_get_server_name(
+    const char *(*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    const char *result = fn(conn);
+    if (result == NULL)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+/* ============================================================================
+ * Application Protocol (ALPN)
+ * ============================================================================ */
+
+const char *s2n_wrap_get_application_protocol(
+    const char *(*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    const char *result = fn(conn);
+    if (result == NULL)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+/* ============================================================================
+ * OCSP & Certificate Transparency
+ * ============================================================================ */
+
+const uint8_t *s2n_wrap_connection_get_ocsp_response(
+    const uint8_t *(*fn)(void *, uint32_t *),
+    void *conn, uint32_t *length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    const uint8_t *result = fn(conn, length);
+    if (result == NULL)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+const uint8_t *s2n_wrap_connection_get_sct_list(
+    const uint8_t *(*fn)(void *, uint32_t *),
+    void *conn, uint32_t *length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    const uint8_t *result = fn(conn, length);
+    if (result == NULL)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+/* ============================================================================
+ * Handshake & TLS Operations
+ * ============================================================================ */
+
+int s2n_wrap_negotiate(
+    int (*fn)(void *, int *),
+    void *conn, int *blocked,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, blocked);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+ssize_t s2n_wrap_send(
+    ssize_t (*fn)(void *, const void *, ssize_t, int *),
+    void *conn, const void *buf, ssize_t size, int *blocked,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    ssize_t result = fn(conn, buf, size, blocked);
+    int err = *(err_funcs->errno_location)();
+    bool is_blocked = err_funcs->error_get_type(err) == 3; // S2N_ERR_T_BLOCKED==3
+    if (result < 0)
+    {
+        S2N_FILL_ERROR_MAYBE(err_funcs, err_out, !is_blocked);
+    }
+    return result;
+}
+
+ssize_t s2n_wrap_recv(
+    ssize_t (*fn)(void *, void *, ssize_t, int *),
+    void *conn, void *buf, ssize_t size, int *blocked,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    ssize_t result = fn(conn, buf, size, blocked);
+    int err = *(err_funcs->errno_location)();
+    bool is_blocked = err_funcs->error_get_type(err) == 3; // S2N_ERR_T_BLOCKED==3
+    if (result < 0)
+    {
+        S2N_FILL_ERROR_MAYBE(err_funcs, err_out, !is_blocked);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_free_handshake(
+    int (*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_release_buffers(
+    int (*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_wipe(
+    int (*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_free(
+    int (*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_shutdown(
+    int (*fn)(void *, int *),
+    void *conn, int *blocked,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, blocked);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_shutdown_send(
+    int (*fn)(void *, int *),
+    void *conn, int *blocked,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, blocked);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+/* ============================================================================
+ * Client Authentication
+ * ============================================================================ */
+
+int s2n_wrap_connection_get_client_auth_type(
+    int (*fn)(void *, int *),
+    void *conn, int *auth_type,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, auth_type);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_set_client_auth_type(
+    int (*fn)(void *, int),
+    void *conn, int auth_type,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, auth_type);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_get_client_cert_chain(
+    int (*fn)(void *, const uint8_t **, uint32_t *),
+    void *conn, const uint8_t **cert_chain, uint32_t *cert_chain_length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, cert_chain, cert_chain_length);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_client_cert_used(
+    int (*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+/* ============================================================================
+ * Session Management
+ * ============================================================================ */
+
+int s2n_wrap_connection_add_new_tickets_to_send(
+    int (*fn)(void *, uint8_t),
+    void *conn, uint8_t num_tickets,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, num_tickets);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_get_tickets_sent(
+    int (*fn)(void *, uint16_t *),
+    void *conn, uint16_t *tickets_sent,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, tickets_sent);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_set_server_keying_material_lifetime(
+    int (*fn)(void *, uint32_t),
+    void *conn, uint32_t lifetime,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, lifetime);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_session_ticket_get_data_len(
+    int (*fn)(void *, size_t *),
+    void *ticket, size_t *length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(ticket, length);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_session_ticket_get_data(
+    int (*fn)(void *, size_t, uint8_t *),
+    void *ticket, size_t max_length, uint8_t *data,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(ticket, max_length, data);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_session_ticket_get_lifetime(
+    int (*fn)(void *, uint32_t *),
+    void *ticket, uint32_t *lifetime,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(ticket, lifetime);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_set_session(
+    int (*fn)(void *, const uint8_t *, size_t),
+    void *conn, const uint8_t *session, size_t length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, session, length);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_get_session(
+    int (*fn)(void *, uint8_t *, size_t),
+    void *conn, uint8_t *session, size_t max_length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, session, max_length);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_get_session_ticket_lifetime_hint(
+    int (*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_get_session_length(
+    int (*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_get_session_id_length(
+    int (*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_get_session_id(
+    int (*fn)(void *, uint8_t *, size_t),
+    void *conn, uint8_t *session_id, size_t max_length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, session_id, max_length);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_is_session_resumed(
+    int (*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+/* ============================================================================
+ * Certificate Information
+ * ============================================================================ */
+
+int s2n_wrap_connection_is_ocsp_stapled(
+    int (*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_get_selected_signature_algorithm(
+    int (*fn)(void *, int *),
+    void *conn, int *sig_alg,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, sig_alg);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_get_selected_digest_algorithm(
+    int (*fn)(void *, int *),
+    void *conn, int *hash_alg,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, hash_alg);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_get_selected_client_cert_signature_algorithm(
+    int (*fn)(void *, int *),
+    void *conn, int *sig_alg,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, sig_alg);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_get_selected_client_cert_digest_algorithm(
+    int (*fn)(void *, int *),
+    void *conn, int *hash_alg,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, hash_alg);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+void *s2n_wrap_connection_get_selected_cert(
+    void *(*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    void *result = fn(conn);
+    if (result == NULL)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_cert_chain_get_length(
+    int (*fn)(void *, uint32_t *),
+    void *chain, uint32_t *length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(chain, length);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_cert_chain_get_cert(
+    int (*fn)(void *, void **, uint32_t),
+    void *chain, void **cert, uint32_t index,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(chain, cert, index);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_cert_get_der(
+    int (*fn)(void *, const uint8_t **, uint32_t *),
+    void *cert, const uint8_t **der, uint32_t *length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(cert, der, length);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_get_peer_cert_chain(
+    int (*fn)(void *, void *),
+    void *conn, void *cert_chain,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, cert_chain);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_cert_get_x509_extension_value_length(
+    int (*fn)(void *, const uint8_t *, uint32_t *),
+    void *cert, const uint8_t *oid, uint32_t *length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(cert, oid, length);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_cert_get_x509_extension_value(
+    int (*fn)(void *, const uint8_t *, uint8_t *, uint32_t *, int *),
+    void *cert, const uint8_t *oid, uint8_t *value, uint32_t *length, int *critical,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(cert, oid, value, length, critical);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_cert_get_utf8_string_from_extension_data_length(
+    int (*fn)(const uint8_t *, uint32_t, uint32_t *),
+    const uint8_t *data, uint32_t data_length, uint32_t *utf8_length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(data, data_length, utf8_length);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_cert_get_utf8_string_from_extension_data(
+    int (*fn)(const uint8_t *, uint32_t, uint8_t *, uint32_t *),
+    const uint8_t *data, uint32_t data_length, uint8_t *utf8, uint32_t *utf8_length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(data, data_length, utf8, utf8_length);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+/* ============================================================================
+ * Pre-Shared Keys (PSK)
+ * ============================================================================ */
+
+void *s2n_wrap_external_psk_new(
+    void *(*fn)(void),
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    void *result = fn();
+    if (result == NULL)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_psk_free(
+    int (*fn)(void **),
+    void **psk,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(psk);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_psk_set_identity(
+    int (*fn)(void *, const uint8_t *, uint16_t),
+    void *psk, const uint8_t *identity, uint16_t length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(psk, identity, length);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_psk_set_secret(
+    int (*fn)(void *, const uint8_t *, uint16_t),
+    void *psk, const uint8_t *secret, uint16_t length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(psk, secret, length);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_psk_set_hmac(
+    int (*fn)(void *, int),
+    void *psk, int hmac,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(psk, hmac);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_append_psk(
+    int (*fn)(void *, void *),
+    void *conn, void *psk,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, psk);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_set_psk_mode(
+    int (*fn)(void *, int),
+    void *conn, int mode,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, mode);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_get_negotiated_psk_identity_length(
+    int (*fn)(void *, uint16_t *),
+    void *conn, uint16_t *length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, length);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_get_negotiated_psk_identity(
+    int (*fn)(void *, uint8_t *, uint16_t),
+    void *conn, uint8_t *identity, uint16_t max_length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, identity, max_length);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+void *s2n_wrap_offered_psk_new(
+    void *(*fn)(void),
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    void *result = fn();
+    if (result == NULL)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_offered_psk_free(
+    int (*fn)(void **),
+    void **psk,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(psk);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_offered_psk_get_identity(
+    int (*fn)(void *, uint8_t **, uint16_t *),
+    void *psk, uint8_t **identity, uint16_t *length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(psk, identity, length);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_offered_psk_list_next(
+    int (*fn)(void *, void *),
+    void *psk_list, void *psk,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(psk_list, psk);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_offered_psk_list_reread(
+    int (*fn)(void *),
+    void *psk_list,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(psk_list);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_offered_psk_list_choose_psk(
+    int (*fn)(void *, void *),
+    void *psk_list, void *psk,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(psk_list, psk);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_psk_configure_early_data(
+    int (*fn)(void *, uint32_t, uint8_t, uint8_t),
+    void *psk, uint32_t max_early_data_size, uint8_t cipher_suite_first_byte, uint8_t cipher_suite_second_byte,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(psk, max_early_data_size, cipher_suite_first_byte, cipher_suite_second_byte);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_psk_set_application_protocol(
+    int (*fn)(void *, const uint8_t *, uint8_t),
+    void *psk, const uint8_t *protocol, uint8_t length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(psk, protocol, length);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_psk_set_early_data_context(
+    int (*fn)(void *, const uint8_t *, uint16_t),
+    void *psk, const uint8_t *context, uint16_t length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(psk, context, length);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+/* ============================================================================
+ * Protocol Version Information
+ * ============================================================================ */
+
+int s2n_wrap_connection_get_client_protocol_version(
+    int (*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_get_server_protocol_version(
+    int (*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_get_actual_protocol_version(
+    int (*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_get_client_hello_version(
+    int (*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+/* ============================================================================
+ * Cipher & Security Information
+ * ============================================================================ */
+
+const char *s2n_wrap_connection_get_cipher(
+    const char *(*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    const char *result = fn(conn);
+    if (result == NULL)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_get_certificate_match(
+    int (*fn)(void *, int *),
+    void *conn, int *cert_match,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, cert_match);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_get_master_secret(
+    int (*fn)(void *, uint8_t *, size_t),
+    void *conn, uint8_t *secret, size_t max_length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, secret, max_length);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_tls_exporter(
+    int (*fn)(void *, const uint8_t *, uint32_t, const uint8_t *, uint32_t, uint8_t *, uint32_t),
+    void *conn, const uint8_t *label, uint32_t label_length,
+    const uint8_t *context, uint32_t context_length,
+    uint8_t *output, uint32_t output_length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, label, label_length, context, context_length, output, output_length);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_get_cipher_iana_value(
+    int (*fn)(void *, uint8_t *, uint8_t *),
+    void *conn, uint8_t *first, uint8_t *second,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, first, second);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_is_valid_for_cipher_preferences(
+    int (*fn)(void *, const char *),
+    void *conn, const char *version,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, version);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+const char *s2n_wrap_connection_get_curve(
+    const char *(*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    const char *result = fn(conn);
+    if (result == NULL)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+const char *s2n_wrap_connection_get_kem_name(
+    const char *(*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    const char *result = fn(conn);
+    if (result == NULL)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+const char *s2n_wrap_connection_get_kem_group_name(
+    const char *(*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    const char *result = fn(conn);
+    if (result == NULL)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_get_key_exchange_group(
+    int (*fn)(void *, const char **),
+    void *conn, const char **group_name,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, group_name);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_get_alert(
+    int (*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+const char *s2n_wrap_connection_get_handshake_type_name(
+    const char *(*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    const char *result = fn(conn);
+    if (result == NULL)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+const char *s2n_wrap_connection_get_last_message_name(
+    const char *(*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    const char *result = fn(conn);
+    if (result == NULL)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+/* ============================================================================
+ * Async Private Key Operations
+ * ============================================================================ */
+
+int s2n_wrap_async_pkey_op_perform(
+    int (*fn)(void *, void *),
+    void *op, void *key,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(op, key);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_async_pkey_op_apply(
+    int (*fn)(void *, void *),
+    void *op, void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(op, conn);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_async_pkey_op_free(
+    int (*fn)(void *),
+    void *op,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(op);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_async_pkey_op_get_op_type(
+    int (*fn)(void *, int *),
+    void *op, int *op_type,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(op, op_type);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_async_pkey_op_get_input_size(
+    int (*fn)(void *, uint32_t *),
+    void *op, uint32_t *size,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(op, size);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_async_pkey_op_get_input(
+    int (*fn)(void *, uint8_t *, uint32_t),
+    void *op, uint8_t *data, uint32_t length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(op, data, length);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_async_pkey_op_set_output(
+    int (*fn)(void *, const uint8_t *, uint32_t),
+    void *op, const uint8_t *data, uint32_t length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(op, data, length);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+/* ============================================================================
+ * Early Data
+ * ============================================================================ */
+
+int s2n_wrap_connection_set_server_max_early_data_size(
+    int (*fn)(void *, uint32_t),
+    void *conn, uint32_t size,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, size);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_set_server_early_data_context(
+    int (*fn)(void *, const uint8_t *, uint16_t),
+    void *conn, const uint8_t *context, uint16_t length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, context, length);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_get_early_data_status(
+    int (*fn)(void *, int *),
+    void *conn, int *status,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, status);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_get_remaining_early_data_size(
+    int (*fn)(void *, uint32_t *),
+    void *conn, uint32_t *size,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, size);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_get_max_early_data_size(
+    int (*fn)(void *, uint32_t *),
+    void *conn, uint32_t *size,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, size);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_send_early_data(
+    int (*fn)(void *, const uint8_t *, ssize_t, ssize_t *, int *),
+    void *conn, const uint8_t *data, ssize_t data_len, ssize_t *written, int *blocked,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, data, data_len, written, blocked);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_recv_early_data(
+    int (*fn)(void *, uint8_t *, ssize_t, ssize_t *, int *),
+    void *conn, uint8_t *data, ssize_t max_data_len, ssize_t *read, int *blocked,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, data, max_data_len, read, blocked);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_offered_early_data_get_context_length(
+    int (*fn)(void *, uint16_t *),
+    void *early_data, uint16_t *length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(early_data, length);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_offered_early_data_get_context(
+    int (*fn)(void *, uint8_t *, uint16_t),
+    void *early_data, uint8_t *context, uint16_t max_length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(early_data, context, max_length);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_offered_early_data_reject(
+    int (*fn)(void *),
+    void *early_data,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(early_data);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_offered_early_data_accept(
+    int (*fn)(void *),
+    void *early_data,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(early_data);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+/* ============================================================================
+ * Connection Serialization
+ * ============================================================================ */
+
+int s2n_wrap_connection_serialization_length(
+    int (*fn)(void *, uint32_t *),
+    void *conn, uint32_t *length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, length);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_serialize(
+    int (*fn)(void *, uint8_t *, uint32_t),
+    void *conn, uint8_t *buffer, uint32_t length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, buffer, length);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
+
+int s2n_wrap_connection_deserialize(
+    int (*fn)(void *, const uint8_t *, uint32_t),
+    void *conn, const uint8_t *buffer, uint32_t length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
+{
+    int result = fn(conn, buffer, length);
+    if (result < 0)
+    {
+        S2N_FILL_ERROR(err_funcs, err_out);
+    }
+    return result;
+}
diff --git a/cbits/s2n_wrapper.h b/cbits/s2n_wrapper.h
new file mode 100644
--- /dev/null
+++ b/cbits/s2n_wrapper.h
@@ -0,0 +1,1294 @@
+/*
+ * s2n_wrapper.h - C wrappers for s2n-tls functions
+ *
+ * These wrappers capture error information from thread-local storage
+ * immediately after s2n function calls, making error handling safe
+ * for Haskell FFI where TLS can be unreliable.
+ */
+
+#ifndef S2N_WRAPPER_H
+#define S2N_WRAPPER_H
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <sys/types.h>
+
+/* Size of the owned debug string buffer in S2nErrorInfo */
+#define S2N_ERROR_DEBUG_STRING_SIZE 256
+
+/*
+ * Error Functions Struct
+ * Holds pointers to s2n error functions, loaded first during initialization.
+ * These are used by all wrappers to capture error information.
+ */
+typedef struct
+{
+    int *(*errno_location)(void);
+    const char *(*strerror_debug)(int error, const char *lang);
+    int (*error_get_type)(int error);
+} S2nErrorFuncs;
+
+/*
+ * Error Info Output Struct
+ * Populated by wrappers on failure with full error information.
+ * Uses an owned buffer for the debug string since s2n's string pointers
+ * are ephemeral and may become invalid.
+ */
+typedef struct
+{
+    int error_code;
+    char debug_string[S2N_ERROR_DEBUG_STRING_SIZE];
+} S2nErrorInfo;
+
+/*
+ * Helper macro to populate error info on failure.
+ * Copies the debug string into the owned buffer.
+ */
+#define S2N_FILL_ERROR_MAYBE(err_funcs, err_out, do_dbg)                             \
+    do                                                                               \
+    {                                                                                \
+        (err_out)->error_code = *(err_funcs)->errno_location();                      \
+        const char *_dbg = (err_funcs)->strerror_debug((err_out)->error_code, "EN"); \
+        if (_dbg && (do_dbg))                                                        \
+        {                                                                            \
+            strncpy((err_out)->debug_string, _dbg, S2N_ERROR_DEBUG_STRING_SIZE - 1); \
+            (err_out)->debug_string[S2N_ERROR_DEBUG_STRING_SIZE - 1] = '\0';         \
+        }                                                                            \
+        else                                                                         \
+        {                                                                            \
+            (err_out)->debug_string[0] = '\0';                                       \
+        }                                                                            \
+    } while (0)
+
+/*
+ * Helper macro to populate error info on failure.
+ * Copies the debug string into the owned buffer.
+ */
+#define S2N_FILL_ERROR(err_funcs, err_out) S2N_FILL_ERROR_MAYBE(err_funcs, err_out, true)
+
+/* ============================================================================
+ * Wrapper Function Declarations
+ * ============================================================================
+ * Each wrapper takes:
+ * - Function pointer to the underlying s2n function
+ * - Same arguments as the original function
+ * - Pointer to S2nErrorFuncs struct
+ * - Pointer to S2nErrorInfo output struct
+ * Returns the same type as the underlying function.
+ */
+
+/* --- Initialization & Cleanup --- */
+int s2n_wrap_init(
+    int (*fn)(void),
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_cleanup(
+    int (*fn)(void),
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_cleanup_final(
+    int (*fn)(void),
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_crypto_disable_init(
+    int (*fn)(void),
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_disable_atexit(
+    int (*fn)(void),
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+/* s2n_get_openssl_version returns long, no error - no wrapper needed */
+/* s2n_get_fips_mode takes output param */
+int s2n_wrap_get_fips_mode(
+    int (*fn)(int *),
+    int *fips_mode,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+/* --- Stack Traces --- */
+/* s2n_stack_traces_enabled returns bool, no wrapper needed */
+
+int s2n_wrap_stack_traces_enabled_set(
+    int (*fn)(int),
+    int enabled,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_calculate_stacktrace(
+    int (*fn)(void),
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_free_stacktrace(
+    int (*fn)(void),
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_get_stacktrace(
+    int (*fn)(void *),
+    void *stacktrace,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+/* --- Config Management --- */
+void *s2n_wrap_config_new(
+    void *(*fn)(void),
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+void *s2n_wrap_config_new_minimal(
+    void *(*fn)(void),
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_free(
+    int (*fn)(void *),
+    void *config,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_free_dhparams(
+    int (*fn)(void *),
+    void *config,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_free_cert_chain_and_key(
+    int (*fn)(void *),
+    void *config,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_set_wall_clock(
+    int (*fn)(void *, void *, void *),
+    void *config, void *clock_fn, void *ctx,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_set_monotonic_clock(
+    int (*fn)(void *, void *, void *),
+    void *config, void *clock_fn, void *ctx,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+/* --- Cache Callbacks --- */
+int s2n_wrap_config_set_cache_store_callback(
+    int (*fn)(void *, void *, void *),
+    void *config, void *callback, void *ctx,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_set_cache_retrieve_callback(
+    int (*fn)(void *, void *, void *),
+    void *config, void *callback, void *ctx,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_set_cache_delete_callback(
+    int (*fn)(void *, void *, void *),
+    void *config, void *callback, void *ctx,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+/* --- Memory & Random Callbacks --- */
+int s2n_wrap_mem_set_callbacks(
+    int (*fn)(void *, void *, void *, void *),
+    void *init_cb, void *cleanup_cb, void *malloc_cb, void *free_cb,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_rand_set_callbacks(
+    int (*fn)(void *, void *, void *, void *),
+    void *init_cb, void *cleanup_cb, void *seed_cb, void *mix_cb,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+/* --- Certificate Chain Management --- */
+void *s2n_wrap_cert_chain_and_key_new(
+    void *(*fn)(void),
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_cert_chain_and_key_load_pem(
+    int (*fn)(void *, const char *, const char *),
+    void *chain_and_key, const char *chain_pem, const char *private_key_pem,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_cert_chain_and_key_load_pem_bytes(
+    int (*fn)(void *, const uint8_t *, uint32_t, const uint8_t *, uint32_t),
+    void *chain_and_key, const uint8_t *chain_pem, uint32_t chain_pem_len,
+    const uint8_t *private_key_pem, uint32_t private_key_pem_len,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_cert_chain_and_key_load_public_pem_bytes(
+    int (*fn)(void *, const uint8_t *, uint32_t),
+    void *chain_and_key, const uint8_t *chain_pem, uint32_t chain_pem_len,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_cert_chain_and_key_free(
+    int (*fn)(void *),
+    void *chain_and_key,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_cert_chain_and_key_set_ctx(
+    int (*fn)(void *, void *),
+    void *chain_and_key, void *ctx,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+void *s2n_wrap_cert_chain_and_key_get_ctx(
+    void *(*fn)(void *),
+    void *chain_and_key,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+void *s2n_wrap_cert_chain_and_key_get_private_key(
+    void *(*fn)(void *),
+    void *chain_and_key,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_cert_chain_and_key_set_ocsp_data(
+    int (*fn)(void *, const uint8_t *, uint32_t),
+    void *chain_and_key, const uint8_t *data, uint32_t length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_cert_chain_and_key_set_sct_list(
+    int (*fn)(void *, const uint8_t *, uint32_t),
+    void *chain_and_key, const uint8_t *data, uint32_t length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_set_cert_tiebreak_callback(
+    int (*fn)(void *, void *),
+    void *config, void *callback,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_add_cert_chain_and_key(
+    int (*fn)(void *, const char *, const char *),
+    void *config, const char *chain_pem, const char *private_key_pem,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_add_cert_chain_and_key_to_store(
+    int (*fn)(void *, void *),
+    void *config, void *chain_and_key,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_set_cert_chain_and_key_defaults(
+    int (*fn)(void *, void **, uint32_t),
+    void *config, void **chain_and_key_array, uint32_t count,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+/* --- Trust Store --- */
+int s2n_wrap_config_set_verification_ca_location(
+    int (*fn)(void *, const char *, const char *),
+    void *config, const char *ca_pem_filename, const char *ca_dir,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_add_pem_to_trust_store(
+    int (*fn)(void *, const char *),
+    void *config, const char *pem,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_wipe_trust_store(
+    int (*fn)(void *),
+    void *config,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_load_system_certs(
+    int (*fn)(void *),
+    void *config,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_set_cert_authorities_from_trust_store(
+    int (*fn)(void *),
+    void *config,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+/* --- Verification & Validation --- */
+int s2n_wrap_config_set_verify_after_sign(
+    int (*fn)(void *, int),
+    void *config, int mode,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_set_check_stapled_ocsp_response(
+    int (*fn)(void *, uint8_t),
+    void *config, uint8_t check,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_disable_x509_time_verification(
+    int (*fn)(void *),
+    void *config,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_disable_x509_verification(
+    int (*fn)(void *),
+    void *config,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_set_max_cert_chain_depth(
+    int (*fn)(void *, uint16_t),
+    void *config, uint16_t depth,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_set_verify_host_callback(
+    int (*fn)(void *, void *, void *),
+    void *config, void *callback, void *ctx,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+/* --- DH Parameters --- */
+int s2n_wrap_config_add_dhparams(
+    int (*fn)(void *, const char *),
+    void *config, const char *dhparams_pem,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+/* --- Security Policies & Preferences --- */
+int s2n_wrap_config_set_cipher_preferences(
+    int (*fn)(void *, const char *),
+    void *config, const char *version,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_append_protocol_preference(
+    int (*fn)(void *, const uint8_t *, uint8_t),
+    void *config, const uint8_t *protocol, uint8_t protocol_len,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_set_protocol_preferences(
+    int (*fn)(void *, const char **, int),
+    void *config, const char **protocols, int protocol_count,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_set_status_request_type(
+    int (*fn)(void *, int),
+    void *config, int type,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_set_ct_support_level(
+    int (*fn)(void *, int),
+    void *config, int level,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_set_alert_behavior(
+    int (*fn)(void *, int),
+    void *config, int behavior,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+/* --- Extension Data --- */
+int s2n_wrap_config_set_extension_data(
+    int (*fn)(void *, int, const uint8_t *, uint32_t),
+    void *config, int extension_type, const uint8_t *data, uint32_t length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_send_max_fragment_length(
+    int (*fn)(void *, int),
+    void *config, int mfl_code,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_accept_max_fragment_length(
+    int (*fn)(void *),
+    void *config,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+/* --- Session & Ticket Configuration --- */
+int s2n_wrap_config_set_session_state_lifetime(
+    int (*fn)(void *, uint64_t),
+    void *config, uint64_t lifetime,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_set_session_tickets_onoff(
+    int (*fn)(void *, uint8_t),
+    void *config, uint8_t enabled,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_set_session_cache_onoff(
+    int (*fn)(void *, uint8_t),
+    void *config, uint8_t enabled,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_set_ticket_encrypt_decrypt_key_lifetime(
+    int (*fn)(void *, uint64_t),
+    void *config, uint64_t lifetime,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_set_ticket_decrypt_key_lifetime(
+    int (*fn)(void *, uint64_t),
+    void *config, uint64_t lifetime,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_add_ticket_crypto_key(
+    int (*fn)(void *, const uint8_t *, uint32_t, const uint8_t *, uint32_t, uint64_t),
+    void *config, const uint8_t *name, uint32_t name_len,
+    const uint8_t *key, uint32_t key_len, uint64_t intro_time_in_seconds_from_epoch,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_require_ticket_forward_secrecy(
+    int (*fn)(void *, int),
+    void *config, int enabled,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+/* --- Buffer & I/O Configuration --- */
+int s2n_wrap_config_set_send_buffer_size(
+    int (*fn)(void *, uint32_t),
+    void *config, uint32_t size,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_set_recv_multi_record(
+    int (*fn)(void *, int),
+    void *config, int enabled,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+/* --- Miscellaneous Config --- */
+int s2n_wrap_config_set_ctx(
+    int (*fn)(void *, void *),
+    void *config, void *ctx,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_get_ctx(
+    int (*fn)(void *, void **),
+    void *config, void **ctx,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_set_client_hello_cb(
+    int (*fn)(void *, void *, void *),
+    void *config, void *callback, void *ctx,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_set_client_hello_cb_mode(
+    int (*fn)(void *, int),
+    void *config, int mode,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_set_max_blinding_delay(
+    int (*fn)(void *, uint32_t),
+    void *config, uint32_t max_delay,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_get_client_auth_type(
+    int (*fn)(void *, int *),
+    void *config, int *auth_type,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_set_client_auth_type(
+    int (*fn)(void *, int),
+    void *config, int auth_type,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_set_initial_ticket_count(
+    int (*fn)(void *, uint8_t),
+    void *config, uint8_t count,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_set_psk_mode(
+    int (*fn)(void *, int),
+    void *config, int mode,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_set_psk_selection_callback(
+    int (*fn)(void *, void *, void *),
+    void *config, void *callback, void *ctx,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_set_async_pkey_callback(
+    int (*fn)(void *, void *),
+    void *config, void *callback,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_set_async_pkey_validation_mode(
+    int (*fn)(void *, int),
+    void *config, int mode,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_set_session_ticket_cb(
+    int (*fn)(void *, void *, void *),
+    void *config, void *callback, void *ctx,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_set_key_log_cb(
+    int (*fn)(void *, void *, void *),
+    void *config, void *callback, void *ctx,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_enable_cert_req_dss_legacy_compat(
+    int (*fn)(void *),
+    void *config,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_set_server_max_early_data_size(
+    int (*fn)(void *, uint32_t),
+    void *config, uint32_t size,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_set_early_data_cb(
+    int (*fn)(void *, void *),
+    void *config, void *callback,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_get_supported_groups(
+    int (*fn)(void *, uint16_t *, uint16_t, uint16_t *),
+    void *config, uint16_t *groups, uint16_t groups_count, uint16_t *groups_count_out,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_config_set_serialization_version(
+    int (*fn)(void *, int),
+    void *config, int version,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+/* --- Connection Creation & Management --- */
+void *s2n_wrap_connection_new(
+    void *(*fn)(int),
+    int mode,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_set_config(
+    int (*fn)(void *, void *),
+    void *conn, void *config,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_set_ctx(
+    int (*fn)(void *, void *),
+    void *conn, void *ctx,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+void *s2n_wrap_connection_get_ctx(
+    void *(*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_client_hello_cb_done(
+    int (*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_server_name_extension_used(
+    int (*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+/* --- Client Hello Access --- */
+void *s2n_wrap_connection_get_client_hello(
+    void *(*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+void *s2n_wrap_client_hello_parse_message(
+    void *(*fn)(const uint8_t *, uint32_t),
+    const uint8_t *data, uint32_t length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_client_hello_free(
+    int (*fn)(void **),
+    void **client_hello,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+ssize_t s2n_wrap_client_hello_get_raw_message_length(
+    ssize_t (*fn)(void *),
+    void *client_hello,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+ssize_t s2n_wrap_client_hello_get_raw_message(
+    ssize_t (*fn)(void *, uint8_t *, uint32_t),
+    void *client_hello, uint8_t *out, uint32_t max_length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+ssize_t s2n_wrap_client_hello_get_cipher_suites_length(
+    ssize_t (*fn)(void *),
+    void *client_hello,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+ssize_t s2n_wrap_client_hello_get_cipher_suites(
+    ssize_t (*fn)(void *, uint8_t *, uint32_t),
+    void *client_hello, uint8_t *out, uint32_t max_length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+ssize_t s2n_wrap_client_hello_get_extensions_length(
+    ssize_t (*fn)(void *),
+    void *client_hello,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+ssize_t s2n_wrap_client_hello_get_extensions(
+    ssize_t (*fn)(void *, uint8_t *, uint32_t),
+    void *client_hello, uint8_t *out, uint32_t max_length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+ssize_t s2n_wrap_client_hello_get_extension_length(
+    ssize_t (*fn)(void *, int),
+    void *client_hello, int extension_type,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+ssize_t s2n_wrap_client_hello_get_extension_by_id(
+    ssize_t (*fn)(void *, int, uint8_t *, uint32_t),
+    void *client_hello, int extension_type, uint8_t *out, uint32_t max_length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_client_hello_has_extension(
+    int (*fn)(void *, uint16_t, int *),
+    void *client_hello, uint16_t extension_iana, int *exists,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_client_hello_get_session_id_length(
+    int (*fn)(void *, uint32_t *),
+    void *client_hello, uint32_t *length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_client_hello_get_session_id(
+    int (*fn)(void *, uint8_t *, uint32_t *, uint32_t),
+    void *client_hello, uint8_t *out, uint32_t *out_length, uint32_t max_length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_client_hello_get_compression_methods_length(
+    int (*fn)(void *, uint32_t *),
+    void *client_hello, uint32_t *length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_client_hello_get_compression_methods(
+    int (*fn)(void *, uint8_t *, uint32_t, uint32_t *),
+    void *client_hello, uint8_t *out, uint32_t max_length, uint32_t *out_length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_client_hello_get_legacy_protocol_version(
+    int (*fn)(void *, uint8_t *),
+    void *client_hello, uint8_t *version,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_client_hello_get_supported_groups(
+    int (*fn)(void *, uint16_t *, uint16_t, uint16_t *),
+    void *client_hello, uint16_t *groups, uint16_t groups_count, uint16_t *groups_count_out,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_client_hello_get_server_name_length(
+    int (*fn)(void *, uint16_t *),
+    void *client_hello, uint16_t *length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_client_hello_get_server_name(
+    int (*fn)(void *, uint8_t *, uint16_t, uint16_t *),
+    void *client_hello, uint8_t *out, uint16_t max_length, uint16_t *out_length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_client_hello_get_legacy_record_version(
+    int (*fn)(void *, uint8_t *),
+    void *client_hello, uint8_t *version,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+/* --- File Descriptor & I/O --- */
+int s2n_wrap_connection_set_fd(
+    int (*fn)(void *, int),
+    void *conn, int fd,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_set_read_fd(
+    int (*fn)(void *, int),
+    void *conn, int fd,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_set_write_fd(
+    int (*fn)(void *, int),
+    void *conn, int fd,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_get_read_fd(
+    int (*fn)(void *, int *),
+    void *conn, int *fd,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_get_write_fd(
+    int (*fn)(void *, int *),
+    void *conn, int *fd,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_use_corked_io(
+    int (*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_set_recv_ctx(
+    int (*fn)(void *, void *),
+    void *conn, void *ctx,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_set_send_ctx(
+    int (*fn)(void *, void *),
+    void *conn, void *ctx,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_set_recv_cb(
+    int (*fn)(void *, void *),
+    void *conn, void *callback,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_set_send_cb(
+    int (*fn)(void *, void *),
+    void *conn, void *callback,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+/* --- Connection Preferences --- */
+int s2n_wrap_connection_prefer_throughput(
+    int (*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_prefer_low_latency(
+    int (*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_set_recv_buffering(
+    int (*fn)(void *, int),
+    void *conn, int enabled,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+/* s2n_peek_buffered returns uint32, no error - no wrapper needed */
+
+int s2n_wrap_connection_set_dynamic_buffers(
+    int (*fn)(void *, int),
+    void *conn, int enabled,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_set_dynamic_record_threshold(
+    int (*fn)(void *, uint32_t, uint16_t),
+    void *conn, uint32_t resize_threshold, uint16_t timeout_threshold,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+/* --- Host Verification --- */
+int s2n_wrap_connection_set_verify_host_callback(
+    int (*fn)(void *, void *, void *),
+    void *conn, void *callback, void *ctx,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+/* --- Blinding & Security --- */
+int s2n_wrap_connection_set_blinding(
+    int (*fn)(void *, int),
+    void *conn, int blinding,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+/* s2n_connection_get_delay returns uint64, no error - no wrapper needed */
+
+/* --- Cipher & Protocol Configuration --- */
+int s2n_wrap_connection_set_cipher_preferences(
+    int (*fn)(void *, const char *),
+    void *conn, const char *version,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_request_key_update(
+    int (*fn)(void *, int),
+    void *conn, int peer_request,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_append_protocol_preference(
+    int (*fn)(void *, const uint8_t *, uint8_t),
+    void *conn, const uint8_t *protocol, uint8_t protocol_len,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_set_protocol_preferences(
+    int (*fn)(void *, const char **, int),
+    void *conn, const char **protocols, int protocol_count,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+/* --- Server Name (SNI) --- */
+int s2n_wrap_set_server_name(
+    int (*fn)(void *, const char *),
+    void *conn, const char *server_name,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+const char *s2n_wrap_get_server_name(
+    const char *(*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+/* --- Application Protocol (ALPN) --- */
+const char *s2n_wrap_get_application_protocol(
+    const char *(*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+/* --- OCSP & Certificate Transparency --- */
+const uint8_t *s2n_wrap_connection_get_ocsp_response(
+    const uint8_t *(*fn)(void *, uint32_t *),
+    void *conn, uint32_t *length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+const uint8_t *s2n_wrap_connection_get_sct_list(
+    const uint8_t *(*fn)(void *, uint32_t *),
+    void *conn, uint32_t *length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+/* --- Handshake & TLS Operations --- */
+int s2n_wrap_negotiate(
+    int (*fn)(void *, int *),
+    void *conn, int *blocked,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+ssize_t s2n_wrap_send(
+    ssize_t (*fn)(void *, const void *, ssize_t, int *),
+    void *conn, const void *buf, ssize_t size, int *blocked,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+ssize_t s2n_wrap_recv(
+    ssize_t (*fn)(void *, void *, ssize_t, int *),
+    void *conn, void *buf, ssize_t size, int *blocked,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+/* s2n_peek returns uint32, no error - no wrapper needed */
+
+int s2n_wrap_connection_free_handshake(
+    int (*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_release_buffers(
+    int (*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_wipe(
+    int (*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_free(
+    int (*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_shutdown(
+    int (*fn)(void *, int *),
+    void *conn, int *blocked,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_shutdown_send(
+    int (*fn)(void *, int *),
+    void *conn, int *blocked,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+/* --- Client Authentication --- */
+int s2n_wrap_connection_get_client_auth_type(
+    int (*fn)(void *, int *),
+    void *conn, int *auth_type,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_set_client_auth_type(
+    int (*fn)(void *, int),
+    void *conn, int auth_type,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_get_client_cert_chain(
+    int (*fn)(void *, const uint8_t **, uint32_t *),
+    void *conn, const uint8_t **cert_chain, uint32_t *cert_chain_length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_client_cert_used(
+    int (*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+/* --- Session Management --- */
+int s2n_wrap_connection_add_new_tickets_to_send(
+    int (*fn)(void *, uint8_t),
+    void *conn, uint8_t num_tickets,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_get_tickets_sent(
+    int (*fn)(void *, uint16_t *),
+    void *conn, uint16_t *tickets_sent,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_set_server_keying_material_lifetime(
+    int (*fn)(void *, uint32_t),
+    void *conn, uint32_t lifetime,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_session_ticket_get_data_len(
+    int (*fn)(void *, size_t *),
+    void *ticket, size_t *length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_session_ticket_get_data(
+    int (*fn)(void *, size_t, uint8_t *),
+    void *ticket, size_t max_length, uint8_t *data,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_session_ticket_get_lifetime(
+    int (*fn)(void *, uint32_t *),
+    void *ticket, uint32_t *lifetime,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_set_session(
+    int (*fn)(void *, const uint8_t *, size_t),
+    void *conn, const uint8_t *session, size_t length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_get_session(
+    int (*fn)(void *, uint8_t *, size_t),
+    void *conn, uint8_t *session, size_t max_length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_get_session_ticket_lifetime_hint(
+    int (*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_get_session_length(
+    int (*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_get_session_id_length(
+    int (*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_get_session_id(
+    int (*fn)(void *, uint8_t *, size_t),
+    void *conn, uint8_t *session_id, size_t max_length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_is_session_resumed(
+    int (*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+/* --- Certificate Information --- */
+int s2n_wrap_connection_is_ocsp_stapled(
+    int (*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_get_selected_signature_algorithm(
+    int (*fn)(void *, int *),
+    void *conn, int *sig_alg,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_get_selected_digest_algorithm(
+    int (*fn)(void *, int *),
+    void *conn, int *hash_alg,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_get_selected_client_cert_signature_algorithm(
+    int (*fn)(void *, int *),
+    void *conn, int *sig_alg,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_get_selected_client_cert_digest_algorithm(
+    int (*fn)(void *, int *),
+    void *conn, int *hash_alg,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+void *s2n_wrap_connection_get_selected_cert(
+    void *(*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_cert_chain_get_length(
+    int (*fn)(void *, uint32_t *),
+    void *chain, uint32_t *length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_cert_chain_get_cert(
+    int (*fn)(void *, void **, uint32_t),
+    void *chain, void **cert, uint32_t index,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_cert_get_der(
+    int (*fn)(void *, const uint8_t **, uint32_t *),
+    void *cert, const uint8_t **der, uint32_t *length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_get_peer_cert_chain(
+    int (*fn)(void *, void *),
+    void *conn, void *cert_chain,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_cert_get_x509_extension_value_length(
+    int (*fn)(void *, const uint8_t *, uint32_t *),
+    void *cert, const uint8_t *oid, uint32_t *length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_cert_get_x509_extension_value(
+    int (*fn)(void *, const uint8_t *, uint8_t *, uint32_t *, int *),
+    void *cert, const uint8_t *oid, uint8_t *value, uint32_t *length, int *critical,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_cert_get_utf8_string_from_extension_data_length(
+    int (*fn)(const uint8_t *, uint32_t, uint32_t *),
+    const uint8_t *data, uint32_t data_length, uint32_t *utf8_length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_cert_get_utf8_string_from_extension_data(
+    int (*fn)(const uint8_t *, uint32_t, uint8_t *, uint32_t *),
+    const uint8_t *data, uint32_t data_length, uint8_t *utf8, uint32_t *utf8_length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+/* --- Pre-Shared Keys (PSK) --- */
+void *s2n_wrap_external_psk_new(
+    void *(*fn)(void),
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_psk_free(
+    int (*fn)(void **),
+    void **psk,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_psk_set_identity(
+    int (*fn)(void *, const uint8_t *, uint16_t),
+    void *psk, const uint8_t *identity, uint16_t length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_psk_set_secret(
+    int (*fn)(void *, const uint8_t *, uint16_t),
+    void *psk, const uint8_t *secret, uint16_t length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_psk_set_hmac(
+    int (*fn)(void *, int),
+    void *psk, int hmac,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_append_psk(
+    int (*fn)(void *, void *),
+    void *conn, void *psk,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_set_psk_mode(
+    int (*fn)(void *, int),
+    void *conn, int mode,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_get_negotiated_psk_identity_length(
+    int (*fn)(void *, uint16_t *),
+    void *conn, uint16_t *length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_get_negotiated_psk_identity(
+    int (*fn)(void *, uint8_t *, uint16_t),
+    void *conn, uint8_t *identity, uint16_t max_length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+void *s2n_wrap_offered_psk_new(
+    void *(*fn)(void),
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_offered_psk_free(
+    int (*fn)(void **),
+    void **psk,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_offered_psk_get_identity(
+    int (*fn)(void *, uint8_t **, uint16_t *),
+    void *psk, uint8_t **identity, uint16_t *length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+/* s2n_offered_psk_list_has_next returns bool, no error - no wrapper needed */
+
+int s2n_wrap_offered_psk_list_next(
+    int (*fn)(void *, void *),
+    void *psk_list, void *psk,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_offered_psk_list_reread(
+    int (*fn)(void *),
+    void *psk_list,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_offered_psk_list_choose_psk(
+    int (*fn)(void *, void *),
+    void *psk_list, void *psk,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_psk_configure_early_data(
+    int (*fn)(void *, uint32_t, uint8_t, uint8_t),
+    void *psk, uint32_t max_early_data_size, uint8_t cipher_suite_first_byte, uint8_t cipher_suite_second_byte,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_psk_set_application_protocol(
+    int (*fn)(void *, const uint8_t *, uint8_t),
+    void *psk, const uint8_t *protocol, uint8_t length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_psk_set_early_data_context(
+    int (*fn)(void *, const uint8_t *, uint16_t),
+    void *psk, const uint8_t *context, uint16_t length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+/* --- Connection Statistics --- */
+/* s2n_connection_get_wire_bytes_in returns uint64, no error - no wrapper needed */
+/* s2n_connection_get_wire_bytes_out returns uint64, no error - no wrapper needed */
+
+/* --- Protocol Version Information --- */
+int s2n_wrap_connection_get_client_protocol_version(
+    int (*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_get_server_protocol_version(
+    int (*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_get_actual_protocol_version(
+    int (*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_get_client_hello_version(
+    int (*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+/* --- Cipher & Security Information --- */
+const char *s2n_wrap_connection_get_cipher(
+    const char *(*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_get_certificate_match(
+    int (*fn)(void *, int *),
+    void *conn, int *cert_match,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_get_master_secret(
+    int (*fn)(void *, uint8_t *, size_t),
+    void *conn, uint8_t *secret, size_t max_length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_tls_exporter(
+    int (*fn)(void *, const uint8_t *, uint32_t, const uint8_t *, uint32_t, uint8_t *, uint32_t),
+    void *conn, const uint8_t *label, uint32_t label_length,
+    const uint8_t *context, uint32_t context_length,
+    uint8_t *output, uint32_t output_length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_get_cipher_iana_value(
+    int (*fn)(void *, uint8_t *, uint8_t *),
+    void *conn, uint8_t *first, uint8_t *second,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_is_valid_for_cipher_preferences(
+    int (*fn)(void *, const char *),
+    void *conn, const char *version,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+const char *s2n_wrap_connection_get_curve(
+    const char *(*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+const char *s2n_wrap_connection_get_kem_name(
+    const char *(*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+const char *s2n_wrap_connection_get_kem_group_name(
+    const char *(*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_get_key_exchange_group(
+    int (*fn)(void *, const char **),
+    void *conn, const char **group_name,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_get_alert(
+    int (*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+const char *s2n_wrap_connection_get_handshake_type_name(
+    const char *(*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+const char *s2n_wrap_connection_get_last_message_name(
+    const char *(*fn)(void *),
+    void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+/* --- Async Private Key Operations --- */
+int s2n_wrap_async_pkey_op_perform(
+    int (*fn)(void *, void *),
+    void *op, void *key,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_async_pkey_op_apply(
+    int (*fn)(void *, void *),
+    void *op, void *conn,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_async_pkey_op_free(
+    int (*fn)(void *),
+    void *op,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_async_pkey_op_get_op_type(
+    int (*fn)(void *, int *),
+    void *op, int *op_type,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_async_pkey_op_get_input_size(
+    int (*fn)(void *, uint32_t *),
+    void *op, uint32_t *size,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_async_pkey_op_get_input(
+    int (*fn)(void *, uint8_t *, uint32_t),
+    void *op, uint8_t *data, uint32_t length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_async_pkey_op_set_output(
+    int (*fn)(void *, const uint8_t *, uint32_t),
+    void *op, const uint8_t *data, uint32_t length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+/* --- Early Data --- */
+int s2n_wrap_connection_set_server_max_early_data_size(
+    int (*fn)(void *, uint32_t),
+    void *conn, uint32_t size,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_set_server_early_data_context(
+    int (*fn)(void *, const uint8_t *, uint16_t),
+    void *conn, const uint8_t *context, uint16_t length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_get_early_data_status(
+    int (*fn)(void *, int *),
+    void *conn, int *status,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_get_remaining_early_data_size(
+    int (*fn)(void *, uint32_t *),
+    void *conn, uint32_t *size,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_get_max_early_data_size(
+    int (*fn)(void *, uint32_t *),
+    void *conn, uint32_t *size,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_send_early_data(
+    int (*fn)(void *, const uint8_t *, ssize_t, ssize_t *, int *),
+    void *conn, const uint8_t *data, ssize_t data_len, ssize_t *written, int *blocked,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_recv_early_data(
+    int (*fn)(void *, uint8_t *, ssize_t, ssize_t *, int *),
+    void *conn, uint8_t *data, ssize_t max_data_len, ssize_t *read, int *blocked,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_offered_early_data_get_context_length(
+    int (*fn)(void *, uint16_t *),
+    void *early_data, uint16_t *length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_offered_early_data_get_context(
+    int (*fn)(void *, uint8_t *, uint16_t),
+    void *early_data, uint8_t *context, uint16_t max_length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_offered_early_data_reject(
+    int (*fn)(void *),
+    void *early_data,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_offered_early_data_accept(
+    int (*fn)(void *),
+    void *early_data,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+/* --- Connection Serialization --- */
+int s2n_wrap_connection_serialization_length(
+    int (*fn)(void *, uint32_t *),
+    void *conn, uint32_t *length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_serialize(
+    int (*fn)(void *, uint8_t *, uint32_t),
+    void *conn, uint8_t *buffer, uint32_t length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+int s2n_wrap_connection_deserialize(
+    int (*fn)(void *, const uint8_t *, uint32_t),
+    void *conn, const uint8_t *buffer, uint32_t length,
+    const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
+
+#endif /* S2N_WRAPPER_H */
diff --git a/s2n-tls-ffi.cabal b/s2n-tls-ffi.cabal
new file mode 100644
--- /dev/null
+++ b/s2n-tls-ffi.cabal
@@ -0,0 +1,60 @@
+cabal-version:      3.0
+name:               s2n-tls-ffi
+version:            0.1.0.0
+synopsis:           Low-level FFI bindings to the s2n-tls library
+description:
+    Low-level FFI bindings to the s2n-tls library.
+
+license:            Apache-2.0
+license-file:       LICENSE
+author:             Daniel Goertzen
+maintainer:         daniel.goertzen@gmail.com
+copyright:          2026 Daniel Goertzen
+category:           Cryptography, FFI, Network
+build-type:         Simple
+tested-with:        GHC == 9.8.2
+extra-doc-files:
+    CHANGELOG.md
+    README.md
+extra-source-files: cbits/s2n_wrapper.h
+homepage:           https://github.com/goertzenator/s2n-tls-ffi
+bug-reports:        https://github.com/goertzenator/s2n-tls-ffi/issues
+
+source-repository head
+    type:     git
+    location: https://github.com/goertzenator/s2n-tls-ffi.git
+
+source-repository this
+    type:     git
+    location: https://github.com/goertzenator/s2n-tls-ffi.git
+    tag:      v0.1.0.0
+
+library
+    exposed-modules:
+        S2nTls.Ffi
+        S2nTls.Ffi.Types
+
+    build-depends:
+        base >= 4.14 && < 5
+      , unix >= 2.7 && < 2.9
+
+    hs-source-dirs:   src
+    default-language: GHC2021
+    ghc-options:      -Wall -haddock
+
+    -- C wrapper for capturing error info from thread-local storage
+    c-sources:        cbits/s2n_wrapper.c
+    include-dirs:     cbits
+
+    -- Required for Linked mode (symbols loaded via dlopen(NULL))
+    extra-libraries:  s2n
+
+test-suite memory-safety
+    type:             exitcode-stdio-1.0
+    main-is:          MemorySafety.hs
+    hs-source-dirs:   test
+    default-language: GHC2021
+    build-depends:
+        base >= 4.14 && < 5
+      , s2n-tls-ffi
+    ghc-options:      -threaded -rtsopts "-with-rtsopts=-N"
diff --git a/src/S2nTls/Ffi.hs b/src/S2nTls/Ffi.hs
new file mode 100644
--- /dev/null
+++ b/src/S2nTls/Ffi.hs
@@ -0,0 +1,918 @@
+{-# LANGUAGE RecordWildCards #-}
+
+{- |
+Module      : S2nTls.Ffi
+Description : Low-level FFI bindings to the s2n-tls library
+Copyright   : (c) 2026 Daniel Goertzen
+License     : Apache-2.0
+Maintainer  : daniel.goertzen@gmail.com
+Stability   : experimental
+Portability : non-portable (requires s2n-tls C library)
+
+This module provides low-level FFI bindings to the s2n-tls library.
+
+The core type is 'S2nTlsFfi', a record containing all FFI function
+pointers. Use 'withS2nTlsFfi' with a 'Library' to obtain one:
+
+* 'Linked': For executables that link s2n-tls at compile time.
+  Uses dlopen(NULL) to load symbols from the running executable.
+
+* @'Dynamic' path@: To load the library at runtime via dlopen.
+  Pass the path to libs2n.so.
+
+C wrappers are used to safely capture error information
+(including TLS-dependent error strings) in the same C stack frame,
+avoiding thread-local storage issues in Haskell FFI.
+
+Symbol loading is forgiving - missing symbols don't cause failure
+at load time. Instead, calling a missing symbol throws 'MissingSymbol'.
+Check the 'missingSymbols' field to see which symbols weren't found.
+
+= Memory Locking (mlock)
+
+== What is mlock?
+
+s2n-tls uses the Linux @mlock()@ system call to lock memory pages containing
+cryptographic secrets (private keys, session keys, etc.) into RAM. This prevents
+the operating system from swapping these pages to disk, where they could
+potentially be recovered by an attacker after your application terminates.
+
+== The RLIMIT_MEMLOCK Limit
+
+Linux enforces a per-process limit on how much memory can be locked, controlled
+by @RLIMIT_MEMLOCK@. On many systems, this defaults to just __64 KB__ (or even
+32 KB on some Debian versions). Since s2n-tls locks memory for all TLS
+connections and cryptographic operations, this limit can be exhausted quickly
+in applications handling multiple connections.
+
+When the limit is exceeded, you'll see errors like:
+
+> Error Message: 'error calling mlock'
+> Debug String: 'Error encountered in s2n_mem.c line 106'
+
+== Solutions
+
+__Option 1: Increase the mlock limit (recommended for production)__
+
+Raise the limit for your shell session:
+
+> ulimit -l unlimited
+
+Or set it to a specific value (in KB):
+
+> ulimit -l 65536  # 64 MB
+
+For systemd services, add to your unit file:
+
+> [Service]
+> LimitMEMLOCK=infinity
+
+For persistent user limits, add to @\/etc\/security\/limits.conf@:
+
+> youruser  soft  memlock  unlimited
+> youruser  hard  memlock  unlimited
+
+__Option 2: Disable mlock (acceptable for development\/testing)__
+
+Set the environment variable to disable memory locking entirely:
+
+> S2N_DONT_MLOCK=1 ./your-application
+
+== Security Considerations
+
+* __With mlock enabled__: Secrets are protected from being written to swap,
+  reducing the risk of recovery from disk. This is the recommended setting
+  for production deployments handling sensitive data.
+
+* __With mlock disabled__: Secrets may be swapped to disk under memory
+  pressure. This is generally acceptable for development, testing, and
+  applications where the threat model doesn't include disk forensics.
+
+* __Note__: Even with mlock enabled, laptop suspend\/hibernate modes may
+  save RAM contents to disk regardless of memory locks.
+
+== Running Tests
+
+Tests may exhaust the default mlock limit. Use:
+
+> S2N_DONT_MLOCK=1 cabal test
+-}
+module S2nTls.Ffi (
+    -- * Core Types
+    S2nTlsFfi (..),
+
+    -- * Library Loading
+    Library (..),
+    withS2nTlsFfi,
+    LoadError (..),
+
+    -- * Error Types
+    MissingSymbol (..),
+    S2nError (..),
+    S2nErrorFuncs (..),
+) where
+
+import Control.Exception (Exception, bracket, throwIO)
+import Control.Monad (when)
+import Data.IORef (modifyIORef', newIORef, readIORef)
+import Data.Word (Word16, Word32, Word64, Word8)
+import Foreign.C.String (CString)
+import Foreign.C.Types (CBool (..), CInt (..), CLong (..), CSize (..))
+import Foreign.Marshal.Alloc (alloca, free, malloc)
+import Foreign.Ptr (FunPtr, Ptr, nullFunPtr, nullPtr)
+import Foreign.Storable (peek, poke)
+import System.Posix.DynamicLinker (DL, RTLDFlags (RTLD_LAZY, RTLD_LOCAL), dlclose, dlopen, dlsym)
+import System.Posix.Types (CSsize (..))
+
+import S2nTls.Ffi.Types
+
+-- | Specifies how to load the s2n-tls library.
+data Library
+    = -- | Load symbols from the currently linked executable (uses dlopen(NULL))
+      Linked
+    | -- | Load library dynamically from the given path
+      Dynamic String
+    deriving (Show, Eq)
+
+-- | Errors that can occur when loading the s2n-tls library.
+data LoadError
+    = -- | Library file could not be opened
+      LibraryNotFound FilePath String
+    | -- | A required symbol was not found in the library
+      RequiredSymbolNotFound String
+    deriving (Show, Eq)
+
+instance Exception LoadError
+
+class TransformError a where
+    transformError :: (Ptr S2nError -> IO a) -> IO (Either S2nError a)
+
+instance TransformError CInt where
+    transformError action = alloca $ \errInfoPtr -> do
+        res <- action errInfoPtr
+        if res < 0
+            then Left <$> peek errInfoPtr
+            else pure $ Right res
+
+instance TransformError CSsize where
+    transformError action = alloca $ \errInfoPtr -> do
+        res <- action errInfoPtr
+        if res < 0
+            then Left <$> peek errInfoPtr
+            else pure $ Right res
+
+instance TransformError (Ptr a) where
+    transformError action = alloca $ \errInfoPtr -> do
+        res <- action errInfoPtr
+        if res == nullPtr
+            then Left <$> peek errInfoPtr
+            else pure $ Right res
+
+-- CSize is unsigned, so functions returning CSize typically can't fail
+-- They always return a valid size value
+instance TransformError CSize where
+    transformError action = alloca $ \errInfoPtr -> do
+        res <- action errInfoPtr
+        pure $ Right res
+
+const2 :: a -> b -> c -> a
+const2 x _ _ = x
+
+const3 :: a -> b -> c -> d -> a
+const3 x _ _ _ = x
+
+const4 :: a -> b -> c -> d -> e -> a
+const4 x _ _ _ _ = x
+
+const5 :: a -> b -> c -> d -> e -> f -> a
+const5 x _ _ _ _ _ = x
+
+const6 :: a -> b -> c -> d -> e -> f -> g -> a
+const6 x _ _ _ _ _ _ = x
+
+const7 :: a -> b -> c -> d -> e -> f -> g -> h -> a
+const7 x _ _ _ _ _ _ _ = x
+
+{- | Load the s2n-tls library and provide a 'S2nTlsFfi' record to the
+given callback. The library is automatically unloaded when the callback
+returns (or throws an exception).
+
+@
+-- Use symbols from the linked executable
+withS2nTlsFfi Linked $ \\ffi -> do
+    result <- s2n_init ffi
+    ...
+
+-- Load library dynamically
+withS2nTlsFfi (Dynamic "libs2n.so") $ \\ffi -> do
+    print (missingSymbols ffi)  -- see which symbols weren't found
+    ...
+@
+-}
+withS2nTlsFfi ::
+    -- | How to load the library
+    Library ->
+    -- | Callback that receives the populated 'S2nTlsFfi' record
+    (S2nTlsFfi -> IO a) ->
+    IO a
+withS2nTlsFfi lib action =
+    bracket (openLib (libraryPath lib)) dlclose $ \dl ->
+        bracket (malloc :: IO (Ptr S2nErrorFuncs)) free $ \errFuncsPtr -> do
+            -- Load error functions first (fatal if missing)
+            errFuncs <- loadErrorFuncs dl
+            poke errFuncsPtr errFuncs
+
+            -- Load all other symbols (forgiving)
+            (ffi, missing) <- loadSymbols dl errFuncsPtr
+            action ffi{missingSymbols = missing}
+
+-- | Convert Library to the path string for dlopen
+libraryPath :: Library -> FilePath
+libraryPath Linked = ""
+libraryPath (Dynamic path) = path
+
+-- | Open the library, handling empty path for dlopen(NULL)
+openLib :: FilePath -> IO DL
+openLib path = dlopen path [RTLD_LAZY, RTLD_LOCAL]
+
+-- | Load the error functions (required for meaningful error reporting)
+loadErrorFuncs :: DL -> IO S2nErrorFuncs
+loadErrorFuncs dl = do
+    el <- dlsym dl "s2n_errno_location"
+    sd <- dlsym dl "s2n_strerror_debug"
+    et <- dlsym dl "s2n_error_get_type"
+    pure $ S2nErrorFuncs el sd et
+
+-- | Create a closure that throws MissingSymbol
+throwMissing :: String -> IO a
+throwMissing name = throwIO (MissingSymbol name)
+
+-- | Indicate where a symbol is required vs optional
+data MethodRequirement = Mandatory | Optional
+    deriving (Show, Eq)
+
+{- | Load all s2n-tls symbols from the given dynamic library handle.
+Returns the S2nTlsFfi record and list of missing symbol names.
+-}
+loadSymbols :: DL -> Ptr S2nErrorFuncs -> IO (S2nTlsFfi, [String])
+loadSymbols dl errFuncsPtr = do
+    missingRef <- newIORef []
+
+    let
+        -- Helper to load a symbol with forgiving behavior
+        load :: String -> MethodRequirement -> IO (FunPtr a)
+        load name req = do
+            ptr <- dlsym dl name
+            when (ptr == nullFunPtr) $ do
+                modifyIORef' missingRef (name :)
+            if ptr == nullFunPtr && req == Mandatory
+                then do
+                    throwMissing name
+                else do
+                    pure ptr
+
+        mkMethod0Direct :: String -> MethodRequirement -> (FunPtr x -> IO r) -> IO (IO r)
+        mkMethod0Direct name req action = do
+            ptr <- load name req
+            pure $
+                if ptr == nullFunPtr
+                    then throwMissing name
+                    else action ptr
+
+        mkMethod1Direct :: String -> MethodRequirement -> (FunPtr x -> a -> IO r) -> IO (a -> IO r)
+        mkMethod1Direct name req action = do
+            ptr <- load name req
+            pure $
+                if ptr == nullFunPtr
+                    then const (throwMissing name)
+                    else action ptr
+
+        mkMethod2Direct :: String -> MethodRequirement -> (FunPtr x -> a -> b -> IO r) -> IO (a -> b -> IO r)
+        mkMethod2Direct name req action = do
+            ptr <- load name req
+            pure $
+                if ptr == nullFunPtr
+                    then const2 (throwMissing name)
+                    else action ptr
+
+        -- Note: C wrappers have signature: FunPtr -> args... -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO r
+        mkMethod0 :: (TransformError r) => String -> MethodRequirement -> (FunPtr () -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO r) -> IO (IO (Either S2nError r))
+        mkMethod0 name req action = do
+            ptr <- load name req
+            pure $
+                if ptr == nullFunPtr
+                    then throwMissing name
+                    else transformError (action ptr errFuncsPtr)
+
+        mkMethod1 :: (TransformError r) => String -> MethodRequirement -> (FunPtr () -> a -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO r) -> IO (a -> IO (Either S2nError r))
+        mkMethod1 name req action = do
+            ptr <- load name req
+            pure $
+                if ptr == nullFunPtr
+                    then const (throwMissing name)
+                    else \a -> transformError (action ptr a errFuncsPtr)
+
+        mkMethod2 :: (TransformError r) => String -> MethodRequirement -> (FunPtr () -> a -> b -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO r) -> IO (a -> b -> IO (Either S2nError r))
+        mkMethod2 name req action = do
+            ptr <- load name req
+            pure $
+                if ptr == nullFunPtr
+                    then const2 (throwMissing name)
+                    else \a b -> transformError (action ptr a b errFuncsPtr)
+
+        mkMethod3 :: (TransformError r) => String -> MethodRequirement -> (FunPtr () -> a -> b -> c -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO r) -> IO (a -> b -> c -> IO (Either S2nError r))
+        mkMethod3 name req action = do
+            ptr <- load name req
+            pure $
+                if ptr == nullFunPtr
+                    then const3 (throwMissing name)
+                    else \a b c -> transformError (action ptr a b c errFuncsPtr)
+
+        mkMethod4 :: (TransformError r) => String -> MethodRequirement -> (FunPtr () -> a -> b -> c -> d -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO r) -> IO (a -> b -> c -> d -> IO (Either S2nError r))
+        mkMethod4 name req action = do
+            ptr <- load name req
+            pure $
+                if ptr == nullFunPtr
+                    then const4 (throwMissing name)
+                    else \a b c d -> transformError (action ptr a b c d errFuncsPtr)
+
+        mkMethod5 :: (TransformError r) => String -> MethodRequirement -> (FunPtr () -> a -> b -> c -> d -> e -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO r) -> IO (a -> b -> c -> d -> e -> IO (Either S2nError r))
+        mkMethod5 name req action = do
+            ptr <- load name req
+            pure $
+                if ptr == nullFunPtr
+                    then const5 (throwMissing name)
+                    else \a b c d e -> transformError (action ptr a b c d e errFuncsPtr)
+
+        mkMethod6 :: (TransformError r) => String -> MethodRequirement -> (FunPtr () -> a -> b -> c -> d -> e -> f -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO r) -> IO (a -> b -> c -> d -> e -> f -> IO (Either S2nError r))
+        mkMethod6 name req action = do
+            ptr <- load name req
+            pure $
+                if ptr == nullFunPtr
+                    then const6 (throwMissing name)
+                    else \a b c d e f -> transformError (action ptr a b c d e f errFuncsPtr)
+
+        mkMethod7 :: (TransformError r) => String -> MethodRequirement -> (FunPtr () -> a -> b -> c -> d -> e -> f -> g -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO r) -> IO (a -> b -> c -> d -> e -> f -> g -> IO (Either S2nError r))
+        mkMethod7 name req action = do
+            ptr <- load name req
+            pure $
+                if ptr == nullFunPtr
+                    then const7 (throwMissing name)
+                    else \a b c d e f g -> transformError (action ptr a b c d e f g errFuncsPtr)
+
+    -- Initialization & Cleanup
+    s2n_init <- mkMethod0 "s2n_init" Optional c_wrap_init
+    s2n_cleanup <- mkMethod0 "s2n_cleanup" Optional c_wrap_cleanup
+    s2n_cleanup_final <- mkMethod0 "s2n_cleanup_final" Optional c_wrap_cleanup_final
+    s2n_crypto_disable_init <- mkMethod0 "s2n_crypto_disable_init" Optional c_wrap_crypto_disable_init
+    s2n_disable_atexit <- mkMethod0 "s2n_disable_atexit" Optional c_wrap_disable_atexit
+    s2n_get_openssl_version <- mkMethod0Direct "s2n_get_openssl_version" Mandatory mk_s2n_get_openssl_version
+    s2n_get_fips_mode <- mkMethod1 "s2n_get_fips_mode" Optional c_wrap_get_fips_mode
+
+    -- Error Handling (load function pointers and wrap directly)
+    s2n_errno_location <- mkMethod0Direct "s2n_errno_location" Mandatory mk_s2n_errno_location
+    s2n_strerror <- mkMethod2Direct "s2n_strerror" Mandatory mk_s2n_strerror
+    s2n_strerror_debug <- mkMethod2Direct "s2n_strerror_debug" Mandatory mk_s2n_strerror_debug
+    s2n_strerror_source <- mkMethod1Direct "s2n_strerror_source" Mandatory mk_s2n_strerror_source
+    s2n_error_get_type <- mkMethod1Direct "s2n_error_get_type" Mandatory mk_s2n_error_get_type
+    s2n_strerror_name <- mkMethod1Direct "s2n_strerror_name" Mandatory mk_s2n_strerror_name
+
+    -- Stack Traces
+    s2n_stack_traces_enabled <- mkMethod0Direct "s2n_stack_traces_enabled" Optional mk_s2n_stack_traces_enabled
+    s2n_stack_traces_enabled_set <- mkMethod1 "s2n_stack_traces_enabled_set" Optional c_wrap_stack_traces_enabled_set
+    s2n_calculate_stacktrace <- mkMethod0 "s2n_calculate_stacktrace" Optional c_wrap_calculate_stacktrace
+    s2n_free_stacktrace <- mkMethod0 "s2n_free_stacktrace" Optional c_wrap_free_stacktrace
+    s2n_get_stacktrace <- mkMethod1 "s2n_get_stacktrace" Optional c_wrap_get_stacktrace
+
+    -- Config Management
+    s2n_config_new <- mkMethod0 "s2n_config_new" Optional c_wrap_config_new
+    s2n_config_new_minimal <- mkMethod0 "s2n_config_new_minimal" Optional c_wrap_config_new_minimal
+    s2n_config_free <- mkMethod1 "s2n_config_free" Optional c_wrap_config_free
+    s2n_config_free_dhparams <- mkMethod1 "s2n_config_free_dhparams" Optional c_wrap_config_free_dhparams
+    s2n_config_free_cert_chain_and_key <- mkMethod1 "s2n_config_free_cert_chain_and_key" Optional c_wrap_config_free_cert_chain_and_key
+    s2n_config_set_wall_clock <- mkMethod3 "s2n_config_set_wall_clock" Optional c_wrap_config_set_wall_clock
+    s2n_config_set_monotonic_clock <- mkMethod3 "s2n_config_set_monotonic_clock" Optional c_wrap_config_set_monotonic_clock
+    -- Cache Callbacks
+    s2n_config_set_cache_store_callback <- mkMethod3 "s2n_config_set_cache_store_callback" Optional c_wrap_config_set_cache_store_callback
+    s2n_config_set_cache_retrieve_callback <- mkMethod3 "s2n_config_set_cache_retrieve_callback" Optional c_wrap_config_set_cache_retrieve_callback
+    s2n_config_set_cache_delete_callback <- mkMethod3 "s2n_config_set_cache_delete_callback" Optional c_wrap_config_set_cache_delete_callback
+    -- Memory & Random Callbacks
+    s2n_mem_set_callbacks <- mkMethod4 "s2n_mem_set_callbacks" Optional c_wrap_mem_set_callbacks
+    s2n_rand_set_callbacks <- mkMethod4 "s2n_rand_set_callbacks" Optional c_wrap_rand_set_callbacks
+
+    -- Certificate Chain Management
+    s2n_cert_chain_and_key_new <- mkMethod0 "s2n_cert_chain_and_key_new" Optional c_wrap_cert_chain_and_key_new
+    s2n_cert_chain_and_key_load_pem <- mkMethod3 "s2n_cert_chain_and_key_load_pem" Optional c_wrap_cert_chain_and_key_load_pem
+    s2n_cert_chain_and_key_load_pem_bytes <- mkMethod5 "s2n_cert_chain_and_key_load_pem_bytes" Optional c_wrap_cert_chain_and_key_load_pem_bytes
+    s2n_cert_chain_and_key_load_public_pem_bytes <- mkMethod3 "s2n_cert_chain_and_key_load_public_pem_bytes" Optional c_wrap_cert_chain_and_key_load_public_pem_bytes
+    s2n_cert_chain_and_key_free <- mkMethod1 "s2n_cert_chain_and_key_free" Optional c_wrap_cert_chain_and_key_free
+    s2n_cert_chain_and_key_set_ctx <- mkMethod2 "s2n_cert_chain_and_key_set_ctx" Optional c_wrap_cert_chain_and_key_set_ctx
+    s2n_cert_chain_and_key_get_ctx <- mkMethod1Direct "s2n_cert_chain_and_key_get_ctx" Mandatory mk_s2n_cert_chain_and_key_get_ctx
+    s2n_cert_chain_and_key_get_private_key <- mkMethod1 "s2n_cert_chain_and_key_get_private_key" Optional c_wrap_cert_chain_and_key_get_private_key
+    s2n_cert_chain_and_key_set_ocsp_data <- mkMethod3 "s2n_cert_chain_and_key_set_ocsp_data" Optional c_wrap_cert_chain_and_key_set_ocsp_data
+    s2n_cert_chain_and_key_set_sct_list <- mkMethod3 "s2n_cert_chain_and_key_set_sct_list" Optional c_wrap_cert_chain_and_key_set_sct_list
+    s2n_config_set_cert_tiebreak_callback <- mkMethod2 "s2n_config_set_cert_tiebreak_callback" Optional c_wrap_config_set_cert_tiebreak_callback
+    s2n_config_add_cert_chain_and_key <- mkMethod3 "s2n_config_add_cert_chain_and_key" Optional c_wrap_config_add_cert_chain_and_key
+    s2n_config_add_cert_chain_and_key_to_store <- mkMethod2 "s2n_config_add_cert_chain_and_key_to_store" Optional c_wrap_config_add_cert_chain_and_key_to_store
+    s2n_config_set_cert_chain_and_key_defaults <- mkMethod3 "s2n_config_set_cert_chain_and_key_defaults" Optional c_wrap_config_set_cert_chain_and_key_defaults
+
+    -- Trust Store
+    s2n_config_set_verification_ca_location <- mkMethod3 "s2n_config_set_verification_ca_location" Optional c_wrap_config_set_verification_ca_location
+    s2n_config_add_pem_to_trust_store <- mkMethod2 "s2n_config_add_pem_to_trust_store" Optional c_wrap_config_add_pem_to_trust_store
+    s2n_config_wipe_trust_store <- mkMethod1 "s2n_config_wipe_trust_store" Optional c_wrap_config_wipe_trust_store
+    s2n_config_load_system_certs <- mkMethod1 "s2n_config_load_system_certs" Optional c_wrap_config_load_system_certs
+    s2n_config_set_cert_authorities_from_trust_store <- mkMethod1 "s2n_config_set_cert_authorities_from_trust_store" Optional c_wrap_config_set_cert_authorities_from_trust_store
+
+    -- Verification & Validation
+    s2n_config_set_verify_after_sign <- mkMethod2 "s2n_config_set_verify_after_sign" Optional c_wrap_config_set_verify_after_sign
+    s2n_config_set_check_stapled_ocsp_response <- mkMethod2 "s2n_config_set_check_stapled_ocsp_response" Optional c_wrap_config_set_check_stapled_ocsp_response
+    s2n_config_disable_x509_time_verification <- mkMethod1 "s2n_config_disable_x509_time_verification" Optional c_wrap_config_disable_x509_time_verification
+    s2n_config_disable_x509_verification <- mkMethod1 "s2n_config_disable_x509_verification" Optional c_wrap_config_disable_x509_verification
+    s2n_config_set_max_cert_chain_depth <- mkMethod2 "s2n_config_set_max_cert_chain_depth" Optional c_wrap_config_set_max_cert_chain_depth
+    s2n_config_set_verify_host_callback <- mkMethod3 "s2n_config_set_verify_host_callback" Optional c_wrap_config_set_verify_host_callback
+
+    -- DH Parameters
+    s2n_config_add_dhparams <- mkMethod2 "s2n_config_add_dhparams" Optional c_wrap_config_add_dhparams
+
+    -- Security Policies & Preferences
+    s2n_config_set_cipher_preferences <- mkMethod2 "s2n_config_set_cipher_preferences" Optional c_wrap_config_set_cipher_preferences
+    s2n_config_append_protocol_preference <- mkMethod3 "s2n_config_append_protocol_preference" Optional c_wrap_config_append_protocol_preference
+    s2n_config_set_protocol_preferences <- mkMethod3 "s2n_config_set_protocol_preferences" Optional c_wrap_config_set_protocol_preferences
+    s2n_config_set_status_request_type <- mkMethod2 "s2n_config_set_status_request_type" Optional c_wrap_config_set_status_request_type
+    s2n_config_set_ct_support_level <- mkMethod2 "s2n_config_set_ct_support_level" Optional c_wrap_config_set_ct_support_level
+    s2n_config_set_alert_behavior <- mkMethod2 "s2n_config_set_alert_behavior" Optional c_wrap_config_set_alert_behavior
+
+    -- Extension Data
+    s2n_config_set_extension_data <- mkMethod4 "s2n_config_set_extension_data" Optional c_wrap_config_set_extension_data
+    s2n_config_send_max_fragment_length <- mkMethod2 "s2n_config_send_max_fragment_length" Optional c_wrap_config_send_max_fragment_length
+    s2n_config_accept_max_fragment_length <- mkMethod1 "s2n_config_accept_max_fragment_length" Optional c_wrap_config_accept_max_fragment_length
+
+    -- Session & Ticket Configuration
+    s2n_config_set_session_state_lifetime <- mkMethod2 "s2n_config_set_session_state_lifetime" Optional c_wrap_config_set_session_state_lifetime
+    s2n_config_set_session_tickets_onoff <- mkMethod2 "s2n_config_set_session_tickets_onoff" Optional c_wrap_config_set_session_tickets_onoff
+    s2n_config_set_session_cache_onoff <- mkMethod2 "s2n_config_set_session_cache_onoff" Optional c_wrap_config_set_session_cache_onoff
+    s2n_config_set_ticket_encrypt_decrypt_key_lifetime <- mkMethod2 "s2n_config_set_ticket_encrypt_decrypt_key_lifetime" Optional c_wrap_config_set_ticket_encrypt_decrypt_key_lifetime
+    s2n_config_set_ticket_decrypt_key_lifetime <- mkMethod2 "s2n_config_set_ticket_decrypt_key_lifetime" Optional c_wrap_config_set_ticket_decrypt_key_lifetime
+    s2n_config_add_ticket_crypto_key <- mkMethod6 "s2n_config_add_ticket_crypto_key" Optional c_wrap_config_add_ticket_crypto_key
+    s2n_config_require_ticket_forward_secrecy <- mkMethod2 "s2n_config_require_ticket_forward_secrecy" Optional c_wrap_config_require_ticket_forward_secrecy
+    -- Buffer & I/O Configuration
+    s2n_config_set_send_buffer_size <- mkMethod2 "s2n_config_set_send_buffer_size" Optional c_wrap_config_set_send_buffer_size
+    s2n_config_set_recv_multi_record <- mkMethod2 "s2n_config_set_recv_multi_record" Optional c_wrap_config_set_recv_multi_record
+
+    -- Miscellaneous Config
+    s2n_config_set_ctx <- mkMethod2 "s2n_config_set_ctx" Optional c_wrap_config_set_ctx
+    s2n_config_get_ctx <- mkMethod2 "s2n_config_get_ctx" Optional c_wrap_config_get_ctx
+    s2n_config_set_client_hello_cb <- mkMethod3 "s2n_config_set_client_hello_cb" Optional c_wrap_config_set_client_hello_cb
+    s2n_config_set_client_hello_cb_mode <- mkMethod2 "s2n_config_set_client_hello_cb_mode" Optional c_wrap_config_set_client_hello_cb_mode
+    s2n_config_set_max_blinding_delay <- mkMethod2 "s2n_config_set_max_blinding_delay" Optional c_wrap_config_set_max_blinding_delay
+    s2n_config_get_client_auth_type <- mkMethod2 "s2n_config_get_client_auth_type" Optional c_wrap_config_get_client_auth_type
+    s2n_config_set_client_auth_type <- mkMethod2 "s2n_config_set_client_auth_type" Optional c_wrap_config_set_client_auth_type
+    s2n_config_set_initial_ticket_count <- mkMethod2 "s2n_config_set_initial_ticket_count" Optional c_wrap_config_set_initial_ticket_count
+    s2n_config_set_psk_mode <- mkMethod2 "s2n_config_set_psk_mode" Optional c_wrap_config_set_psk_mode
+    s2n_config_set_psk_selection_callback <- mkMethod3 "s2n_config_set_psk_selection_callback" Optional c_wrap_config_set_psk_selection_callback
+    s2n_config_set_async_pkey_callback <- mkMethod2 "s2n_config_set_async_pkey_callback" Optional c_wrap_config_set_async_pkey_callback
+    s2n_config_set_async_pkey_validation_mode <- mkMethod2 "s2n_config_set_async_pkey_validation_mode" Optional c_wrap_config_set_async_pkey_validation_mode
+    s2n_config_set_session_ticket_cb <- mkMethod3 "s2n_config_set_session_ticket_cb" Optional c_wrap_config_set_session_ticket_cb
+    s2n_config_set_key_log_cb <- mkMethod3 "s2n_config_set_key_log_cb" Optional c_wrap_config_set_key_log_cb
+    s2n_config_enable_cert_req_dss_legacy_compat <- mkMethod1 "s2n_config_enable_cert_req_dss_legacy_compat" Optional c_wrap_config_enable_cert_req_dss_legacy_compat
+    s2n_config_set_server_max_early_data_size <- mkMethod2 "s2n_config_set_server_max_early_data_size" Optional c_wrap_config_set_server_max_early_data_size
+    s2n_config_set_early_data_cb <- mkMethod2 "s2n_config_set_early_data_cb" Optional c_wrap_config_set_early_data_cb
+    s2n_config_get_supported_groups <- mkMethod4 "s2n_config_get_supported_groups" Optional c_wrap_config_get_supported_groups
+    s2n_config_set_serialization_version <- mkMethod2 "s2n_config_set_serialization_version" Optional c_wrap_config_set_serialization_version
+
+    -- Connection Creation & Management
+    s2n_connection_new <- mkMethod1 "s2n_connection_new" Optional c_wrap_connection_new
+    s2n_connection_set_config <- mkMethod2 "s2n_connection_set_config" Optional c_wrap_connection_set_config
+    s2n_connection_set_ctx <- mkMethod2 "s2n_connection_set_ctx" Optional c_wrap_connection_set_ctx
+    s2n_connection_get_ctx <- mkMethod1 "s2n_connection_get_ctx" Optional c_wrap_connection_get_ctx
+    s2n_client_hello_cb_done <- mkMethod1 "s2n_client_hello_cb_done" Optional c_wrap_client_hello_cb_done
+    s2n_connection_server_name_extension_used <- mkMethod1 "s2n_connection_server_name_extension_used" Optional c_wrap_connection_server_name_extension_used
+
+    -- Client Hello Access
+    s2n_connection_get_client_hello <- mkMethod1 "s2n_connection_get_client_hello" Optional c_wrap_connection_get_client_hello
+    s2n_client_hello_parse_message <- mkMethod2 "s2n_client_hello_parse_message" Optional c_wrap_client_hello_parse_message
+    s2n_client_hello_free <- mkMethod1 "s2n_client_hello_free" Optional c_wrap_client_hello_free
+    s2n_client_hello_get_raw_message_length <- mkMethod1 "s2n_client_hello_get_raw_message_length" Optional c_wrap_client_hello_get_raw_message_length
+    s2n_client_hello_get_raw_message <- mkMethod3 "s2n_client_hello_get_raw_message" Optional c_wrap_client_hello_get_raw_message
+    s2n_client_hello_get_cipher_suites_length <- mkMethod1 "s2n_client_hello_get_cipher_suites_length" Optional c_wrap_client_hello_get_cipher_suites_length
+    s2n_client_hello_get_cipher_suites <- mkMethod3 "s2n_client_hello_get_cipher_suites" Optional c_wrap_client_hello_get_cipher_suites
+    s2n_client_hello_get_extensions_length <- mkMethod1 "s2n_client_hello_get_extensions_length" Optional c_wrap_client_hello_get_extensions_length
+    s2n_client_hello_get_extensions <- mkMethod3 "s2n_client_hello_get_extensions" Optional c_wrap_client_hello_get_extensions
+    s2n_client_hello_get_extension_length <- mkMethod2 "s2n_client_hello_get_extension_length" Optional c_wrap_client_hello_get_extension_length
+    s2n_client_hello_get_extension_by_id <- mkMethod4 "s2n_client_hello_get_extension_by_id" Optional c_wrap_client_hello_get_extension_by_id
+    s2n_client_hello_has_extension <- mkMethod3 "s2n_client_hello_has_extension" Optional c_wrap_client_hello_has_extension
+    s2n_client_hello_get_session_id_length <- mkMethod2 "s2n_client_hello_get_session_id_length" Optional c_wrap_client_hello_get_session_id_length
+    s2n_client_hello_get_session_id <- mkMethod4 "s2n_client_hello_get_session_id" Optional c_wrap_client_hello_get_session_id
+    s2n_client_hello_get_compression_methods_length <- mkMethod2 "s2n_client_hello_get_compression_methods_length" Optional c_wrap_client_hello_get_compression_methods_length
+    s2n_client_hello_get_compression_methods <- mkMethod4 "s2n_client_hello_get_compression_methods" Optional c_wrap_client_hello_get_compression_methods
+    s2n_client_hello_get_legacy_protocol_version <- mkMethod2 "s2n_client_hello_get_legacy_protocol_version" Optional c_wrap_client_hello_get_legacy_protocol_version
+    s2n_client_hello_get_supported_groups <- mkMethod4 "s2n_client_hello_get_supported_groups" Optional c_wrap_client_hello_get_supported_groups
+    s2n_client_hello_get_server_name_length <- mkMethod2 "s2n_client_hello_get_server_name_length" Optional c_wrap_client_hello_get_server_name_length
+    s2n_client_hello_get_server_name <- mkMethod4 "s2n_client_hello_get_server_name" Optional c_wrap_client_hello_get_server_name
+    s2n_client_hello_get_legacy_record_version <- mkMethod2 "s2n_client_hello_get_legacy_record_version" Optional c_wrap_client_hello_get_legacy_record_version
+
+    -- File Descriptor & I/O
+    s2n_connection_set_fd <- mkMethod2 "s2n_connection_set_fd" Optional c_wrap_connection_set_fd
+    s2n_connection_set_read_fd <- mkMethod2 "s2n_connection_set_read_fd" Optional c_wrap_connection_set_read_fd
+    s2n_connection_set_write_fd <- mkMethod2 "s2n_connection_set_write_fd" Optional c_wrap_connection_set_write_fd
+    s2n_connection_get_read_fd <- mkMethod2 "s2n_connection_get_read_fd" Optional c_wrap_connection_get_read_fd
+    s2n_connection_get_write_fd <- mkMethod2 "s2n_connection_get_write_fd" Optional c_wrap_connection_get_write_fd
+    s2n_connection_use_corked_io <- mkMethod1 "s2n_connection_use_corked_io" Optional c_wrap_connection_use_corked_io
+    s2n_connection_set_recv_ctx <- mkMethod2 "s2n_connection_set_recv_ctx" Optional c_wrap_connection_set_recv_ctx
+    s2n_connection_set_send_ctx <- mkMethod2 "s2n_connection_set_send_ctx" Optional c_wrap_connection_set_send_ctx
+    s2n_connection_set_recv_cb <- mkMethod2 "s2n_connection_set_recv_cb" Optional c_wrap_connection_set_recv_cb
+    s2n_connection_set_send_cb <- mkMethod2 "s2n_connection_set_send_cb" Optional c_wrap_connection_set_send_cb
+    -- Connection Preferences
+    s2n_connection_prefer_throughput <- mkMethod1 "s2n_connection_prefer_throughput" Optional c_wrap_connection_prefer_throughput
+    s2n_connection_prefer_low_latency <- mkMethod1 "s2n_connection_prefer_low_latency" Optional c_wrap_connection_prefer_low_latency
+    s2n_connection_set_recv_buffering <- mkMethod2 "s2n_connection_set_recv_buffering" Optional c_wrap_connection_set_recv_buffering
+    s2n_peek_buffered <- mkMethod1Direct "s2n_peek_buffered" Optional mk_s2n_peek_buffered
+    s2n_connection_set_dynamic_buffers <- mkMethod2 "s2n_connection_set_dynamic_buffers" Optional c_wrap_connection_set_dynamic_buffers
+    s2n_connection_set_dynamic_record_threshold <- mkMethod3 "s2n_connection_set_dynamic_record_threshold" Optional c_wrap_connection_set_dynamic_record_threshold
+
+    -- Host Verification
+    s2n_connection_set_verify_host_callback <- mkMethod3 "s2n_connection_set_verify_host_callback" Optional c_wrap_connection_set_verify_host_callback
+    -- Blinding & Security
+    s2n_connection_set_blinding <- mkMethod2 "s2n_connection_set_blinding" Optional c_wrap_connection_set_blinding
+    s2n_connection_get_delay <- mkMethod1Direct "s2n_connection_get_delay" Optional mk_s2n_connection_get_delay
+    -- Cipher & Protocol Configuration
+    s2n_connection_set_cipher_preferences <- mkMethod2 "s2n_connection_set_cipher_preferences" Optional c_wrap_connection_set_cipher_preferences
+    s2n_connection_request_key_update <- mkMethod2 "s2n_connection_request_key_update" Optional c_wrap_connection_request_key_update
+    s2n_connection_append_protocol_preference <- mkMethod3 "s2n_connection_append_protocol_preference" Optional c_wrap_connection_append_protocol_preference
+    s2n_connection_set_protocol_preferences <- mkMethod3 "s2n_connection_set_protocol_preferences" Optional c_wrap_connection_set_protocol_preferences
+    -- Server Name (SNI)
+    s2n_set_server_name <- mkMethod2 "s2n_set_server_name" Optional c_wrap_set_server_name
+    s2n_get_server_name <- mkMethod1 "s2n_get_server_name" Optional c_wrap_get_server_name
+    -- Application Protocol (ALPN)
+    s2n_get_application_protocol <- mkMethod1 "s2n_get_application_protocol" Optional c_wrap_get_application_protocol
+    -- OCSP & Certificate Transparency
+    s2n_connection_get_ocsp_response <- mkMethod2 "s2n_connection_get_ocsp_response" Optional c_wrap_connection_get_ocsp_response
+    s2n_connection_get_sct_list <- mkMethod2 "s2n_connection_get_sct_list" Optional c_wrap_connection_get_sct_list
+    -- Handshake & TLS Operations
+    s2n_negotiate <- mkMethod2 "s2n_negotiate" Optional c_wrap_negotiate
+    s2n_send <- mkMethod4 "s2n_send" Optional c_wrap_send
+    s2n_recv <- mkMethod4 "s2n_recv" Optional c_wrap_recv
+    s2n_peek <- mkMethod1Direct "s2n_peek" Optional mk_s2n_peek
+    s2n_connection_free_handshake <- mkMethod1 "s2n_connection_free_handshake" Optional c_wrap_connection_free_handshake
+    s2n_connection_release_buffers <- mkMethod1 "s2n_connection_release_buffers" Optional c_wrap_connection_release_buffers
+    s2n_connection_wipe <- mkMethod1 "s2n_connection_wipe" Optional c_wrap_connection_wipe
+    s2n_connection_free <- mkMethod1 "s2n_connection_free" Optional c_wrap_connection_free
+    s2n_shutdown <- mkMethod2 "s2n_shutdown" Optional c_wrap_shutdown
+    s2n_shutdown_send <- mkMethod2 "s2n_shutdown_send" Optional c_wrap_shutdown_send
+    -- Client Authentication
+    s2n_connection_get_client_auth_type <- mkMethod2 "s2n_connection_get_client_auth_type" Optional c_wrap_connection_get_client_auth_type
+    s2n_connection_set_client_auth_type <- mkMethod2 "s2n_connection_set_client_auth_type" Optional c_wrap_connection_set_client_auth_type
+    s2n_connection_get_client_cert_chain <- mkMethod3 "s2n_connection_get_client_cert_chain" Optional c_wrap_connection_get_client_cert_chain
+    s2n_connection_client_cert_used <- mkMethod1 "s2n_connection_client_cert_used" Optional c_wrap_connection_client_cert_used
+    -- Session Management
+    s2n_connection_add_new_tickets_to_send <- mkMethod2 "s2n_connection_add_new_tickets_to_send" Optional c_wrap_connection_add_new_tickets_to_send
+    s2n_connection_get_tickets_sent <- mkMethod2 "s2n_connection_get_tickets_sent" Optional c_wrap_connection_get_tickets_sent
+    s2n_connection_set_server_keying_material_lifetime <- mkMethod2 "s2n_connection_set_server_keying_material_lifetime" Optional c_wrap_connection_set_server_keying_material_lifetime
+    s2n_session_ticket_get_data_len <- mkMethod2 "s2n_session_ticket_get_data_len" Optional c_wrap_session_ticket_get_data_len
+    s2n_session_ticket_get_data <- mkMethod3 "s2n_session_ticket_get_data" Optional c_wrap_session_ticket_get_data
+    s2n_session_ticket_get_lifetime <- mkMethod2 "s2n_session_ticket_get_lifetime" Optional c_wrap_session_ticket_get_lifetime
+    s2n_connection_set_session <- mkMethod3 "s2n_connection_set_session" Optional c_wrap_connection_set_session
+    s2n_connection_get_session <- mkMethod3 "s2n_connection_get_session" Optional c_wrap_connection_get_session
+    s2n_connection_get_session_ticket_lifetime_hint <- mkMethod1 "s2n_connection_get_session_ticket_lifetime_hint" Optional c_wrap_connection_get_session_ticket_lifetime_hint
+    s2n_connection_get_session_length <- mkMethod1 "s2n_connection_get_session_length" Optional c_wrap_connection_get_session_length
+    s2n_connection_get_session_id_length <- mkMethod1 "s2n_connection_get_session_id_length" Optional c_wrap_connection_get_session_id_length
+    s2n_connection_get_session_id <- mkMethod3 "s2n_connection_get_session_id" Optional c_wrap_connection_get_session_id
+    s2n_connection_is_session_resumed <- mkMethod1 "s2n_connection_is_session_resumed" Optional c_wrap_connection_is_session_resumed
+    -- Certificate Information
+    s2n_connection_is_ocsp_stapled <- mkMethod1 "s2n_connection_is_ocsp_stapled" Optional c_wrap_connection_is_ocsp_stapled
+    s2n_connection_get_selected_signature_algorithm <- mkMethod2 "s2n_connection_get_selected_signature_algorithm" Optional c_wrap_connection_get_selected_signature_algorithm
+    s2n_connection_get_selected_digest_algorithm <- mkMethod2 "s2n_connection_get_selected_digest_algorithm" Optional c_wrap_connection_get_selected_digest_algorithm
+    s2n_connection_get_selected_client_cert_signature_algorithm <- mkMethod2 "s2n_connection_get_selected_client_cert_signature_algorithm" Optional c_wrap_connection_get_selected_client_cert_signature_algorithm
+    s2n_connection_get_selected_client_cert_digest_algorithm <- mkMethod2 "s2n_connection_get_selected_client_cert_digest_algorithm" Optional c_wrap_connection_get_selected_client_cert_digest_algorithm
+    s2n_connection_get_selected_cert <- mkMethod1 "s2n_connection_get_selected_cert" Optional c_wrap_connection_get_selected_cert
+    s2n_cert_chain_get_length <- mkMethod2 "s2n_cert_chain_get_length" Optional c_wrap_cert_chain_get_length
+    s2n_cert_chain_get_cert <- mkMethod3 "s2n_cert_chain_get_cert" Optional c_wrap_cert_chain_get_cert
+    s2n_cert_get_der <- mkMethod3 "s2n_cert_get_der" Optional c_wrap_cert_get_der
+    s2n_connection_get_peer_cert_chain <- mkMethod2 "s2n_connection_get_peer_cert_chain" Optional c_wrap_connection_get_peer_cert_chain
+    s2n_cert_get_x509_extension_value_length <- mkMethod3 "s2n_cert_get_x509_extension_value_length" Optional c_wrap_cert_get_x509_extension_value_length
+    s2n_cert_get_x509_extension_value <- mkMethod5 "s2n_cert_get_x509_extension_value" Optional c_wrap_cert_get_x509_extension_value
+    s2n_cert_get_utf8_string_from_extension_data_length <- mkMethod3 "s2n_cert_get_utf8_string_from_extension_data_length" Optional c_wrap_cert_get_utf8_string_from_extension_data_length
+    s2n_cert_get_utf8_string_from_extension_data <- mkMethod4 "s2n_cert_get_utf8_string_from_extension_data" Optional c_wrap_cert_get_utf8_string_from_extension_data
+    -- Pre-Shared Keys (PSK)
+    s2n_external_psk_new <- mkMethod0 "s2n_external_psk_new" Optional c_wrap_external_psk_new
+    s2n_psk_free <- mkMethod1 "s2n_psk_free" Optional c_wrap_psk_free
+    s2n_psk_set_identity <- mkMethod3 "s2n_psk_set_identity" Optional c_wrap_psk_set_identity
+    s2n_psk_set_secret <- mkMethod3 "s2n_psk_set_secret" Optional c_wrap_psk_set_secret
+    s2n_psk_set_hmac <- mkMethod2 "s2n_psk_set_hmac" Optional c_wrap_psk_set_hmac
+    s2n_connection_append_psk <- mkMethod2 "s2n_connection_append_psk" Optional c_wrap_connection_append_psk
+    s2n_connection_set_psk_mode <- mkMethod2 "s2n_connection_set_psk_mode" Optional c_wrap_connection_set_psk_mode
+    s2n_connection_get_negotiated_psk_identity_length <- mkMethod2 "s2n_connection_get_negotiated_psk_identity_length" Optional c_wrap_connection_get_negotiated_psk_identity_length
+    s2n_connection_get_negotiated_psk_identity <- mkMethod3 "s2n_connection_get_negotiated_psk_identity" Optional c_wrap_connection_get_negotiated_psk_identity
+    s2n_offered_psk_new <- mkMethod0 "s2n_offered_psk_new" Optional c_wrap_offered_psk_new
+    s2n_offered_psk_free <- mkMethod1 "s2n_offered_psk_free" Optional c_wrap_offered_psk_free
+    s2n_offered_psk_get_identity <- mkMethod3 "s2n_offered_psk_get_identity" Optional c_wrap_offered_psk_get_identity
+    s2n_offered_psk_list_has_next <- mkMethod1Direct "s2n_offered_psk_list_has_next" Optional mk_s2n_offered_psk_list_has_next
+    s2n_offered_psk_list_next <- mkMethod2 "s2n_offered_psk_list_next" Optional c_wrap_offered_psk_list_next
+    s2n_offered_psk_list_reread <- mkMethod1 "s2n_offered_psk_list_reread" Optional c_wrap_offered_psk_list_reread
+    s2n_offered_psk_list_choose_psk <- mkMethod2 "s2n_offered_psk_list_choose_psk" Optional c_wrap_offered_psk_list_choose_psk
+    s2n_psk_configure_early_data <- mkMethod4 "s2n_psk_configure_early_data" Optional c_wrap_psk_configure_early_data
+    s2n_psk_set_application_protocol <- mkMethod3 "s2n_psk_set_application_protocol" Optional c_wrap_psk_set_application_protocol
+    s2n_psk_set_early_data_context <- mkMethod3 "s2n_psk_set_early_data_context" Optional c_wrap_psk_set_early_data_context
+    -- Connection Statistics
+    s2n_connection_get_wire_bytes_in <- mkMethod1Direct "s2n_connection_get_wire_bytes_in" Optional mk_s2n_connection_get_wire_bytes_in
+    s2n_connection_get_wire_bytes_out <- mkMethod1Direct "s2n_connection_get_wire_bytes_out" Optional mk_s2n_connection_get_wire_bytes_out
+    -- Protocol Version Information
+    s2n_connection_get_client_protocol_version <- mkMethod1 "s2n_connection_get_client_protocol_version" Optional c_wrap_connection_get_client_protocol_version
+    s2n_connection_get_server_protocol_version <- mkMethod1 "s2n_connection_get_server_protocol_version" Optional c_wrap_connection_get_server_protocol_version
+    s2n_connection_get_actual_protocol_version <- mkMethod1 "s2n_connection_get_actual_protocol_version" Optional c_wrap_connection_get_actual_protocol_version
+    s2n_connection_get_client_hello_version <- mkMethod1 "s2n_connection_get_client_hello_version" Optional c_wrap_connection_get_client_hello_version
+    -- Cipher & Security Information
+    s2n_connection_get_cipher <- mkMethod1 "s2n_connection_get_cipher" Optional c_wrap_connection_get_cipher
+    s2n_connection_get_certificate_match <- mkMethod2 "s2n_connection_get_certificate_match" Optional c_wrap_connection_get_certificate_match
+    s2n_connection_get_master_secret <- mkMethod3 "s2n_connection_get_master_secret" Optional c_wrap_connection_get_master_secret
+    s2n_connection_tls_exporter <- mkMethod7 "s2n_connection_tls_exporter" Optional c_wrap_connection_tls_exporter
+    s2n_connection_get_cipher_iana_value <- mkMethod3 "s2n_connection_get_cipher_iana_value" Optional c_wrap_connection_get_cipher_iana_value
+    s2n_connection_is_valid_for_cipher_preferences <- mkMethod2 "s2n_connection_is_valid_for_cipher_preferences" Optional c_wrap_connection_is_valid_for_cipher_preferences
+    s2n_connection_get_curve <- mkMethod1 "s2n_connection_get_curve" Optional c_wrap_connection_get_curve
+    s2n_connection_get_kem_name <- mkMethod1 "s2n_connection_get_kem_name" Optional c_wrap_connection_get_kem_name
+    s2n_connection_get_kem_group_name <- mkMethod1 "s2n_connection_get_kem_group_name" Optional c_wrap_connection_get_kem_group_name
+    s2n_connection_get_key_exchange_group <- mkMethod2 "s2n_connection_get_key_exchange_group" Optional c_wrap_connection_get_key_exchange_group
+    s2n_connection_get_alert <- mkMethod1 "s2n_connection_get_alert" Optional c_wrap_connection_get_alert
+    s2n_connection_get_handshake_type_name <- mkMethod1 "s2n_connection_get_handshake_type_name" Optional c_wrap_connection_get_handshake_type_name
+    s2n_connection_get_last_message_name <- mkMethod1 "s2n_connection_get_last_message_name" Optional c_wrap_connection_get_last_message_name
+    -- Async Private Key Operations
+    s2n_async_pkey_op_perform <- mkMethod2 "s2n_async_pkey_op_perform" Optional c_wrap_async_pkey_op_perform
+    s2n_async_pkey_op_apply <- mkMethod2 "s2n_async_pkey_op_apply" Optional c_wrap_async_pkey_op_apply
+    s2n_async_pkey_op_free <- mkMethod1 "s2n_async_pkey_op_free" Optional c_wrap_async_pkey_op_free
+    s2n_async_pkey_op_get_op_type <- mkMethod2 "s2n_async_pkey_op_get_op_type" Optional c_wrap_async_pkey_op_get_op_type
+    s2n_async_pkey_op_get_input_size <- mkMethod2 "s2n_async_pkey_op_get_input_size" Optional c_wrap_async_pkey_op_get_input_size
+    s2n_async_pkey_op_get_input <- mkMethod3 "s2n_async_pkey_op_get_input" Optional c_wrap_async_pkey_op_get_input
+    s2n_async_pkey_op_set_output <- mkMethod3 "s2n_async_pkey_op_set_output" Optional c_wrap_async_pkey_op_set_output
+    -- Early Data
+    s2n_connection_set_server_max_early_data_size <- mkMethod2 "s2n_connection_set_server_max_early_data_size" Optional c_wrap_connection_set_server_max_early_data_size
+    s2n_connection_set_server_early_data_context <- mkMethod3 "s2n_connection_set_server_early_data_context" Optional c_wrap_connection_set_server_early_data_context
+    s2n_connection_get_early_data_status <- mkMethod2 "s2n_connection_get_early_data_status" Optional c_wrap_connection_get_early_data_status
+    s2n_connection_get_remaining_early_data_size <- mkMethod2 "s2n_connection_get_remaining_early_data_size" Optional c_wrap_connection_get_remaining_early_data_size
+    s2n_connection_get_max_early_data_size <- mkMethod2 "s2n_connection_get_max_early_data_size" Optional c_wrap_connection_get_max_early_data_size
+    s2n_send_early_data <- mkMethod5 "s2n_send_early_data" Optional c_wrap_send_early_data
+    s2n_recv_early_data <- mkMethod5 "s2n_recv_early_data" Optional c_wrap_recv_early_data
+    s2n_offered_early_data_get_context_length <- mkMethod2 "s2n_offered_early_data_get_context_length" Optional c_wrap_offered_early_data_get_context_length
+    s2n_offered_early_data_get_context <- mkMethod3 "s2n_offered_early_data_get_context" Optional c_wrap_offered_early_data_get_context
+    s2n_offered_early_data_reject <- mkMethod1 "s2n_offered_early_data_reject" Optional c_wrap_offered_early_data_reject
+    s2n_offered_early_data_accept <- mkMethod1 "s2n_offered_early_data_accept" Optional c_wrap_offered_early_data_accept
+    -- Connection Serialization
+    s2n_connection_serialization_length <- mkMethod2 "s2n_connection_serialization_length" Optional c_wrap_connection_serialization_length
+    s2n_connection_serialize <- mkMethod3 "s2n_connection_serialize" Optional c_wrap_connection_serialize
+    s2n_connection_deserialize <- mkMethod3 "s2n_connection_deserialize" Optional c_wrap_connection_deserialize
+
+    missingSymbols <- readIORef missingRef
+    pure (S2nTlsFfi{..}, missingSymbols)
+
+-- Foreign imports for C wrappers and direct function pointers
+-- These are defined in cbits/s2n_wrapper.c
+
+foreign import ccall "dynamic" mk_s2n_errno_location :: FunPtr (IO (Ptr CInt)) -> IO (Ptr CInt)
+foreign import ccall "dynamic" mk_s2n_strerror :: FunPtr (CInt -> CString -> IO CString) -> CInt -> CString -> IO CString
+foreign import ccall "dynamic" mk_s2n_strerror_debug :: FunPtr (CInt -> CString -> IO CString) -> CInt -> CString -> IO CString
+foreign import ccall "dynamic" mk_s2n_strerror_source :: FunPtr (CInt -> IO CString) -> CInt -> IO CString
+foreign import ccall "dynamic" mk_s2n_strerror_name :: FunPtr (CInt -> IO CString) -> CInt -> IO CString
+foreign import ccall "dynamic" mk_s2n_error_get_type :: FunPtr (CInt -> IO S2nErrorType) -> CInt -> IO S2nErrorType
+foreign import ccall "dynamic" mk_s2n_get_openssl_version :: FunPtr (IO CLong) -> IO CLong
+foreign import ccall "dynamic" mk_s2n_stack_traces_enabled :: FunPtr (IO CBool) -> IO CBool
+foreign import ccall "dynamic" mk_s2n_peek :: FunPtr (Ptr S2nConnection -> IO Word32) -> Ptr S2nConnection -> IO Word32
+foreign import ccall "dynamic" mk_s2n_peek_buffered :: FunPtr (Ptr S2nConnection -> IO Word32) -> Ptr S2nConnection -> IO Word32
+foreign import ccall "dynamic" mk_s2n_connection_get_delay :: FunPtr (Ptr S2nConnection -> IO Word64) -> Ptr S2nConnection -> IO Word64
+foreign import ccall "dynamic" mk_s2n_connection_get_wire_bytes_in :: FunPtr (Ptr S2nConnection -> IO Word64) -> Ptr S2nConnection -> IO Word64
+foreign import ccall "dynamic" mk_s2n_connection_get_wire_bytes_out :: FunPtr (Ptr S2nConnection -> IO Word64) -> Ptr S2nConnection -> IO Word64
+foreign import ccall "dynamic" mk_s2n_cert_chain_and_key_get_ctx :: FunPtr (Ptr S2nCertChainAndKey -> IO (Ptr ())) -> Ptr S2nCertChainAndKey -> IO (Ptr ())
+foreign import ccall "dynamic" mk_s2n_offered_psk_list_has_next :: FunPtr (Ptr S2nOfferedPskList -> IO CBool) -> Ptr S2nOfferedPskList -> IO CBool
+
+-- C wrapper imports
+foreign import ccall safe "s2n_wrap_init" c_wrap_init :: FunPtr () -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_cleanup" c_wrap_cleanup :: FunPtr () -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_cleanup_final" c_wrap_cleanup_final :: FunPtr () -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_crypto_disable_init" c_wrap_crypto_disable_init :: FunPtr () -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_disable_atexit" c_wrap_disable_atexit :: FunPtr () -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_get_fips_mode" c_wrap_get_fips_mode :: FunPtr () -> Ptr S2nFipsMode -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_stack_traces_enabled_set" c_wrap_stack_traces_enabled_set :: FunPtr () -> CInt -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_calculate_stacktrace" c_wrap_calculate_stacktrace :: FunPtr () -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_free_stacktrace" c_wrap_free_stacktrace :: FunPtr () -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_get_stacktrace" c_wrap_get_stacktrace :: FunPtr () -> Ptr S2nStacktrace -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_new" c_wrap_config_new :: FunPtr () -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO (Ptr S2nConfig)
+foreign import ccall safe "s2n_wrap_config_new_minimal" c_wrap_config_new_minimal :: FunPtr () -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO (Ptr S2nConfig)
+foreign import ccall safe "s2n_wrap_config_free" c_wrap_config_free :: FunPtr () -> Ptr S2nConfig -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_free_dhparams" c_wrap_config_free_dhparams :: FunPtr () -> Ptr S2nConfig -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_free_cert_chain_and_key" c_wrap_config_free_cert_chain_and_key :: FunPtr () -> Ptr S2nConfig -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_set_wall_clock" c_wrap_config_set_wall_clock :: FunPtr () -> Ptr S2nConfig -> S2nClockTimeNanoseconds -> Ptr () -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_set_monotonic_clock" c_wrap_config_set_monotonic_clock :: FunPtr () -> Ptr S2nConfig -> S2nClockTimeNanoseconds -> Ptr () -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_set_cache_store_callback" c_wrap_config_set_cache_store_callback :: FunPtr () -> Ptr S2nConfig -> S2nCacheStoreCallback -> Ptr () -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_set_cache_retrieve_callback" c_wrap_config_set_cache_retrieve_callback :: FunPtr () -> Ptr S2nConfig -> S2nCacheRetrieveCallback -> Ptr () -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_set_cache_delete_callback" c_wrap_config_set_cache_delete_callback :: FunPtr () -> Ptr S2nConfig -> S2nCacheDeleteCallback -> Ptr () -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_mem_set_callbacks" c_wrap_mem_set_callbacks :: FunPtr () -> S2nMemInitCallback -> S2nMemCleanupCallback -> S2nMemMallocCallback -> S2nMemFreeCallback -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_rand_set_callbacks" c_wrap_rand_set_callbacks :: FunPtr () -> S2nRandInitCallback -> S2nRandCleanupCallback -> S2nRandSeedCallback -> S2nRandMixCallback -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_cert_chain_and_key_new" c_wrap_cert_chain_and_key_new :: FunPtr () -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO (Ptr S2nCertChainAndKey)
+foreign import ccall safe "s2n_wrap_cert_chain_and_key_load_pem" c_wrap_cert_chain_and_key_load_pem :: FunPtr () -> Ptr S2nCertChainAndKey -> CString -> CString -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_cert_chain_and_key_load_pem_bytes" c_wrap_cert_chain_and_key_load_pem_bytes :: FunPtr () -> Ptr S2nCertChainAndKey -> Ptr Word8 -> Word32 -> Ptr Word8 -> Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_cert_chain_and_key_load_public_pem_bytes" c_wrap_cert_chain_and_key_load_public_pem_bytes :: FunPtr () -> Ptr S2nCertChainAndKey -> Ptr Word8 -> Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_cert_chain_and_key_free" c_wrap_cert_chain_and_key_free :: FunPtr () -> Ptr S2nCertChainAndKey -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_cert_chain_and_key_set_ctx" c_wrap_cert_chain_and_key_set_ctx :: FunPtr () -> Ptr S2nCertChainAndKey -> Ptr () -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_cert_chain_and_key_get_private_key" c_wrap_cert_chain_and_key_get_private_key :: FunPtr () -> Ptr S2nCertChainAndKey -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO (Ptr S2nCertPrivateKey)
+foreign import ccall safe "s2n_wrap_cert_chain_and_key_set_ocsp_data" c_wrap_cert_chain_and_key_set_ocsp_data :: FunPtr () -> Ptr S2nCertChainAndKey -> Ptr Word8 -> Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_cert_chain_and_key_set_sct_list" c_wrap_cert_chain_and_key_set_sct_list :: FunPtr () -> Ptr S2nCertChainAndKey -> Ptr Word8 -> Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_set_cert_tiebreak_callback" c_wrap_config_set_cert_tiebreak_callback :: FunPtr () -> Ptr S2nConfig -> S2nCertTiebreakCallback -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_add_cert_chain_and_key" c_wrap_config_add_cert_chain_and_key :: FunPtr () -> Ptr S2nConfig -> CString -> CString -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_add_cert_chain_and_key_to_store" c_wrap_config_add_cert_chain_and_key_to_store :: FunPtr () -> Ptr S2nConfig -> Ptr S2nCertChainAndKey -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_set_cert_chain_and_key_defaults" c_wrap_config_set_cert_chain_and_key_defaults :: FunPtr () -> Ptr S2nConfig -> Ptr (Ptr S2nCertChainAndKey) -> Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_set_verification_ca_location" c_wrap_config_set_verification_ca_location :: FunPtr () -> Ptr S2nConfig -> CString -> CString -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_add_pem_to_trust_store" c_wrap_config_add_pem_to_trust_store :: FunPtr () -> Ptr S2nConfig -> CString -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_wipe_trust_store" c_wrap_config_wipe_trust_store :: FunPtr () -> Ptr S2nConfig -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_load_system_certs" c_wrap_config_load_system_certs :: FunPtr () -> Ptr S2nConfig -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_set_cert_authorities_from_trust_store" c_wrap_config_set_cert_authorities_from_trust_store :: FunPtr () -> Ptr S2nConfig -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_set_verify_after_sign" c_wrap_config_set_verify_after_sign :: FunPtr () -> Ptr S2nConfig -> S2nVerifyAfterSign -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_set_check_stapled_ocsp_response" c_wrap_config_set_check_stapled_ocsp_response :: FunPtr () -> Ptr S2nConfig -> CInt -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_disable_x509_time_verification" c_wrap_config_disable_x509_time_verification :: FunPtr () -> Ptr S2nConfig -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_disable_x509_verification" c_wrap_config_disable_x509_verification :: FunPtr () -> Ptr S2nConfig -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_set_max_cert_chain_depth" c_wrap_config_set_max_cert_chain_depth :: FunPtr () -> Ptr S2nConfig -> Word16 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_set_verify_host_callback" c_wrap_config_set_verify_host_callback :: FunPtr () -> Ptr S2nConfig -> S2nVerifyHostFn -> Ptr () -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_add_dhparams" c_wrap_config_add_dhparams :: FunPtr () -> Ptr S2nConfig -> CString -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_set_cipher_preferences" c_wrap_config_set_cipher_preferences :: FunPtr () -> Ptr S2nConfig -> CString -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_append_protocol_preference" c_wrap_config_append_protocol_preference :: FunPtr () -> Ptr S2nConfig -> Ptr Word8 -> Word8 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_set_protocol_preferences" c_wrap_config_set_protocol_preferences :: FunPtr () -> Ptr S2nConfig -> Ptr CString -> CInt -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_set_status_request_type" c_wrap_config_set_status_request_type :: FunPtr () -> Ptr S2nConfig -> S2nStatusRequestType -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_set_ct_support_level" c_wrap_config_set_ct_support_level :: FunPtr () -> Ptr S2nConfig -> S2nCtSupportLevel -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_set_alert_behavior" c_wrap_config_set_alert_behavior :: FunPtr () -> Ptr S2nConfig -> S2nAlertBehavior -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_set_extension_data" c_wrap_config_set_extension_data :: FunPtr () -> Ptr S2nConfig -> S2nTlsExtensionType -> Ptr Word8 -> Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_send_max_fragment_length" c_wrap_config_send_max_fragment_length :: FunPtr () -> Ptr S2nConfig -> S2nMaxFragLen -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_accept_max_fragment_length" c_wrap_config_accept_max_fragment_length :: FunPtr () -> Ptr S2nConfig -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_set_session_state_lifetime" c_wrap_config_set_session_state_lifetime :: FunPtr () -> Ptr S2nConfig -> Word64 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_set_session_tickets_onoff" c_wrap_config_set_session_tickets_onoff :: FunPtr () -> Ptr S2nConfig -> Word8 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_set_session_cache_onoff" c_wrap_config_set_session_cache_onoff :: FunPtr () -> Ptr S2nConfig -> Word8 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_set_ticket_encrypt_decrypt_key_lifetime" c_wrap_config_set_ticket_encrypt_decrypt_key_lifetime :: FunPtr () -> Ptr S2nConfig -> Word64 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_set_ticket_decrypt_key_lifetime" c_wrap_config_set_ticket_decrypt_key_lifetime :: FunPtr () -> Ptr S2nConfig -> Word64 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_add_ticket_crypto_key" c_wrap_config_add_ticket_crypto_key :: FunPtr () -> Ptr S2nConfig -> Ptr Word8 -> Word32 -> Ptr Word8 -> Word32 -> Word64 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_require_ticket_forward_secrecy" c_wrap_config_require_ticket_forward_secrecy :: FunPtr () -> Ptr S2nConfig -> CInt -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_set_send_buffer_size" c_wrap_config_set_send_buffer_size :: FunPtr () -> Ptr S2nConfig -> Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_set_recv_multi_record" c_wrap_config_set_recv_multi_record :: FunPtr () -> Ptr S2nConfig -> CInt -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_set_ctx" c_wrap_config_set_ctx :: FunPtr () -> Ptr S2nConfig -> Ptr () -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_get_ctx" c_wrap_config_get_ctx :: FunPtr () -> Ptr S2nConfig -> Ptr (Ptr ()) -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_set_client_hello_cb" c_wrap_config_set_client_hello_cb :: FunPtr () -> Ptr S2nConfig -> S2nClientHelloFn -> Ptr () -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_set_client_hello_cb_mode" c_wrap_config_set_client_hello_cb_mode :: FunPtr () -> Ptr S2nConfig -> S2nClientHelloCbMode -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_set_max_blinding_delay" c_wrap_config_set_max_blinding_delay :: FunPtr () -> Ptr S2nConfig -> Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_get_client_auth_type" c_wrap_config_get_client_auth_type :: FunPtr () -> Ptr S2nConfig -> Ptr S2nCertAuthType -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_set_client_auth_type" c_wrap_config_set_client_auth_type :: FunPtr () -> Ptr S2nConfig -> S2nCertAuthType -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_set_initial_ticket_count" c_wrap_config_set_initial_ticket_count :: FunPtr () -> Ptr S2nConfig -> Word8 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_set_psk_mode" c_wrap_config_set_psk_mode :: FunPtr () -> Ptr S2nConfig -> S2nPskMode -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_set_psk_selection_callback" c_wrap_config_set_psk_selection_callback :: FunPtr () -> Ptr S2nConfig -> S2nPskSelectionCallback -> Ptr () -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_set_async_pkey_callback" c_wrap_config_set_async_pkey_callback :: FunPtr () -> Ptr S2nConfig -> S2nAsyncPkeyFn -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_set_async_pkey_validation_mode" c_wrap_config_set_async_pkey_validation_mode :: FunPtr () -> Ptr S2nConfig -> S2nAsyncPkeyValidationMode -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_set_session_ticket_cb" c_wrap_config_set_session_ticket_cb :: FunPtr () -> Ptr S2nConfig -> S2nSessionTicketFn -> Ptr () -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_set_key_log_cb" c_wrap_config_set_key_log_cb :: FunPtr () -> Ptr S2nConfig -> S2nKeyLogFn -> Ptr () -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_enable_cert_req_dss_legacy_compat" c_wrap_config_enable_cert_req_dss_legacy_compat :: FunPtr () -> Ptr S2nConfig -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_set_server_max_early_data_size" c_wrap_config_set_server_max_early_data_size :: FunPtr () -> Ptr S2nConfig -> Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_set_early_data_cb" c_wrap_config_set_early_data_cb :: FunPtr () -> Ptr S2nConfig -> S2nEarlyDataCb -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_get_supported_groups" c_wrap_config_get_supported_groups :: FunPtr () -> Ptr S2nConfig -> Ptr Word16 -> Word16 -> Ptr Word16 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_config_set_serialization_version" c_wrap_config_set_serialization_version :: FunPtr () -> Ptr S2nConfig -> S2nSerializationVersion -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_new" c_wrap_connection_new :: FunPtr () -> S2nMode -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO (Ptr S2nConnection)
+foreign import ccall safe "s2n_wrap_connection_set_config" c_wrap_connection_set_config :: FunPtr () -> Ptr S2nConnection -> Ptr S2nConfig -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_set_ctx" c_wrap_connection_set_ctx :: FunPtr () -> Ptr S2nConnection -> Ptr () -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_get_ctx" c_wrap_connection_get_ctx :: FunPtr () -> Ptr S2nConnection -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO (Ptr ())
+foreign import ccall safe "s2n_wrap_client_hello_cb_done" c_wrap_client_hello_cb_done :: FunPtr () -> Ptr S2nConnection -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_server_name_extension_used" c_wrap_connection_server_name_extension_used :: FunPtr () -> Ptr S2nConnection -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_get_client_hello" c_wrap_connection_get_client_hello :: FunPtr () -> Ptr S2nConnection -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO (Ptr S2nClientHello)
+foreign import ccall safe "s2n_wrap_client_hello_parse_message" c_wrap_client_hello_parse_message :: FunPtr () -> Ptr Word8 -> Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO (Ptr S2nClientHello)
+foreign import ccall safe "s2n_wrap_client_hello_free" c_wrap_client_hello_free :: FunPtr () -> Ptr (Ptr S2nClientHello) -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_client_hello_get_raw_message_length" c_wrap_client_hello_get_raw_message_length :: FunPtr () -> Ptr S2nClientHello -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CSsize
+foreign import ccall safe "s2n_wrap_client_hello_get_raw_message" c_wrap_client_hello_get_raw_message :: FunPtr () -> Ptr S2nClientHello -> Ptr Word8 -> Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CSsize
+foreign import ccall safe "s2n_wrap_client_hello_get_cipher_suites_length" c_wrap_client_hello_get_cipher_suites_length :: FunPtr () -> Ptr S2nClientHello -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CSsize
+foreign import ccall safe "s2n_wrap_client_hello_get_cipher_suites" c_wrap_client_hello_get_cipher_suites :: FunPtr () -> Ptr S2nClientHello -> Ptr Word8 -> Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CSsize
+foreign import ccall safe "s2n_wrap_client_hello_get_extensions_length" c_wrap_client_hello_get_extensions_length :: FunPtr () -> Ptr S2nClientHello -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CSsize
+foreign import ccall safe "s2n_wrap_client_hello_get_extensions" c_wrap_client_hello_get_extensions :: FunPtr () -> Ptr S2nClientHello -> Ptr Word8 -> Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CSsize
+foreign import ccall safe "s2n_wrap_client_hello_get_extension_length" c_wrap_client_hello_get_extension_length :: FunPtr () -> Ptr S2nClientHello -> S2nTlsExtensionType -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CSsize
+foreign import ccall safe "s2n_wrap_client_hello_get_extension_by_id" c_wrap_client_hello_get_extension_by_id :: FunPtr () -> Ptr S2nClientHello -> S2nTlsExtensionType -> Ptr Word8 -> Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CSsize
+foreign import ccall safe "s2n_wrap_client_hello_has_extension" c_wrap_client_hello_has_extension :: FunPtr () -> Ptr S2nClientHello -> Word16 -> Ptr CInt -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_client_hello_get_session_id_length" c_wrap_client_hello_get_session_id_length :: FunPtr () -> Ptr S2nClientHello -> Ptr Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_client_hello_get_session_id" c_wrap_client_hello_get_session_id :: FunPtr () -> Ptr S2nClientHello -> Ptr Word8 -> Ptr Word32 -> Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_client_hello_get_compression_methods_length" c_wrap_client_hello_get_compression_methods_length :: FunPtr () -> Ptr S2nClientHello -> Ptr Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_client_hello_get_compression_methods" c_wrap_client_hello_get_compression_methods :: FunPtr () -> Ptr S2nClientHello -> Ptr Word8 -> Word32 -> Ptr Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_client_hello_get_legacy_protocol_version" c_wrap_client_hello_get_legacy_protocol_version :: FunPtr () -> Ptr S2nClientHello -> Ptr Word8 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_client_hello_get_supported_groups" c_wrap_client_hello_get_supported_groups :: FunPtr () -> Ptr S2nClientHello -> Ptr Word16 -> Word16 -> Ptr Word16 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_client_hello_get_server_name_length" c_wrap_client_hello_get_server_name_length :: FunPtr () -> Ptr S2nClientHello -> Ptr Word16 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_client_hello_get_server_name" c_wrap_client_hello_get_server_name :: FunPtr () -> Ptr S2nClientHello -> Ptr Word8 -> Word16 -> Ptr Word16 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_client_hello_get_legacy_record_version" c_wrap_client_hello_get_legacy_record_version :: FunPtr () -> Ptr S2nClientHello -> Ptr Word8 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_set_fd" c_wrap_connection_set_fd :: FunPtr () -> Ptr S2nConnection -> CInt -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_set_read_fd" c_wrap_connection_set_read_fd :: FunPtr () -> Ptr S2nConnection -> CInt -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_set_write_fd" c_wrap_connection_set_write_fd :: FunPtr () -> Ptr S2nConnection -> CInt -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_get_read_fd" c_wrap_connection_get_read_fd :: FunPtr () -> Ptr S2nConnection -> Ptr CInt -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_get_write_fd" c_wrap_connection_get_write_fd :: FunPtr () -> Ptr S2nConnection -> Ptr CInt -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_use_corked_io" c_wrap_connection_use_corked_io :: FunPtr () -> Ptr S2nConnection -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_set_recv_ctx" c_wrap_connection_set_recv_ctx :: FunPtr () -> Ptr S2nConnection -> Ptr () -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_set_send_ctx" c_wrap_connection_set_send_ctx :: FunPtr () -> Ptr S2nConnection -> Ptr () -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_set_recv_cb" c_wrap_connection_set_recv_cb :: FunPtr () -> Ptr S2nConnection -> S2nRecvFn -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_set_send_cb" c_wrap_connection_set_send_cb :: FunPtr () -> Ptr S2nConnection -> S2nSendFn -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_prefer_throughput" c_wrap_connection_prefer_throughput :: FunPtr () -> Ptr S2nConnection -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_prefer_low_latency" c_wrap_connection_prefer_low_latency :: FunPtr () -> Ptr S2nConnection -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_set_recv_buffering" c_wrap_connection_set_recv_buffering :: FunPtr () -> Ptr S2nConnection -> CInt -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_set_dynamic_buffers" c_wrap_connection_set_dynamic_buffers :: FunPtr () -> Ptr S2nConnection -> CInt -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_set_dynamic_record_threshold" c_wrap_connection_set_dynamic_record_threshold :: FunPtr () -> Ptr S2nConnection -> Word32 -> Word16 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_set_verify_host_callback" c_wrap_connection_set_verify_host_callback :: FunPtr () -> Ptr S2nConnection -> S2nVerifyHostFn -> Ptr () -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_set_blinding" c_wrap_connection_set_blinding :: FunPtr () -> Ptr S2nConnection -> S2nBlinding -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_set_cipher_preferences" c_wrap_connection_set_cipher_preferences :: FunPtr () -> Ptr S2nConnection -> CString -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_request_key_update" c_wrap_connection_request_key_update :: FunPtr () -> Ptr S2nConnection -> S2nPeerKeyUpdate -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_append_protocol_preference" c_wrap_connection_append_protocol_preference :: FunPtr () -> Ptr S2nConnection -> Ptr Word8 -> Word8 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_set_protocol_preferences" c_wrap_connection_set_protocol_preferences :: FunPtr () -> Ptr S2nConnection -> Ptr CString -> CInt -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_set_server_name" c_wrap_set_server_name :: FunPtr () -> Ptr S2nConnection -> CString -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_get_server_name" c_wrap_get_server_name :: FunPtr () -> Ptr S2nConnection -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CString
+foreign import ccall safe "s2n_wrap_get_application_protocol" c_wrap_get_application_protocol :: FunPtr () -> Ptr S2nConnection -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CString
+foreign import ccall safe "s2n_wrap_connection_get_ocsp_response" c_wrap_connection_get_ocsp_response :: FunPtr () -> Ptr S2nConnection -> Ptr Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO (Ptr Word8)
+foreign import ccall safe "s2n_wrap_connection_get_sct_list" c_wrap_connection_get_sct_list :: FunPtr () -> Ptr S2nConnection -> Ptr Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO (Ptr Word8)
+foreign import ccall safe "s2n_wrap_negotiate" c_wrap_negotiate :: FunPtr () -> Ptr S2nConnection -> Ptr S2nBlockedStatus -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_send" c_wrap_send :: FunPtr () -> Ptr S2nConnection -> Ptr () -> CSsize -> Ptr S2nBlockedStatus -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CSsize
+foreign import ccall safe "s2n_wrap_recv" c_wrap_recv :: FunPtr () -> Ptr S2nConnection -> Ptr () -> CSsize -> Ptr S2nBlockedStatus -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CSsize
+foreign import ccall safe "s2n_wrap_connection_free_handshake" c_wrap_connection_free_handshake :: FunPtr () -> Ptr S2nConnection -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_release_buffers" c_wrap_connection_release_buffers :: FunPtr () -> Ptr S2nConnection -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_wipe" c_wrap_connection_wipe :: FunPtr () -> Ptr S2nConnection -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_free" c_wrap_connection_free :: FunPtr () -> Ptr S2nConnection -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_shutdown" c_wrap_shutdown :: FunPtr () -> Ptr S2nConnection -> Ptr S2nBlockedStatus -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_shutdown_send" c_wrap_shutdown_send :: FunPtr () -> Ptr S2nConnection -> Ptr S2nBlockedStatus -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_get_client_auth_type" c_wrap_connection_get_client_auth_type :: FunPtr () -> Ptr S2nConnection -> Ptr S2nCertAuthType -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_set_client_auth_type" c_wrap_connection_set_client_auth_type :: FunPtr () -> Ptr S2nConnection -> S2nCertAuthType -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_get_client_cert_chain" c_wrap_connection_get_client_cert_chain :: FunPtr () -> Ptr S2nConnection -> Ptr (Ptr Word8) -> Ptr Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_client_cert_used" c_wrap_connection_client_cert_used :: FunPtr () -> Ptr S2nConnection -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_add_new_tickets_to_send" c_wrap_connection_add_new_tickets_to_send :: FunPtr () -> Ptr S2nConnection -> Word8 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_get_tickets_sent" c_wrap_connection_get_tickets_sent :: FunPtr () -> Ptr S2nConnection -> Ptr Word16 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_set_server_keying_material_lifetime" c_wrap_connection_set_server_keying_material_lifetime :: FunPtr () -> Ptr S2nConnection -> Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_session_ticket_get_data_len" c_wrap_session_ticket_get_data_len :: FunPtr () -> Ptr S2nSessionTicket -> Ptr CSize -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_session_ticket_get_data" c_wrap_session_ticket_get_data :: FunPtr () -> Ptr S2nSessionTicket -> CSize -> Ptr Word8 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_session_ticket_get_lifetime" c_wrap_session_ticket_get_lifetime :: FunPtr () -> Ptr S2nSessionTicket -> Ptr Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_set_session" c_wrap_connection_set_session :: FunPtr () -> Ptr S2nConnection -> Ptr Word8 -> CSize -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_get_session" c_wrap_connection_get_session :: FunPtr () -> Ptr S2nConnection -> Ptr Word8 -> CSize -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_get_session_ticket_lifetime_hint" c_wrap_connection_get_session_ticket_lifetime_hint :: FunPtr () -> Ptr S2nConnection -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_get_session_length" c_wrap_connection_get_session_length :: FunPtr () -> Ptr S2nConnection -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_get_session_id_length" c_wrap_connection_get_session_id_length :: FunPtr () -> Ptr S2nConnection -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_get_session_id" c_wrap_connection_get_session_id :: FunPtr () -> Ptr S2nConnection -> Ptr Word8 -> CSize -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_is_session_resumed" c_wrap_connection_is_session_resumed :: FunPtr () -> Ptr S2nConnection -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_is_ocsp_stapled" c_wrap_connection_is_ocsp_stapled :: FunPtr () -> Ptr S2nConnection -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_get_selected_signature_algorithm" c_wrap_connection_get_selected_signature_algorithm :: FunPtr () -> Ptr S2nConnection -> Ptr S2nTlsSignatureAlgorithm -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_get_selected_digest_algorithm" c_wrap_connection_get_selected_digest_algorithm :: FunPtr () -> Ptr S2nConnection -> Ptr S2nTlsHashAlgorithm -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_get_selected_client_cert_signature_algorithm" c_wrap_connection_get_selected_client_cert_signature_algorithm :: FunPtr () -> Ptr S2nConnection -> Ptr S2nTlsSignatureAlgorithm -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_get_selected_client_cert_digest_algorithm" c_wrap_connection_get_selected_client_cert_digest_algorithm :: FunPtr () -> Ptr S2nConnection -> Ptr S2nTlsHashAlgorithm -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_get_selected_cert" c_wrap_connection_get_selected_cert :: FunPtr () -> Ptr S2nConnection -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO (Ptr S2nCertChainAndKey)
+foreign import ccall safe "s2n_wrap_cert_chain_get_length" c_wrap_cert_chain_get_length :: FunPtr () -> Ptr S2nCertChainAndKey -> Ptr Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_cert_chain_get_cert" c_wrap_cert_chain_get_cert :: FunPtr () -> Ptr S2nCertChainAndKey -> Ptr (Ptr S2nCert) -> Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_cert_get_der" c_wrap_cert_get_der :: FunPtr () -> Ptr S2nCert -> Ptr (Ptr Word8) -> Ptr Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_get_peer_cert_chain" c_wrap_connection_get_peer_cert_chain :: FunPtr () -> Ptr S2nConnection -> Ptr S2nCertChainAndKey -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_cert_get_x509_extension_value_length" c_wrap_cert_get_x509_extension_value_length :: FunPtr () -> Ptr S2nCert -> Ptr Word8 -> Ptr Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_cert_get_x509_extension_value" c_wrap_cert_get_x509_extension_value :: FunPtr () -> Ptr S2nCert -> Ptr Word8 -> Ptr Word8 -> Ptr Word32 -> Ptr CInt -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_cert_get_utf8_string_from_extension_data_length" c_wrap_cert_get_utf8_string_from_extension_data_length :: FunPtr () -> Ptr Word8 -> Word32 -> Ptr Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_cert_get_utf8_string_from_extension_data" c_wrap_cert_get_utf8_string_from_extension_data :: FunPtr () -> Ptr Word8 -> Word32 -> Ptr Word8 -> Ptr Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_external_psk_new" c_wrap_external_psk_new :: FunPtr () -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO (Ptr S2nPsk)
+foreign import ccall safe "s2n_wrap_psk_free" c_wrap_psk_free :: FunPtr () -> Ptr (Ptr S2nPsk) -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_psk_set_identity" c_wrap_psk_set_identity :: FunPtr () -> Ptr S2nPsk -> Ptr Word8 -> Word16 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_psk_set_secret" c_wrap_psk_set_secret :: FunPtr () -> Ptr S2nPsk -> Ptr Word8 -> Word16 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_psk_set_hmac" c_wrap_psk_set_hmac :: FunPtr () -> Ptr S2nPsk -> S2nPskHmac -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_append_psk" c_wrap_connection_append_psk :: FunPtr () -> Ptr S2nConnection -> Ptr S2nPsk -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_set_psk_mode" c_wrap_connection_set_psk_mode :: FunPtr () -> Ptr S2nConnection -> S2nPskMode -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_get_negotiated_psk_identity_length" c_wrap_connection_get_negotiated_psk_identity_length :: FunPtr () -> Ptr S2nConnection -> Ptr Word16 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_get_negotiated_psk_identity" c_wrap_connection_get_negotiated_psk_identity :: FunPtr () -> Ptr S2nConnection -> Ptr Word8 -> Word16 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_offered_psk_new" c_wrap_offered_psk_new :: FunPtr () -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO (Ptr S2nOfferedPsk)
+foreign import ccall safe "s2n_wrap_offered_psk_free" c_wrap_offered_psk_free :: FunPtr () -> Ptr (Ptr S2nOfferedPsk) -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_offered_psk_get_identity" c_wrap_offered_psk_get_identity :: FunPtr () -> Ptr S2nOfferedPsk -> Ptr (Ptr Word8) -> Ptr Word16 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_offered_psk_list_next" c_wrap_offered_psk_list_next :: FunPtr () -> Ptr S2nOfferedPskList -> Ptr S2nOfferedPsk -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_offered_psk_list_reread" c_wrap_offered_psk_list_reread :: FunPtr () -> Ptr S2nOfferedPskList -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_offered_psk_list_choose_psk" c_wrap_offered_psk_list_choose_psk :: FunPtr () -> Ptr S2nOfferedPskList -> Ptr S2nOfferedPsk -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_psk_configure_early_data" c_wrap_psk_configure_early_data :: FunPtr () -> Ptr S2nPsk -> Word32 -> Word8 -> Word8 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_psk_set_application_protocol" c_wrap_psk_set_application_protocol :: FunPtr () -> Ptr S2nPsk -> Ptr Word8 -> Word8 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_psk_set_early_data_context" c_wrap_psk_set_early_data_context :: FunPtr () -> Ptr S2nPsk -> Ptr Word8 -> Word16 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_get_client_protocol_version" c_wrap_connection_get_client_protocol_version :: FunPtr () -> Ptr S2nConnection -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_get_server_protocol_version" c_wrap_connection_get_server_protocol_version :: FunPtr () -> Ptr S2nConnection -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_get_actual_protocol_version" c_wrap_connection_get_actual_protocol_version :: FunPtr () -> Ptr S2nConnection -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_get_client_hello_version" c_wrap_connection_get_client_hello_version :: FunPtr () -> Ptr S2nConnection -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_get_cipher" c_wrap_connection_get_cipher :: FunPtr () -> Ptr S2nConnection -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CString
+foreign import ccall safe "s2n_wrap_connection_get_certificate_match" c_wrap_connection_get_certificate_match :: FunPtr () -> Ptr S2nConnection -> Ptr S2nCertSniMatch -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_get_master_secret" c_wrap_connection_get_master_secret :: FunPtr () -> Ptr S2nConnection -> Ptr Word8 -> CSize -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_tls_exporter" c_wrap_connection_tls_exporter :: FunPtr () -> Ptr S2nConnection -> Ptr Word8 -> Word32 -> Ptr Word8 -> Word32 -> Ptr Word8 -> Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_get_cipher_iana_value" c_wrap_connection_get_cipher_iana_value :: FunPtr () -> Ptr S2nConnection -> Ptr Word8 -> Ptr Word8 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_is_valid_for_cipher_preferences" c_wrap_connection_is_valid_for_cipher_preferences :: FunPtr () -> Ptr S2nConnection -> CString -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_get_curve" c_wrap_connection_get_curve :: FunPtr () -> Ptr S2nConnection -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CString
+foreign import ccall safe "s2n_wrap_connection_get_kem_name" c_wrap_connection_get_kem_name :: FunPtr () -> Ptr S2nConnection -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CString
+foreign import ccall safe "s2n_wrap_connection_get_kem_group_name" c_wrap_connection_get_kem_group_name :: FunPtr () -> Ptr S2nConnection -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CString
+foreign import ccall safe "s2n_wrap_connection_get_key_exchange_group" c_wrap_connection_get_key_exchange_group :: FunPtr () -> Ptr S2nConnection -> Ptr CString -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_get_alert" c_wrap_connection_get_alert :: FunPtr () -> Ptr S2nConnection -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_get_handshake_type_name" c_wrap_connection_get_handshake_type_name :: FunPtr () -> Ptr S2nConnection -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CString
+foreign import ccall safe "s2n_wrap_connection_get_last_message_name" c_wrap_connection_get_last_message_name :: FunPtr () -> Ptr S2nConnection -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CString
+foreign import ccall safe "s2n_wrap_async_pkey_op_perform" c_wrap_async_pkey_op_perform :: FunPtr () -> Ptr S2nAsyncPkeyOp -> Ptr S2nCertPrivateKey -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_async_pkey_op_apply" c_wrap_async_pkey_op_apply :: FunPtr () -> Ptr S2nAsyncPkeyOp -> Ptr S2nConnection -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_async_pkey_op_free" c_wrap_async_pkey_op_free :: FunPtr () -> Ptr S2nAsyncPkeyOp -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_async_pkey_op_get_op_type" c_wrap_async_pkey_op_get_op_type :: FunPtr () -> Ptr S2nAsyncPkeyOp -> Ptr S2nAsyncPkeyOpType -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_async_pkey_op_get_input_size" c_wrap_async_pkey_op_get_input_size :: FunPtr () -> Ptr S2nAsyncPkeyOp -> Ptr Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_async_pkey_op_get_input" c_wrap_async_pkey_op_get_input :: FunPtr () -> Ptr S2nAsyncPkeyOp -> Ptr Word8 -> Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_async_pkey_op_set_output" c_wrap_async_pkey_op_set_output :: FunPtr () -> Ptr S2nAsyncPkeyOp -> Ptr Word8 -> Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_set_server_max_early_data_size" c_wrap_connection_set_server_max_early_data_size :: FunPtr () -> Ptr S2nConnection -> Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_set_server_early_data_context" c_wrap_connection_set_server_early_data_context :: FunPtr () -> Ptr S2nConnection -> Ptr Word8 -> Word16 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_get_early_data_status" c_wrap_connection_get_early_data_status :: FunPtr () -> Ptr S2nConnection -> Ptr S2nEarlyDataStatus -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_get_remaining_early_data_size" c_wrap_connection_get_remaining_early_data_size :: FunPtr () -> Ptr S2nConnection -> Ptr Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_get_max_early_data_size" c_wrap_connection_get_max_early_data_size :: FunPtr () -> Ptr S2nConnection -> Ptr Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_send_early_data" c_wrap_send_early_data :: FunPtr () -> Ptr S2nConnection -> Ptr Word8 -> CSsize -> Ptr CSsize -> Ptr S2nBlockedStatus -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_recv_early_data" c_wrap_recv_early_data :: FunPtr () -> Ptr S2nConnection -> Ptr Word8 -> CSsize -> Ptr CSsize -> Ptr S2nBlockedStatus -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_offered_early_data_get_context_length" c_wrap_offered_early_data_get_context_length :: FunPtr () -> Ptr S2nOfferedEarlyData -> Ptr Word16 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_offered_early_data_get_context" c_wrap_offered_early_data_get_context :: FunPtr () -> Ptr S2nOfferedEarlyData -> Ptr Word8 -> Word16 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_offered_early_data_reject" c_wrap_offered_early_data_reject :: FunPtr () -> Ptr S2nOfferedEarlyData -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_offered_early_data_accept" c_wrap_offered_early_data_accept :: FunPtr () -> Ptr S2nOfferedEarlyData -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_serialization_length" c_wrap_connection_serialization_length :: FunPtr () -> Ptr S2nConnection -> Ptr Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_serialize" c_wrap_connection_serialize :: FunPtr () -> Ptr S2nConnection -> Ptr Word8 -> Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_connection_deserialize" c_wrap_connection_deserialize :: FunPtr () -> Ptr S2nConnection -> Ptr Word8 -> Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
diff --git a/src/S2nTls/Ffi/Types.hs b/src/S2nTls/Ffi/Types.hs
new file mode 100644
--- /dev/null
+++ b/src/S2nTls/Ffi/Types.hs
@@ -0,0 +1,1134 @@
+{-# LANGUAGE PatternSynonyms #-}
+
+{- |
+Module      : S2nTls.Ffi.Types
+Description : Core types for s2n-tls FFI bindings
+Copyright   : (c) 2026 Daniel Goertzen
+License     : Apache-2.0
+Maintainer  : daniel.goertzen@gmail.com
+Stability   : experimental
+Portability : non-portable (requires s2n-tls C library)
+
+This module defines the core types used by the s2n-tls FFI bindings,
+including the 'S2nTlsFfi' record that contains all FFI function pointers.
+-}
+module S2nTls.Ffi.Types (
+  -- * Error Types
+  S2nError (..),
+  S2nErrorFuncs (..),
+  MissingSymbol (..),
+
+  -- * Opaque Types
+  S2nConfig,
+  S2nConnection,
+  S2nCertChainAndKey,
+  S2nCert,
+  S2nClientHello,
+  S2nPsk,
+  S2nOfferedPsk,
+  S2nOfferedPskList,
+  S2nSessionTicket,
+  S2nAsyncPkeyOp,
+  S2nCertPrivateKey,
+  S2nOfferedEarlyData,
+  S2nStacktrace,
+
+  -- * Return Codes
+  pattern S2nSuccess,
+  pattern S2nFailure,
+  pattern S2nCallbackBlocked,
+
+  -- * TLS Versions
+  pattern S2nMinimumSupportedTlsRecordMajorVersion,
+  pattern S2nMaximumSupportedTlsRecordMajorVersion,
+  pattern S2nSslv2,
+  pattern S2nSslv3,
+  pattern S2nTls10,
+  pattern S2nTls11,
+  pattern S2nTls12,
+  pattern S2nTls13,
+  pattern S2nUnknownProtocolVersion,
+
+  -- * Enumerations
+  S2nErrorType (..),
+  pattern S2nErrTOk,
+  pattern S2nErrTIo,
+  pattern S2nErrTClosed,
+  pattern S2nErrTBlocked,
+  pattern S2nErrTAlert,
+  pattern S2nErrTProto,
+  pattern S2nErrTInternal,
+  pattern S2nErrTUsage,
+  S2nMode (..),
+  pattern S2nServer,
+  pattern S2nClient,
+  S2nBlinding (..),
+  pattern S2nBuiltInBlinding,
+  pattern S2nSelfServiceBlinding,
+  S2nBlockedStatus (..),
+  pattern S2nNotBlocked,
+  pattern S2nBlockedOnRead,
+  pattern S2nBlockedOnWrite,
+  pattern S2nBlockedOnApplicationInput,
+  pattern S2nBlockedOnEarlyData,
+  S2nCertAuthType (..),
+  pattern S2nCertAuthNone,
+  pattern S2nCertAuthRequired,
+  pattern S2nCertAuthOptional,
+  S2nPskHmac (..),
+  pattern S2nPskHmacSha256,
+  pattern S2nPskHmacSha384,
+  S2nPskMode (..),
+  pattern S2nPskModeResumption,
+  pattern S2nPskModeExternal,
+  S2nEarlyDataStatus (..),
+  pattern S2nEarlyDataStatusOk,
+  pattern S2nEarlyDataStatusNotRequested,
+  pattern S2nEarlyDataStatusRejected,
+  pattern S2nEarlyDataStatusEnd,
+  S2nAsyncPkeyOpType (..),
+  pattern S2nAsyncDecrypt,
+  pattern S2nAsyncSign,
+  S2nAsyncPkeyValidationMode (..),
+  pattern S2nAsyncPkeyValidationFast,
+  pattern S2nAsyncPkeyValidationStrict,
+  S2nSerializationVersion (..),
+  pattern S2nSerializedConnNone,
+  pattern S2nSerializedConnV1,
+  S2nTlsExtensionType (..),
+  pattern S2nExtensionServerName,
+  pattern S2nExtensionMaxFragLen,
+  pattern S2nExtensionOcspStapling,
+  pattern S2nExtensionSupportedGroups,
+  pattern S2nExtensionEcPointFormats,
+  pattern S2nExtensionSignatureAlgorithms,
+  pattern S2nExtensionAlpn,
+  pattern S2nExtensionCertificateTransparency,
+  pattern S2nExtensionRenegotiationInfo,
+  S2nMaxFragLen (..),
+  pattern S2nTlsMaxFragLen512,
+  pattern S2nTlsMaxFragLen1024,
+  pattern S2nTlsMaxFragLen2048,
+  pattern S2nTlsMaxFragLen4096,
+  S2nFipsMode (..),
+  pattern S2nFipsModeDisabled,
+  pattern S2nFipsModeEnabled,
+  S2nStatusRequestType (..),
+  pattern S2nStatusRequestNone,
+  pattern S2nStatusRequestOcsp,
+  S2nCtSupportLevel (..),
+  pattern S2nCtSupportNone,
+  pattern S2nCtSupportRequest,
+  S2nAlertBehavior (..),
+  pattern S2nAlertFailOnWarnings,
+  pattern S2nAlertIgnoreWarnings,
+  S2nClientHelloCbMode (..),
+  pattern S2nClientHelloCbBlocking,
+  pattern S2nClientHelloCbNonblocking,
+  S2nTlsSignatureAlgorithm (..),
+  pattern S2nTlsSignatureAnonymous,
+  pattern S2nTlsSignatureRsa,
+  pattern S2nTlsSignatureEcdsa,
+  pattern S2nTlsSignatureRsaPssRsae,
+  pattern S2nTlsSignatureRsaPssPss,
+  S2nTlsHashAlgorithm (..),
+  pattern S2nTlsHashNone,
+  pattern S2nTlsHashMd5,
+  pattern S2nTlsHashSha1,
+  pattern S2nTlsHashSha224,
+  pattern S2nTlsHashSha256,
+  pattern S2nTlsHashSha384,
+  pattern S2nTlsHashSha512,
+  S2nCertSniMatch (..),
+  pattern S2nCertSniMatchNotApplicable,
+  pattern S2nCertSniMatched,
+  pattern S2nCertSniNoMatchFound,
+  S2nPeerKeyUpdate (..),
+  pattern S2nKeyUpdateNotRequested,
+  pattern S2nKeyUpdateRequested,
+  S2nVerifyAfterSign (..),
+  pattern S2nVerifyAfterSignDisabled,
+  pattern S2nVerifyAfterSignEnabled,
+
+  -- * Callback Types
+  S2nClockTimeNanoseconds,
+  S2nRecvFn,
+  S2nSendFn,
+  S2nCacheStoreCallback,
+  S2nCacheRetrieveCallback,
+  S2nCacheDeleteCallback,
+  S2nMemInitCallback,
+  S2nMemCleanupCallback,
+  S2nMemMallocCallback,
+  S2nMemFreeCallback,
+  S2nRandInitCallback,
+  S2nRandCleanupCallback,
+  S2nRandSeedCallback,
+  S2nRandMixCallback,
+  S2nClientHelloFn,
+  S2nCertTiebreakCallback,
+  S2nVerifyHostFn,
+  S2nPskSelectionCallback,
+  S2nAsyncPkeyFn,
+  S2nSessionTicketFn,
+  S2nKeyLogFn,
+  S2nEarlyDataCb,
+
+  -- * FFI Record
+  S2nTlsFfi (..),
+) where
+
+import Control.Exception (Exception)
+import Data.Word (Word16, Word32, Word64, Word8)
+import Foreign.C.String (CString, castCharToCChar, peekCString)
+import Foreign.C.Types (CBool (..), CInt (..), CLong (..), CSize (..))
+import Foreign.Marshal.Array (pokeArray0)
+import Foreign.Ptr (FunPtr, Ptr, plusPtr)
+import Foreign.Storable (Storable (..))
+import System.Posix.Types (CSsize (..))
+
+--------------------------------------------------------------------------------
+-- Error Types
+--------------------------------------------------------------------------------
+
+{- | Exception thrown when calling an s2n function that wasn't loaded.
+The string contains the name of the missing symbol.
+-}
+newtype MissingSymbol = MissingSymbol String
+  deriving (Show, Eq)
+
+instance Exception MissingSymbol
+
+{- | Full error information captured from an s2n function call.
+Contains the error code plus the string representations.
+-}
+data S2nError = S2nError
+  { s2nErrorCode :: !CInt
+  , s2nErrorDebugMessage :: String
+  -- ^ Result of s2n_strerror_debug()
+  }
+  deriving (Show, Eq)
+
+instance Exception S2nError
+
+{- | Struct of error function pointers, passed to C wrappers.
+Loaded first during initialization - fatal if any are missing.
+-}
+data S2nErrorFuncs = S2nErrorFuncs
+  { errnoLocation :: !(FunPtr (IO (Ptr CInt)))
+  , strerrorDebug :: !(FunPtr (CInt -> CString -> IO CString))
+  , errorGetType :: !(FunPtr (CInt -> IO S2nErrorType))
+  }
+
+instance Storable S2nErrorFuncs where
+  sizeOf _ = 3 * sizeOf (undefined :: FunPtr ())
+  alignment _ = alignment (undefined :: FunPtr ())
+  peek ptr = do
+    a <- peekByteOff ptr 0
+    b <- peekByteOff ptr (sizeOf (undefined :: FunPtr ()))
+    c <- peekByteOff ptr (2 * sizeOf (undefined :: FunPtr ()))
+    pure $ S2nErrorFuncs a b c
+  poke ptr (S2nErrorFuncs a b c) = do
+    pokeByteOff ptr 0 a
+    pokeByteOff ptr (sizeOf (undefined :: FunPtr ())) b
+    pokeByteOff ptr (2 * sizeOf (undefined :: FunPtr ())) c
+
+-- | Size of the debug string buffer in S2nError (must match C S2N_ERROR_DEBUG_STRING_SIZE)
+s2nErrorDebugStringSize :: Int
+s2nErrorDebugStringSize = 256
+
+{- | Output struct populated by C wrappers on error.
+Contains error code and an owned copy of the debug string.
+-}
+instance Storable S2nError where
+  sizeOf _ = sizeOf (undefined :: CInt) + s2nErrorDebugStringSize
+  alignment _ = alignment (undefined :: CInt)
+  peek ptr = do
+    code <- peekByteOff ptr 0
+    -- Read the fixed-size debug string buffer
+    let strPtr = ptr `plusPtr` sizeOf (undefined :: CInt)
+    dbgStr <- peekCString strPtr
+    pure $ S2nError code dbgStr
+  poke ptr (S2nError code dbgStr) = do
+    pokeByteOff ptr 0 code
+    -- Write the debug string to the fixed-size buffer
+    let strPtr = ptr `plusPtr` sizeOf (undefined :: CInt)
+    -- Zero out the buffer first, then copy the string
+    _ <- memset strPtr 0 (fromIntegral s2nErrorDebugStringSize)
+    let truncated = take (s2nErrorDebugStringSize - 1) dbgStr
+    pokeArray0 0 strPtr (map castCharToCChar truncated)
+
+-- | Foreign import for memset
+foreign import ccall unsafe "string.h memset"
+  memset :: Ptr a -> CInt -> CSize -> IO (Ptr a)
+
+--------------------------------------------------------------------------------
+-- Opaque Types
+--------------------------------------------------------------------------------
+
+-- | Opaque type representing an s2n configuration.
+data S2nConfig
+
+-- | Opaque type representing an s2n connection.
+data S2nConnection
+
+-- | Opaque type representing a certificate chain and private key pair.
+data S2nCertChainAndKey
+
+-- | Opaque type representing a certificate.
+data S2nCert
+
+-- | Opaque type representing a parsed ClientHello message.
+data S2nClientHello
+
+-- | Opaque type representing a pre-shared key.
+data S2nPsk
+
+-- | Opaque type representing an offered PSK during negotiation.
+data S2nOfferedPsk
+
+-- | Opaque type representing a list of offered PSKs.
+data S2nOfferedPskList
+
+-- | Opaque type representing a session ticket.
+data S2nSessionTicket
+
+-- | Opaque type representing an async private key operation.
+data S2nAsyncPkeyOp
+
+-- | Opaque type representing a certificate's private key.
+data S2nCertPrivateKey
+
+-- | Opaque type representing offered early data.
+data S2nOfferedEarlyData
+
+-- | Opaque type representing a stack trace.
+data S2nStacktrace
+
+--------------------------------------------------------------------------------
+-- Return Codes
+--------------------------------------------------------------------------------
+
+-- | Function completed successfully.
+pattern S2nSuccess :: CInt
+pattern S2nSuccess = 0
+
+-- | Function encountered an error.
+pattern S2nFailure :: CInt
+pattern S2nFailure = -1
+
+-- | Callback was blocked and needs to be retried.
+pattern S2nCallbackBlocked :: CInt
+pattern S2nCallbackBlocked = -2
+
+--------------------------------------------------------------------------------
+-- TLS Versions
+--------------------------------------------------------------------------------
+
+-- | Minimum supported TLS record major version.
+pattern S2nMinimumSupportedTlsRecordMajorVersion :: Word8
+pattern S2nMinimumSupportedTlsRecordMajorVersion = 2
+
+-- | Maximum supported TLS record major version.
+pattern S2nMaximumSupportedTlsRecordMajorVersion :: Word8
+pattern S2nMaximumSupportedTlsRecordMajorVersion = 3
+
+-- | SSL version 2 (deprecated, insecure).
+pattern S2nSslv2 :: CInt
+pattern S2nSslv2 = 20
+
+-- | SSL version 3 (deprecated, insecure).
+pattern S2nSslv3 :: CInt
+pattern S2nSslv3 = 30
+
+-- | TLS version 1.0.
+pattern S2nTls10 :: CInt
+pattern S2nTls10 = 31
+
+-- | TLS version 1.1.
+pattern S2nTls11 :: CInt
+pattern S2nTls11 = 32
+
+-- | TLS version 1.2.
+pattern S2nTls12 :: CInt
+pattern S2nTls12 = 33
+
+-- | TLS version 1.3.
+pattern S2nTls13 :: CInt
+pattern S2nTls13 = 34
+
+-- | Unknown or unrecognized protocol version.
+pattern S2nUnknownProtocolVersion :: CInt
+pattern S2nUnknownProtocolVersion = 0
+
+--------------------------------------------------------------------------------
+-- Enumerations
+--------------------------------------------------------------------------------
+
+-- | Type of error returned by s2n functions.
+newtype S2nErrorType = S2nErrorType CInt
+  deriving (Eq, Ord, Show, Storable)
+
+-- | No error occurred.
+pattern S2nErrTOk :: S2nErrorType
+pattern S2nErrTOk = S2nErrorType 0
+
+-- | I\/O error (check errno).
+pattern S2nErrTIo :: S2nErrorType
+pattern S2nErrTIo = S2nErrorType 1
+
+-- | Connection was closed.
+pattern S2nErrTClosed :: S2nErrorType
+pattern S2nErrTClosed = S2nErrorType 2
+
+-- | Operation blocked (retry needed).
+pattern S2nErrTBlocked :: S2nErrorType
+pattern S2nErrTBlocked = S2nErrorType 3
+
+-- | TLS alert received.
+pattern S2nErrTAlert :: S2nErrorType
+pattern S2nErrTAlert = S2nErrorType 4
+
+-- | Protocol error.
+pattern S2nErrTProto :: S2nErrorType
+pattern S2nErrTProto = S2nErrorType 5
+
+-- | Internal library error.
+pattern S2nErrTInternal :: S2nErrorType
+pattern S2nErrTInternal = S2nErrorType 6
+
+-- | Incorrect API usage.
+pattern S2nErrTUsage :: S2nErrorType
+pattern S2nErrTUsage = S2nErrorType 7
+
+-- | TLS connection mode (server or client).
+newtype S2nMode = S2nMode CInt
+  deriving (Eq, Ord, Show, Storable)
+
+-- | Server mode.
+pattern S2nServer :: S2nMode
+pattern S2nServer = S2nMode 0
+
+-- | Client mode.
+pattern S2nClient :: S2nMode
+pattern S2nClient = S2nMode 1
+
+-- | Blinding mode for timing attack mitigation.
+newtype S2nBlinding = S2nBlinding CInt
+  deriving (Eq, Ord, Show, Storable)
+
+-- | s2n handles blinding automatically.
+pattern S2nBuiltInBlinding :: S2nBlinding
+pattern S2nBuiltInBlinding = S2nBlinding 0
+
+-- | Application handles blinding delays.
+pattern S2nSelfServiceBlinding :: S2nBlinding
+pattern S2nSelfServiceBlinding = S2nBlinding 1
+
+-- | Status indicating why an operation was blocked.
+newtype S2nBlockedStatus = S2nBlockedStatus CInt
+  deriving (Eq, Ord, Show, Storable)
+
+-- | Operation completed successfully.
+pattern S2nNotBlocked :: S2nBlockedStatus
+pattern S2nNotBlocked = S2nBlockedStatus 0
+
+-- | Blocked waiting for data to read.
+pattern S2nBlockedOnRead :: S2nBlockedStatus
+pattern S2nBlockedOnRead = S2nBlockedStatus 1
+
+-- | Blocked waiting to write data.
+pattern S2nBlockedOnWrite :: S2nBlockedStatus
+pattern S2nBlockedOnWrite = S2nBlockedStatus 2
+
+-- | Blocked waiting for application input.
+pattern S2nBlockedOnApplicationInput :: S2nBlockedStatus
+pattern S2nBlockedOnApplicationInput = S2nBlockedStatus 3
+
+-- | Blocked on early data processing.
+pattern S2nBlockedOnEarlyData :: S2nBlockedStatus
+pattern S2nBlockedOnEarlyData = S2nBlockedStatus 4
+
+-- | Client certificate authentication mode.
+newtype S2nCertAuthType = S2nCertAuthType CInt
+  deriving (Eq, Ord, Show, Storable)
+
+-- | No client authentication.
+pattern S2nCertAuthNone :: S2nCertAuthType
+pattern S2nCertAuthNone = S2nCertAuthType 0
+
+-- | Client certificate required.
+pattern S2nCertAuthRequired :: S2nCertAuthType
+pattern S2nCertAuthRequired = S2nCertAuthType 1
+
+-- | Client certificate optional.
+pattern S2nCertAuthOptional :: S2nCertAuthType
+pattern S2nCertAuthOptional = S2nCertAuthType 2
+
+-- | HMAC algorithm for pre-shared keys.
+newtype S2nPskHmac = S2nPskHmac CInt
+  deriving (Eq, Ord, Show, Storable)
+
+-- | HMAC-SHA256 for PSK.
+pattern S2nPskHmacSha256 :: S2nPskHmac
+pattern S2nPskHmacSha256 = S2nPskHmac 0
+
+-- | HMAC-SHA384 for PSK.
+pattern S2nPskHmacSha384 :: S2nPskHmac
+pattern S2nPskHmacSha384 = S2nPskHmac 1
+
+-- | Pre-shared key mode.
+newtype S2nPskMode = S2nPskMode CInt
+  deriving (Eq, Ord, Show, Storable)
+
+-- | PSK for session resumption.
+pattern S2nPskModeResumption :: S2nPskMode
+pattern S2nPskModeResumption = S2nPskMode 0
+
+-- | External\/out-of-band PSK.
+pattern S2nPskModeExternal :: S2nPskMode
+pattern S2nPskModeExternal = S2nPskMode 1
+
+-- | Status of TLS 1.3 early data (0-RTT).
+newtype S2nEarlyDataStatus = S2nEarlyDataStatus CInt
+  deriving (Eq, Ord, Show, Storable)
+
+-- | Early data accepted.
+pattern S2nEarlyDataStatusOk :: S2nEarlyDataStatus
+pattern S2nEarlyDataStatusOk = S2nEarlyDataStatus 0
+
+-- | Early data not requested.
+pattern S2nEarlyDataStatusNotRequested :: S2nEarlyDataStatus
+pattern S2nEarlyDataStatusNotRequested = S2nEarlyDataStatus 1
+
+-- | Early data rejected.
+pattern S2nEarlyDataStatusRejected :: S2nEarlyDataStatus
+pattern S2nEarlyDataStatusRejected = S2nEarlyDataStatus 2
+
+-- | Early data processing complete.
+pattern S2nEarlyDataStatusEnd :: S2nEarlyDataStatus
+pattern S2nEarlyDataStatusEnd = S2nEarlyDataStatus 3
+
+-- | Type of async private key operation.
+newtype S2nAsyncPkeyOpType = S2nAsyncPkeyOpType CInt
+  deriving (Eq, Ord, Show, Storable)
+
+-- | Decryption operation.
+pattern S2nAsyncDecrypt :: S2nAsyncPkeyOpType
+pattern S2nAsyncDecrypt = S2nAsyncPkeyOpType 0
+
+-- | Signing operation.
+pattern S2nAsyncSign :: S2nAsyncPkeyOpType
+pattern S2nAsyncSign = S2nAsyncPkeyOpType 1
+
+-- | Validation mode for async private key operations.
+newtype S2nAsyncPkeyValidationMode = S2nAsyncPkeyValidationMode CInt
+  deriving (Eq, Ord, Show, Storable)
+
+-- | Fast validation (less strict).
+pattern S2nAsyncPkeyValidationFast :: S2nAsyncPkeyValidationMode
+pattern S2nAsyncPkeyValidationFast = S2nAsyncPkeyValidationMode 0
+
+-- | Strict validation.
+pattern S2nAsyncPkeyValidationStrict :: S2nAsyncPkeyValidationMode
+pattern S2nAsyncPkeyValidationStrict = S2nAsyncPkeyValidationMode 1
+
+-- | Connection serialization format version.
+newtype S2nSerializationVersion = S2nSerializationVersion CInt
+  deriving (Eq, Ord, Show, Storable)
+
+-- | No serialization.
+pattern S2nSerializedConnNone :: S2nSerializationVersion
+pattern S2nSerializedConnNone = S2nSerializationVersion 0
+
+-- | Serialization format version 1.
+pattern S2nSerializedConnV1 :: S2nSerializationVersion
+pattern S2nSerializedConnV1 = S2nSerializationVersion 1
+
+-- | TLS extension type identifier.
+newtype S2nTlsExtensionType = S2nTlsExtensionType CInt
+  deriving (Eq, Ord, Show, Storable)
+
+-- | Server Name Indication (SNI) extension.
+pattern S2nExtensionServerName :: S2nTlsExtensionType
+pattern S2nExtensionServerName = S2nTlsExtensionType 0
+
+-- | Maximum fragment length extension.
+pattern S2nExtensionMaxFragLen :: S2nTlsExtensionType
+pattern S2nExtensionMaxFragLen = S2nTlsExtensionType 1
+
+-- | OCSP stapling extension.
+pattern S2nExtensionOcspStapling :: S2nTlsExtensionType
+pattern S2nExtensionOcspStapling = S2nTlsExtensionType 5
+
+-- | Supported groups (elliptic curves) extension.
+pattern S2nExtensionSupportedGroups :: S2nTlsExtensionType
+pattern S2nExtensionSupportedGroups = S2nTlsExtensionType 10
+
+-- | EC point formats extension.
+pattern S2nExtensionEcPointFormats :: S2nTlsExtensionType
+pattern S2nExtensionEcPointFormats = S2nTlsExtensionType 11
+
+-- | Signature algorithms extension.
+pattern S2nExtensionSignatureAlgorithms :: S2nTlsExtensionType
+pattern S2nExtensionSignatureAlgorithms = S2nTlsExtensionType 13
+
+-- | Application-Layer Protocol Negotiation (ALPN) extension.
+pattern S2nExtensionAlpn :: S2nTlsExtensionType
+pattern S2nExtensionAlpn = S2nTlsExtensionType 16
+
+-- | Certificate Transparency extension.
+pattern S2nExtensionCertificateTransparency :: S2nTlsExtensionType
+pattern S2nExtensionCertificateTransparency = S2nTlsExtensionType 18
+
+-- | Renegotiation info extension.
+pattern S2nExtensionRenegotiationInfo :: S2nTlsExtensionType
+pattern S2nExtensionRenegotiationInfo = S2nTlsExtensionType 65281
+
+-- | Maximum TLS fragment length.
+newtype S2nMaxFragLen = S2nMaxFragLen CInt
+  deriving (Eq, Ord, Show, Storable)
+
+-- | Maximum fragment length of 512 bytes.
+pattern S2nTlsMaxFragLen512 :: S2nMaxFragLen
+pattern S2nTlsMaxFragLen512 = S2nMaxFragLen 1
+
+-- | Maximum fragment length of 1024 bytes.
+pattern S2nTlsMaxFragLen1024 :: S2nMaxFragLen
+pattern S2nTlsMaxFragLen1024 = S2nMaxFragLen 2
+
+-- | Maximum fragment length of 2048 bytes.
+pattern S2nTlsMaxFragLen2048 :: S2nMaxFragLen
+pattern S2nTlsMaxFragLen2048 = S2nMaxFragLen 3
+
+-- | Maximum fragment length of 4096 bytes.
+pattern S2nTlsMaxFragLen4096 :: S2nMaxFragLen
+pattern S2nTlsMaxFragLen4096 = S2nMaxFragLen 4
+
+-- | FIPS mode status.
+newtype S2nFipsMode = S2nFipsMode CInt
+  deriving (Eq, Ord, Show, Storable)
+
+-- | FIPS mode disabled.
+pattern S2nFipsModeDisabled :: S2nFipsMode
+pattern S2nFipsModeDisabled = S2nFipsMode 0
+
+-- | FIPS mode enabled.
+pattern S2nFipsModeEnabled :: S2nFipsMode
+pattern S2nFipsModeEnabled = S2nFipsMode 1
+
+-- | OCSP status request type.
+newtype S2nStatusRequestType = S2nStatusRequestType CInt
+  deriving (Eq, Ord, Show, Storable)
+
+-- | No status request.
+pattern S2nStatusRequestNone :: S2nStatusRequestType
+pattern S2nStatusRequestNone = S2nStatusRequestType 0
+
+-- | Request OCSP status.
+pattern S2nStatusRequestOcsp :: S2nStatusRequestType
+pattern S2nStatusRequestOcsp = S2nStatusRequestType 1
+
+-- | Certificate Transparency support level.
+newtype S2nCtSupportLevel = S2nCtSupportLevel CInt
+  deriving (Eq, Ord, Show, Storable)
+
+-- | No Certificate Transparency support.
+pattern S2nCtSupportNone :: S2nCtSupportLevel
+pattern S2nCtSupportNone = S2nCtSupportLevel 0
+
+-- | Request Certificate Transparency.
+pattern S2nCtSupportRequest :: S2nCtSupportLevel
+pattern S2nCtSupportRequest = S2nCtSupportLevel 1
+
+-- | TLS alert behavior on warnings.
+newtype S2nAlertBehavior = S2nAlertBehavior CInt
+  deriving (Eq, Ord, Show, Storable)
+
+-- | Fail on warning alerts.
+pattern S2nAlertFailOnWarnings :: S2nAlertBehavior
+pattern S2nAlertFailOnWarnings = S2nAlertBehavior 0
+
+-- | Ignore warning alerts.
+pattern S2nAlertIgnoreWarnings :: S2nAlertBehavior
+pattern S2nAlertIgnoreWarnings = S2nAlertBehavior 1
+
+-- | Client hello callback mode.
+newtype S2nClientHelloCbMode = S2nClientHelloCbMode CInt
+  deriving (Eq, Ord, Show, Storable)
+
+-- | Blocking callback mode.
+pattern S2nClientHelloCbBlocking :: S2nClientHelloCbMode
+pattern S2nClientHelloCbBlocking = S2nClientHelloCbMode 0
+
+-- | Non-blocking callback mode.
+pattern S2nClientHelloCbNonblocking :: S2nClientHelloCbMode
+pattern S2nClientHelloCbNonblocking = S2nClientHelloCbMode 1
+
+-- | TLS signature algorithm.
+newtype S2nTlsSignatureAlgorithm = S2nTlsSignatureAlgorithm CInt
+  deriving (Eq, Ord, Show, Storable)
+
+-- | Anonymous (no signature).
+pattern S2nTlsSignatureAnonymous :: S2nTlsSignatureAlgorithm
+pattern S2nTlsSignatureAnonymous = S2nTlsSignatureAlgorithm 0
+
+-- | RSA signature.
+pattern S2nTlsSignatureRsa :: S2nTlsSignatureAlgorithm
+pattern S2nTlsSignatureRsa = S2nTlsSignatureAlgorithm 1
+
+-- | ECDSA signature.
+pattern S2nTlsSignatureEcdsa :: S2nTlsSignatureAlgorithm
+pattern S2nTlsSignatureEcdsa = S2nTlsSignatureAlgorithm 3
+
+-- | RSA-PSS with RSAE key.
+pattern S2nTlsSignatureRsaPssRsae :: S2nTlsSignatureAlgorithm
+pattern S2nTlsSignatureRsaPssRsae = S2nTlsSignatureAlgorithm 4
+
+-- | RSA-PSS with PSS key.
+pattern S2nTlsSignatureRsaPssPss :: S2nTlsSignatureAlgorithm
+pattern S2nTlsSignatureRsaPssPss = S2nTlsSignatureAlgorithm 5
+
+-- | TLS hash algorithm.
+newtype S2nTlsHashAlgorithm = S2nTlsHashAlgorithm CInt
+  deriving (Eq, Ord, Show, Storable)
+
+-- | No hash algorithm.
+pattern S2nTlsHashNone :: S2nTlsHashAlgorithm
+pattern S2nTlsHashNone = S2nTlsHashAlgorithm 0
+
+-- | MD5 hash (deprecated).
+pattern S2nTlsHashMd5 :: S2nTlsHashAlgorithm
+pattern S2nTlsHashMd5 = S2nTlsHashAlgorithm 1
+
+-- | SHA-1 hash (deprecated).
+pattern S2nTlsHashSha1 :: S2nTlsHashAlgorithm
+pattern S2nTlsHashSha1 = S2nTlsHashAlgorithm 2
+
+-- | SHA-224 hash.
+pattern S2nTlsHashSha224 :: S2nTlsHashAlgorithm
+pattern S2nTlsHashSha224 = S2nTlsHashAlgorithm 3
+
+-- | SHA-256 hash.
+pattern S2nTlsHashSha256 :: S2nTlsHashAlgorithm
+pattern S2nTlsHashSha256 = S2nTlsHashAlgorithm 4
+
+-- | SHA-384 hash.
+pattern S2nTlsHashSha384 :: S2nTlsHashAlgorithm
+pattern S2nTlsHashSha384 = S2nTlsHashAlgorithm 5
+
+-- | SHA-512 hash.
+pattern S2nTlsHashSha512 :: S2nTlsHashAlgorithm
+pattern S2nTlsHashSha512 = S2nTlsHashAlgorithm 6
+
+-- | Certificate SNI match result.
+newtype S2nCertSniMatch = S2nCertSniMatch CInt
+  deriving (Eq, Ord, Show, Storable)
+
+-- | SNI matching not applicable.
+pattern S2nCertSniMatchNotApplicable :: S2nCertSniMatch
+pattern S2nCertSniMatchNotApplicable = S2nCertSniMatch 0
+
+-- | Certificate matches SNI.
+pattern S2nCertSniMatched :: S2nCertSniMatch
+pattern S2nCertSniMatched = S2nCertSniMatch 1
+
+-- | No matching certificate found.
+pattern S2nCertSniNoMatchFound :: S2nCertSniMatch
+pattern S2nCertSniNoMatchFound = S2nCertSniMatch 2
+
+-- | Peer key update request type.
+newtype S2nPeerKeyUpdate = S2nPeerKeyUpdate CInt
+  deriving (Eq, Ord, Show, Storable)
+
+-- | Key update not requested.
+pattern S2nKeyUpdateNotRequested :: S2nPeerKeyUpdate
+pattern S2nKeyUpdateNotRequested = S2nPeerKeyUpdate 0
+
+-- | Key update requested.
+pattern S2nKeyUpdateRequested :: S2nPeerKeyUpdate
+pattern S2nKeyUpdateRequested = S2nPeerKeyUpdate 1
+
+-- | Verify-after-sign mode for signatures.
+newtype S2nVerifyAfterSign = S2nVerifyAfterSign CInt
+  deriving (Eq, Ord, Show, Storable)
+
+-- | Verification after signing disabled.
+pattern S2nVerifyAfterSignDisabled :: S2nVerifyAfterSign
+pattern S2nVerifyAfterSignDisabled = S2nVerifyAfterSign 0
+
+-- | Verification after signing enabled.
+pattern S2nVerifyAfterSignEnabled :: S2nVerifyAfterSign
+pattern S2nVerifyAfterSignEnabled = S2nVerifyAfterSign 1
+
+--------------------------------------------------------------------------------
+-- Callback Types
+--------------------------------------------------------------------------------
+
+-- | Clock callback: returns current time in nanoseconds.
+type S2nClockTimeNanoseconds = FunPtr (Ptr () -> Ptr Word64 -> IO CInt)
+
+-- | Receive callback for custom I/O.
+type S2nRecvFn = FunPtr (Ptr () -> Ptr Word8 -> Word32 -> IO CInt)
+
+-- | Send callback for custom I/O.
+type S2nSendFn = FunPtr (Ptr () -> Ptr Word8 -> Word32 -> IO CInt)
+
+-- | Cache store callback.
+type S2nCacheStoreCallback = FunPtr (Ptr S2nConnection -> Ptr () -> Word64 -> Ptr Word8 -> Word64 -> Ptr Word8 -> Word64 -> IO CInt)
+
+-- | Cache retrieve callback.
+type S2nCacheRetrieveCallback = FunPtr (Ptr S2nConnection -> Ptr () -> Ptr Word8 -> Word64 -> Ptr Word8 -> Ptr Word64 -> IO CInt)
+
+-- | Cache delete callback.
+type S2nCacheDeleteCallback = FunPtr (Ptr S2nConnection -> Ptr () -> Ptr Word8 -> Word64 -> IO CInt)
+
+-- | Memory initialization callback.
+type S2nMemInitCallback = FunPtr (IO CInt)
+
+-- | Memory cleanup callback.
+type S2nMemCleanupCallback = FunPtr (IO CInt)
+
+-- | Memory allocation callback.
+type S2nMemMallocCallback = FunPtr (Ptr (Ptr ()) -> Word32 -> Word32 -> IO CInt)
+
+-- | Memory free callback.
+type S2nMemFreeCallback = FunPtr (Ptr () -> Word32 -> IO CInt)
+
+-- | Random initialization callback.
+type S2nRandInitCallback = FunPtr (IO CInt)
+
+-- | Random cleanup callback.
+type S2nRandCleanupCallback = FunPtr (IO CInt)
+
+-- | Random seed callback.
+type S2nRandSeedCallback = FunPtr (Ptr () -> Word32 -> IO CInt)
+
+-- | Random mix callback.
+type S2nRandMixCallback = FunPtr (Ptr () -> Word32 -> IO CInt)
+
+-- | Client hello callback.
+type S2nClientHelloFn = FunPtr (Ptr S2nConnection -> Ptr () -> IO CInt)
+
+-- | Certificate tiebreak callback.
+type S2nCertTiebreakCallback = FunPtr (Ptr S2nCertChainAndKey -> Ptr S2nCertChainAndKey -> Word8 -> Ptr (Ptr S2nCertChainAndKey) -> IO CInt)
+
+-- | Verify host callback.
+type S2nVerifyHostFn = FunPtr (CString -> CSize -> Ptr () -> IO Word8)
+
+-- | PSK selection callback.
+type S2nPskSelectionCallback = FunPtr (Ptr S2nConnection -> Ptr () -> Ptr S2nOfferedPskList -> IO CInt)
+
+-- | Async private key callback.
+type S2nAsyncPkeyFn = FunPtr (Ptr S2nConnection -> Ptr S2nAsyncPkeyOp -> IO CInt)
+
+-- | Session ticket callback.
+type S2nSessionTicketFn = FunPtr (Ptr S2nConnection -> Ptr () -> Ptr S2nSessionTicket -> IO CInt)
+
+-- | Key log callback (for debugging).
+type S2nKeyLogFn = FunPtr (Ptr () -> Ptr S2nConnection -> Ptr Word8 -> CSize -> IO CInt)
+
+-- | Early data callback.
+type S2nEarlyDataCb = FunPtr (Ptr S2nConnection -> Ptr S2nOfferedEarlyData -> IO CInt)
+
+--------------------------------------------------------------------------------
+-- S2nTlsFfi Record
+--------------------------------------------------------------------------------
+
+{- | A record containing all FFI bindings to the s2n-tls library.
+
+This record is populated by 'S2nTls.Ffi.withS2nTlsFfi' with the 'S2nTls.Ffi.Library'
+parameter specifying either 'S2nTls.Ffi.Linked' or 'S2nTls.Ffi.Dynamic' loading.
+-}
+data S2nTlsFfi = S2nTlsFfi
+  { missingSymbols :: [String]
+  -- ^ List of symbol names that couldn't be loaded.
+  -- Calling functions for these symbols will throw 'MissingSymbol'.
+  , s2n_init :: IO (Either S2nError CInt)
+  -- ^ __Initialization & Cleanup__
+  , s2n_cleanup :: IO (Either S2nError CInt)
+  , s2n_cleanup_final :: IO (Either S2nError CInt)
+  , s2n_crypto_disable_init :: IO (Either S2nError CInt)
+  , s2n_disable_atexit :: IO (Either S2nError CInt)
+  , s2n_get_openssl_version :: IO CLong
+  , s2n_get_fips_mode :: Ptr S2nFipsMode -> IO (Either S2nError CInt)
+  , s2n_errno_location :: IO (Ptr CInt)
+  -- ^ __Error Handling__
+  , s2n_error_get_type :: CInt -> IO S2nErrorType
+  , s2n_strerror :: CInt -> CString -> IO CString
+  , s2n_strerror_debug :: CInt -> CString -> IO CString
+  , s2n_strerror_name :: CInt -> IO CString
+  , s2n_strerror_source :: CInt -> IO CString
+  , s2n_stack_traces_enabled :: IO CBool
+  -- ^ __Stack Traces__
+  , s2n_stack_traces_enabled_set :: CInt -> IO (Either S2nError CInt)
+  , s2n_calculate_stacktrace :: IO (Either S2nError CInt)
+  , s2n_free_stacktrace :: IO (Either S2nError CInt)
+  , s2n_get_stacktrace :: Ptr S2nStacktrace -> IO (Either S2nError CInt)
+  , s2n_config_new :: IO (Either S2nError (Ptr S2nConfig))
+  -- ^ __Config Management__
+  , s2n_config_new_minimal :: IO (Either S2nError (Ptr S2nConfig))
+  , s2n_config_free :: Ptr S2nConfig -> IO (Either S2nError CInt)
+  , s2n_config_free_dhparams :: Ptr S2nConfig -> IO (Either S2nError CInt)
+  , s2n_config_free_cert_chain_and_key :: Ptr S2nConfig -> IO (Either S2nError CInt)
+  , s2n_config_set_wall_clock :: Ptr S2nConfig -> S2nClockTimeNanoseconds -> Ptr () -> IO (Either S2nError CInt)
+  , s2n_config_set_monotonic_clock :: Ptr S2nConfig -> S2nClockTimeNanoseconds -> Ptr () -> IO (Either S2nError CInt)
+  , s2n_config_set_cache_store_callback :: Ptr S2nConfig -> S2nCacheStoreCallback -> Ptr () -> IO (Either S2nError CInt)
+  -- ^ __Cache Callbacks__
+  , s2n_config_set_cache_retrieve_callback :: Ptr S2nConfig -> S2nCacheRetrieveCallback -> Ptr () -> IO (Either S2nError CInt)
+  , s2n_config_set_cache_delete_callback :: Ptr S2nConfig -> S2nCacheDeleteCallback -> Ptr () -> IO (Either S2nError CInt)
+  , s2n_mem_set_callbacks :: S2nMemInitCallback -> S2nMemCleanupCallback -> S2nMemMallocCallback -> S2nMemFreeCallback -> IO (Either S2nError CInt)
+  -- ^ __Memory & Random Callbacks__
+  , s2n_rand_set_callbacks :: S2nRandInitCallback -> S2nRandCleanupCallback -> S2nRandSeedCallback -> S2nRandMixCallback -> IO (Either S2nError CInt)
+  , s2n_cert_chain_and_key_new :: IO (Either S2nError (Ptr S2nCertChainAndKey))
+  -- ^ __Certificate Chain Management__
+  , s2n_cert_chain_and_key_load_pem :: Ptr S2nCertChainAndKey -> CString -> CString -> IO (Either S2nError CInt)
+  , s2n_cert_chain_and_key_load_pem_bytes :: Ptr S2nCertChainAndKey -> Ptr Word8 -> Word32 -> Ptr Word8 -> Word32 -> IO (Either S2nError CInt)
+  , s2n_cert_chain_and_key_load_public_pem_bytes :: Ptr S2nCertChainAndKey -> Ptr Word8 -> Word32 -> IO (Either S2nError CInt)
+  , s2n_cert_chain_and_key_free :: Ptr S2nCertChainAndKey -> IO (Either S2nError CInt)
+  , s2n_cert_chain_and_key_set_ctx :: Ptr S2nCertChainAndKey -> Ptr () -> IO (Either S2nError CInt)
+  , s2n_cert_chain_and_key_get_ctx :: Ptr S2nCertChainAndKey -> IO (Ptr ())
+  , s2n_cert_chain_and_key_get_private_key :: Ptr S2nCertChainAndKey -> IO (Either S2nError (Ptr S2nCertPrivateKey))
+  , s2n_cert_chain_and_key_set_ocsp_data :: Ptr S2nCertChainAndKey -> Ptr Word8 -> Word32 -> IO (Either S2nError CInt)
+  , s2n_cert_chain_and_key_set_sct_list :: Ptr S2nCertChainAndKey -> Ptr Word8 -> Word32 -> IO (Either S2nError CInt)
+  , s2n_config_set_cert_tiebreak_callback :: Ptr S2nConfig -> S2nCertTiebreakCallback -> IO (Either S2nError CInt)
+  , s2n_config_add_cert_chain_and_key :: Ptr S2nConfig -> CString -> CString -> IO (Either S2nError CInt)
+  , s2n_config_add_cert_chain_and_key_to_store :: Ptr S2nConfig -> Ptr S2nCertChainAndKey -> IO (Either S2nError CInt)
+  , s2n_config_set_cert_chain_and_key_defaults :: Ptr S2nConfig -> Ptr (Ptr S2nCertChainAndKey) -> Word32 -> IO (Either S2nError CInt)
+  , s2n_config_set_verification_ca_location :: Ptr S2nConfig -> CString -> CString -> IO (Either S2nError CInt)
+  -- ^ __Trust Store__
+  , s2n_config_add_pem_to_trust_store :: Ptr S2nConfig -> CString -> IO (Either S2nError CInt)
+  , s2n_config_wipe_trust_store :: Ptr S2nConfig -> IO (Either S2nError CInt)
+  , s2n_config_load_system_certs :: Ptr S2nConfig -> IO (Either S2nError CInt)
+  , s2n_config_set_cert_authorities_from_trust_store :: Ptr S2nConfig -> IO (Either S2nError CInt)
+  , s2n_config_set_verify_after_sign :: Ptr S2nConfig -> S2nVerifyAfterSign -> IO (Either S2nError CInt)
+  -- ^ __Verification & Validation__
+  , s2n_config_set_check_stapled_ocsp_response :: Ptr S2nConfig -> CInt -> IO (Either S2nError CInt)
+  , s2n_config_disable_x509_time_verification :: Ptr S2nConfig -> IO (Either S2nError CInt)
+  , -- , s2n_config_disable_x509_intent_verification :: Ptr S2nConfig -> IO (Either S2nError CInt)
+    s2n_config_disable_x509_verification :: Ptr S2nConfig -> IO (Either S2nError CInt)
+  , s2n_config_set_max_cert_chain_depth :: Ptr S2nConfig -> Word16 -> IO (Either S2nError CInt)
+  , s2n_config_set_verify_host_callback :: Ptr S2nConfig -> S2nVerifyHostFn -> Ptr () -> IO (Either S2nError CInt)
+  , s2n_config_add_dhparams :: Ptr S2nConfig -> CString -> IO (Either S2nError CInt)
+  -- ^ __DH Parameters__
+  , s2n_config_set_cipher_preferences :: Ptr S2nConfig -> CString -> IO (Either S2nError CInt)
+  -- ^ __Security Policies & Preferences__
+  , s2n_config_append_protocol_preference :: Ptr S2nConfig -> Ptr Word8 -> Word8 -> IO (Either S2nError CInt)
+  , s2n_config_set_protocol_preferences :: Ptr S2nConfig -> Ptr CString -> CInt -> IO (Either S2nError CInt)
+  , s2n_config_set_status_request_type :: Ptr S2nConfig -> S2nStatusRequestType -> IO (Either S2nError CInt)
+  , s2n_config_set_ct_support_level :: Ptr S2nConfig -> S2nCtSupportLevel -> IO (Either S2nError CInt)
+  , s2n_config_set_alert_behavior :: Ptr S2nConfig -> S2nAlertBehavior -> IO (Either S2nError CInt)
+  , s2n_config_set_extension_data :: Ptr S2nConfig -> S2nTlsExtensionType -> Ptr Word8 -> Word32 -> IO (Either S2nError CInt)
+  -- ^ __Extension Data__
+  , s2n_config_send_max_fragment_length :: Ptr S2nConfig -> S2nMaxFragLen -> IO (Either S2nError CInt)
+  , s2n_config_accept_max_fragment_length :: Ptr S2nConfig -> IO (Either S2nError CInt)
+  , s2n_config_set_session_state_lifetime :: Ptr S2nConfig -> Word64 -> IO (Either S2nError CInt)
+  -- ^ __Session & Ticket Configuration__
+  , s2n_config_set_session_tickets_onoff :: Ptr S2nConfig -> Word8 -> IO (Either S2nError CInt)
+  , s2n_config_set_session_cache_onoff :: Ptr S2nConfig -> Word8 -> IO (Either S2nError CInt)
+  , s2n_config_set_ticket_encrypt_decrypt_key_lifetime :: Ptr S2nConfig -> Word64 -> IO (Either S2nError CInt)
+  , s2n_config_set_ticket_decrypt_key_lifetime :: Ptr S2nConfig -> Word64 -> IO (Either S2nError CInt)
+  , s2n_config_add_ticket_crypto_key :: Ptr S2nConfig -> Ptr Word8 -> Word32 -> Ptr Word8 -> Word32 -> Word64 -> IO (Either S2nError CInt)
+  , s2n_config_require_ticket_forward_secrecy :: Ptr S2nConfig -> CInt -> IO (Either S2nError CInt)
+  , s2n_config_set_send_buffer_size :: Ptr S2nConfig -> Word32 -> IO (Either S2nError CInt)
+  -- ^ __Buffer & I\/O Configuration__
+  , s2n_config_set_recv_multi_record :: Ptr S2nConfig -> CInt -> IO (Either S2nError CInt)
+  , s2n_config_set_ctx :: Ptr S2nConfig -> Ptr () -> IO (Either S2nError CInt)
+  -- ^ __Miscellaneous Config__
+  , s2n_config_get_ctx :: Ptr S2nConfig -> Ptr (Ptr ()) -> IO (Either S2nError CInt)
+  , s2n_config_set_client_hello_cb :: Ptr S2nConfig -> S2nClientHelloFn -> Ptr () -> IO (Either S2nError CInt)
+  , s2n_config_set_client_hello_cb_mode :: Ptr S2nConfig -> S2nClientHelloCbMode -> IO (Either S2nError CInt)
+  , s2n_config_set_max_blinding_delay :: Ptr S2nConfig -> Word32 -> IO (Either S2nError CInt)
+  , s2n_config_get_client_auth_type :: Ptr S2nConfig -> Ptr S2nCertAuthType -> IO (Either S2nError CInt)
+  , s2n_config_set_client_auth_type :: Ptr S2nConfig -> S2nCertAuthType -> IO (Either S2nError CInt)
+  , s2n_config_set_initial_ticket_count :: Ptr S2nConfig -> Word8 -> IO (Either S2nError CInt)
+  , s2n_config_set_psk_mode :: Ptr S2nConfig -> S2nPskMode -> IO (Either S2nError CInt)
+  , s2n_config_set_psk_selection_callback :: Ptr S2nConfig -> S2nPskSelectionCallback -> Ptr () -> IO (Either S2nError CInt)
+  , s2n_config_set_async_pkey_callback :: Ptr S2nConfig -> S2nAsyncPkeyFn -> IO (Either S2nError CInt)
+  , s2n_config_set_async_pkey_validation_mode :: Ptr S2nConfig -> S2nAsyncPkeyValidationMode -> IO (Either S2nError CInt)
+  , s2n_config_set_session_ticket_cb :: Ptr S2nConfig -> S2nSessionTicketFn -> Ptr () -> IO (Either S2nError CInt)
+  , s2n_config_set_key_log_cb :: Ptr S2nConfig -> S2nKeyLogFn -> Ptr () -> IO (Either S2nError CInt)
+  , s2n_config_enable_cert_req_dss_legacy_compat :: Ptr S2nConfig -> IO (Either S2nError CInt)
+  , s2n_config_set_server_max_early_data_size :: Ptr S2nConfig -> Word32 -> IO (Either S2nError CInt)
+  , s2n_config_set_early_data_cb :: Ptr S2nConfig -> S2nEarlyDataCb -> IO (Either S2nError CInt)
+  , s2n_config_get_supported_groups :: Ptr S2nConfig -> Ptr Word16 -> Word16 -> Ptr Word16 -> IO (Either S2nError CInt)
+  , s2n_config_set_serialization_version :: Ptr S2nConfig -> S2nSerializationVersion -> IO (Either S2nError CInt)
+  , s2n_connection_new :: S2nMode -> IO (Either S2nError (Ptr S2nConnection))
+  -- ^ __Connection Creation & Management__
+  , s2n_connection_set_config :: Ptr S2nConnection -> Ptr S2nConfig -> IO (Either S2nError CInt)
+  , s2n_connection_set_ctx :: Ptr S2nConnection -> Ptr () -> IO (Either S2nError CInt)
+  , s2n_connection_get_ctx :: Ptr S2nConnection -> IO (Either S2nError (Ptr ()))
+  , s2n_client_hello_cb_done :: Ptr S2nConnection -> IO (Either S2nError CInt)
+  , s2n_connection_server_name_extension_used :: Ptr S2nConnection -> IO (Either S2nError CInt)
+  , s2n_connection_get_client_hello :: Ptr S2nConnection -> IO (Either S2nError (Ptr S2nClientHello))
+  -- ^ __Client Hello Access__
+  , s2n_client_hello_parse_message :: Ptr Word8 -> Word32 -> IO (Either S2nError (Ptr S2nClientHello))
+  , s2n_client_hello_free :: Ptr (Ptr S2nClientHello) -> IO (Either S2nError CInt)
+  , s2n_client_hello_get_raw_message_length :: Ptr S2nClientHello -> IO (Either S2nError CSsize)
+  , s2n_client_hello_get_raw_message :: Ptr S2nClientHello -> Ptr Word8 -> Word32 -> IO (Either S2nError CSsize)
+  , s2n_client_hello_get_cipher_suites_length :: Ptr S2nClientHello -> IO (Either S2nError CSsize)
+  , s2n_client_hello_get_cipher_suites :: Ptr S2nClientHello -> Ptr Word8 -> Word32 -> IO (Either S2nError CSsize)
+  , s2n_client_hello_get_extensions_length :: Ptr S2nClientHello -> IO (Either S2nError CSsize)
+  , s2n_client_hello_get_extensions :: Ptr S2nClientHello -> Ptr Word8 -> Word32 -> IO (Either S2nError CSsize)
+  , s2n_client_hello_get_extension_length :: Ptr S2nClientHello -> S2nTlsExtensionType -> IO (Either S2nError CSsize)
+  , s2n_client_hello_get_extension_by_id :: Ptr S2nClientHello -> S2nTlsExtensionType -> Ptr Word8 -> Word32 -> IO (Either S2nError CSsize)
+  , s2n_client_hello_has_extension :: Ptr S2nClientHello -> Word16 -> Ptr CInt -> IO (Either S2nError CInt)
+  , s2n_client_hello_get_session_id_length :: Ptr S2nClientHello -> Ptr Word32 -> IO (Either S2nError CInt)
+  , s2n_client_hello_get_session_id :: Ptr S2nClientHello -> Ptr Word8 -> Ptr Word32 -> Word32 -> IO (Either S2nError CInt)
+  , s2n_client_hello_get_compression_methods_length :: Ptr S2nClientHello -> Ptr Word32 -> IO (Either S2nError CInt)
+  , s2n_client_hello_get_compression_methods :: Ptr S2nClientHello -> Ptr Word8 -> Word32 -> Ptr Word32 -> IO (Either S2nError CInt)
+  , s2n_client_hello_get_legacy_protocol_version :: Ptr S2nClientHello -> Ptr Word8 -> IO (Either S2nError CInt)
+  , -- , s2n_client_hello_get_random :: Ptr S2nClientHello -> Ptr Word8 -> Word32 -> IO (Either S2nError CInt)
+    s2n_client_hello_get_supported_groups :: Ptr S2nClientHello -> Ptr Word16 -> Word16 -> Ptr Word16 -> IO (Either S2nError CInt)
+  , s2n_client_hello_get_server_name_length :: Ptr S2nClientHello -> Ptr Word16 -> IO (Either S2nError CInt)
+  , s2n_client_hello_get_server_name :: Ptr S2nClientHello -> Ptr Word8 -> Word16 -> Ptr Word16 -> IO (Either S2nError CInt)
+  , s2n_client_hello_get_legacy_record_version :: Ptr S2nClientHello -> Ptr Word8 -> IO (Either S2nError CInt)
+  , s2n_connection_set_fd :: Ptr S2nConnection -> CInt -> IO (Either S2nError CInt)
+  -- ^ __File Descriptor & I\/O__
+  , s2n_connection_set_read_fd :: Ptr S2nConnection -> CInt -> IO (Either S2nError CInt)
+  , s2n_connection_set_write_fd :: Ptr S2nConnection -> CInt -> IO (Either S2nError CInt)
+  , s2n_connection_get_read_fd :: Ptr S2nConnection -> Ptr CInt -> IO (Either S2nError CInt)
+  , s2n_connection_get_write_fd :: Ptr S2nConnection -> Ptr CInt -> IO (Either S2nError CInt)
+  , s2n_connection_use_corked_io :: Ptr S2nConnection -> IO (Either S2nError CInt)
+  , s2n_connection_set_recv_ctx :: Ptr S2nConnection -> Ptr () -> IO (Either S2nError CInt)
+  , s2n_connection_set_send_ctx :: Ptr S2nConnection -> Ptr () -> IO (Either S2nError CInt)
+  , s2n_connection_set_recv_cb :: Ptr S2nConnection -> S2nRecvFn -> IO (Either S2nError CInt)
+  , s2n_connection_set_send_cb :: Ptr S2nConnection -> S2nSendFn -> IO (Either S2nError CInt)
+  , s2n_connection_prefer_throughput :: Ptr S2nConnection -> IO (Either S2nError CInt)
+  -- ^ __Connection Preferences__
+  , s2n_connection_prefer_low_latency :: Ptr S2nConnection -> IO (Either S2nError CInt)
+  , s2n_connection_set_recv_buffering :: Ptr S2nConnection -> CInt -> IO (Either S2nError CInt)
+  , s2n_peek_buffered :: Ptr S2nConnection -> IO Word32
+  , s2n_connection_set_dynamic_buffers :: Ptr S2nConnection -> CInt -> IO (Either S2nError CInt)
+  , s2n_connection_set_dynamic_record_threshold :: Ptr S2nConnection -> Word32 -> Word16 -> IO (Either S2nError CInt)
+  , s2n_connection_set_verify_host_callback :: Ptr S2nConnection -> S2nVerifyHostFn -> Ptr () -> IO (Either S2nError CInt)
+  -- ^ __Host Verification__
+  , s2n_connection_set_blinding :: Ptr S2nConnection -> S2nBlinding -> IO (Either S2nError CInt)
+  -- ^ __Blinding & Security__
+  , s2n_connection_get_delay :: Ptr S2nConnection -> IO Word64
+  , s2n_connection_set_cipher_preferences :: Ptr S2nConnection -> CString -> IO (Either S2nError CInt)
+  -- ^ __Cipher & Protocol Configuration__
+  , s2n_connection_request_key_update :: Ptr S2nConnection -> S2nPeerKeyUpdate -> IO (Either S2nError CInt)
+  , s2n_connection_append_protocol_preference :: Ptr S2nConnection -> Ptr Word8 -> Word8 -> IO (Either S2nError CInt)
+  , s2n_connection_set_protocol_preferences :: Ptr S2nConnection -> Ptr CString -> CInt -> IO (Either S2nError CInt)
+  , s2n_set_server_name :: Ptr S2nConnection -> CString -> IO (Either S2nError CInt)
+  -- ^ __Server Name (SNI)__
+  , s2n_get_server_name :: Ptr S2nConnection -> IO (Either S2nError CString)
+  , s2n_get_application_protocol :: Ptr S2nConnection -> IO (Either S2nError CString)
+  -- ^ __Application Protocol (ALPN)__
+  , s2n_connection_get_ocsp_response :: Ptr S2nConnection -> Ptr Word32 -> IO (Either S2nError (Ptr Word8))
+  -- ^ __OCSP & Certificate Transparency__
+  , s2n_connection_get_sct_list :: Ptr S2nConnection -> Ptr Word32 -> IO (Either S2nError (Ptr Word8))
+  , s2n_negotiate :: Ptr S2nConnection -> Ptr S2nBlockedStatus -> IO (Either S2nError CInt)
+  -- ^ __Handshake & TLS Operations__
+  , s2n_send :: Ptr S2nConnection -> Ptr () -> CSsize -> Ptr S2nBlockedStatus -> IO (Either S2nError CSsize)
+  , s2n_recv :: Ptr S2nConnection -> Ptr () -> CSsize -> Ptr S2nBlockedStatus -> IO (Either S2nError CSsize)
+  , s2n_peek :: Ptr S2nConnection -> IO Word32
+  , s2n_connection_free_handshake :: Ptr S2nConnection -> IO (Either S2nError CInt)
+  , s2n_connection_release_buffers :: Ptr S2nConnection -> IO (Either S2nError CInt)
+  , s2n_connection_wipe :: Ptr S2nConnection -> IO (Either S2nError CInt)
+  , s2n_connection_free :: Ptr S2nConnection -> IO (Either S2nError CInt)
+  , s2n_shutdown :: Ptr S2nConnection -> Ptr S2nBlockedStatus -> IO (Either S2nError CInt)
+  , s2n_shutdown_send :: Ptr S2nConnection -> Ptr S2nBlockedStatus -> IO (Either S2nError CInt)
+  , s2n_connection_get_client_auth_type :: Ptr S2nConnection -> Ptr S2nCertAuthType -> IO (Either S2nError CInt)
+  -- ^ __Client Authentication__
+  , s2n_connection_set_client_auth_type :: Ptr S2nConnection -> S2nCertAuthType -> IO (Either S2nError CInt)
+  , s2n_connection_get_client_cert_chain :: Ptr S2nConnection -> Ptr (Ptr Word8) -> Ptr Word32 -> IO (Either S2nError CInt)
+  , s2n_connection_client_cert_used :: Ptr S2nConnection -> IO (Either S2nError CInt)
+  , s2n_connection_add_new_tickets_to_send :: Ptr S2nConnection -> Word8 -> IO (Either S2nError CInt)
+  -- ^ __Session Management__
+  , s2n_connection_get_tickets_sent :: Ptr S2nConnection -> Ptr Word16 -> IO (Either S2nError CInt)
+  , s2n_connection_set_server_keying_material_lifetime :: Ptr S2nConnection -> Word32 -> IO (Either S2nError CInt)
+  , s2n_session_ticket_get_data_len :: Ptr S2nSessionTicket -> Ptr CSize -> IO (Either S2nError CInt)
+  , s2n_session_ticket_get_data :: Ptr S2nSessionTicket -> CSize -> Ptr Word8 -> IO (Either S2nError CInt)
+  , s2n_session_ticket_get_lifetime :: Ptr S2nSessionTicket -> Ptr Word32 -> IO (Either S2nError CInt)
+  , s2n_connection_set_session :: Ptr S2nConnection -> Ptr Word8 -> CSize -> IO (Either S2nError CInt)
+  , s2n_connection_get_session :: Ptr S2nConnection -> Ptr Word8 -> CSize -> IO (Either S2nError CInt)
+  , s2n_connection_get_session_ticket_lifetime_hint :: Ptr S2nConnection -> IO (Either S2nError CInt)
+  , s2n_connection_get_session_length :: Ptr S2nConnection -> IO (Either S2nError CInt)
+  , s2n_connection_get_session_id_length :: Ptr S2nConnection -> IO (Either S2nError CInt)
+  , s2n_connection_get_session_id :: Ptr S2nConnection -> Ptr Word8 -> CSize -> IO (Either S2nError CInt)
+  , s2n_connection_is_session_resumed :: Ptr S2nConnection -> IO (Either S2nError CInt)
+  , s2n_connection_is_ocsp_stapled :: Ptr S2nConnection -> IO (Either S2nError CInt)
+  -- ^ __Certificate Information__
+  , s2n_connection_get_selected_signature_algorithm :: Ptr S2nConnection -> Ptr S2nTlsSignatureAlgorithm -> IO (Either S2nError CInt)
+  , s2n_connection_get_selected_digest_algorithm :: Ptr S2nConnection -> Ptr S2nTlsHashAlgorithm -> IO (Either S2nError CInt)
+  , s2n_connection_get_selected_client_cert_signature_algorithm :: Ptr S2nConnection -> Ptr S2nTlsSignatureAlgorithm -> IO (Either S2nError CInt)
+  , s2n_connection_get_selected_client_cert_digest_algorithm :: Ptr S2nConnection -> Ptr S2nTlsHashAlgorithm -> IO (Either S2nError CInt)
+  , -- , s2n_connection_get_signature_scheme :: Ptr S2nConnection -> Ptr CString -> IO (Either S2nError CInt)
+    s2n_connection_get_selected_cert :: Ptr S2nConnection -> IO (Either S2nError (Ptr S2nCertChainAndKey))
+  , s2n_cert_chain_get_length :: Ptr S2nCertChainAndKey -> Ptr Word32 -> IO (Either S2nError CInt)
+  , s2n_cert_chain_get_cert :: Ptr S2nCertChainAndKey -> Ptr (Ptr S2nCert) -> Word32 -> IO (Either S2nError CInt)
+  , s2n_cert_get_der :: Ptr S2nCert -> Ptr (Ptr Word8) -> Ptr Word32 -> IO (Either S2nError CInt)
+  , s2n_connection_get_peer_cert_chain :: Ptr S2nConnection -> Ptr S2nCertChainAndKey -> IO (Either S2nError CInt)
+  , s2n_cert_get_x509_extension_value_length :: Ptr S2nCert -> Ptr Word8 -> Ptr Word32 -> IO (Either S2nError CInt)
+  , s2n_cert_get_x509_extension_value :: Ptr S2nCert -> Ptr Word8 -> Ptr Word8 -> Ptr Word32 -> Ptr CInt -> IO (Either S2nError CInt)
+  , s2n_cert_get_utf8_string_from_extension_data_length :: Ptr Word8 -> Word32 -> Ptr Word32 -> IO (Either S2nError CInt)
+  , s2n_cert_get_utf8_string_from_extension_data :: Ptr Word8 -> Word32 -> Ptr Word8 -> Ptr Word32 -> IO (Either S2nError CInt)
+  , s2n_external_psk_new :: IO (Either S2nError (Ptr S2nPsk))
+  -- ^ __Pre-Shared Keys (PSK)__
+  , s2n_psk_free :: Ptr (Ptr S2nPsk) -> IO (Either S2nError CInt)
+  , s2n_psk_set_identity :: Ptr S2nPsk -> Ptr Word8 -> Word16 -> IO (Either S2nError CInt)
+  , s2n_psk_set_secret :: Ptr S2nPsk -> Ptr Word8 -> Word16 -> IO (Either S2nError CInt)
+  , s2n_psk_set_hmac :: Ptr S2nPsk -> S2nPskHmac -> IO (Either S2nError CInt)
+  , s2n_connection_append_psk :: Ptr S2nConnection -> Ptr S2nPsk -> IO (Either S2nError CInt)
+  , s2n_connection_set_psk_mode :: Ptr S2nConnection -> S2nPskMode -> IO (Either S2nError CInt)
+  , s2n_connection_get_negotiated_psk_identity_length :: Ptr S2nConnection -> Ptr Word16 -> IO (Either S2nError CInt)
+  , s2n_connection_get_negotiated_psk_identity :: Ptr S2nConnection -> Ptr Word8 -> Word16 -> IO (Either S2nError CInt)
+  , s2n_offered_psk_new :: IO (Either S2nError (Ptr S2nOfferedPsk))
+  , s2n_offered_psk_free :: Ptr (Ptr S2nOfferedPsk) -> IO (Either S2nError CInt)
+  , s2n_offered_psk_get_identity :: Ptr S2nOfferedPsk -> Ptr (Ptr Word8) -> Ptr Word16 -> IO (Either S2nError CInt)
+  , s2n_offered_psk_list_has_next :: Ptr S2nOfferedPskList -> IO CBool
+  , s2n_offered_psk_list_next :: Ptr S2nOfferedPskList -> Ptr S2nOfferedPsk -> IO (Either S2nError CInt)
+  , s2n_offered_psk_list_reread :: Ptr S2nOfferedPskList -> IO (Either S2nError CInt)
+  , s2n_offered_psk_list_choose_psk :: Ptr S2nOfferedPskList -> Ptr S2nOfferedPsk -> IO (Either S2nError CInt)
+  , s2n_psk_configure_early_data :: Ptr S2nPsk -> Word32 -> Word8 -> Word8 -> IO (Either S2nError CInt)
+  , s2n_psk_set_application_protocol :: Ptr S2nPsk -> Ptr Word8 -> Word8 -> IO (Either S2nError CInt)
+  , s2n_psk_set_early_data_context :: Ptr S2nPsk -> Ptr Word8 -> Word16 -> IO (Either S2nError CInt)
+  , s2n_connection_get_wire_bytes_in :: Ptr S2nConnection -> IO Word64
+  -- ^ __Connection Statistics__
+  , s2n_connection_get_wire_bytes_out :: Ptr S2nConnection -> IO Word64
+  , s2n_connection_get_client_protocol_version :: Ptr S2nConnection -> IO (Either S2nError CInt)
+  -- ^ __Protocol Version Information__
+  , s2n_connection_get_server_protocol_version :: Ptr S2nConnection -> IO (Either S2nError CInt)
+  , s2n_connection_get_actual_protocol_version :: Ptr S2nConnection -> IO (Either S2nError CInt)
+  , s2n_connection_get_client_hello_version :: Ptr S2nConnection -> IO (Either S2nError CInt)
+  , s2n_connection_get_cipher :: Ptr S2nConnection -> IO (Either S2nError CString)
+  -- ^ __Cipher & Security Information__
+  , s2n_connection_get_certificate_match :: Ptr S2nConnection -> Ptr S2nCertSniMatch -> IO (Either S2nError CInt)
+  , s2n_connection_get_master_secret :: Ptr S2nConnection -> Ptr Word8 -> CSize -> IO (Either S2nError CInt)
+  , s2n_connection_tls_exporter :: Ptr S2nConnection -> Ptr Word8 -> Word32 -> Ptr Word8 -> Word32 -> Ptr Word8 -> Word32 -> IO (Either S2nError CInt)
+  , s2n_connection_get_cipher_iana_value :: Ptr S2nConnection -> Ptr Word8 -> Ptr Word8 -> IO (Either S2nError CInt)
+  , s2n_connection_is_valid_for_cipher_preferences :: Ptr S2nConnection -> CString -> IO (Either S2nError CInt)
+  , s2n_connection_get_curve :: Ptr S2nConnection -> IO (Either S2nError CString)
+  , s2n_connection_get_kem_name :: Ptr S2nConnection -> IO (Either S2nError CString)
+  , s2n_connection_get_kem_group_name :: Ptr S2nConnection -> IO (Either S2nError CString)
+  , s2n_connection_get_key_exchange_group :: Ptr S2nConnection -> Ptr CString -> IO (Either S2nError CInt)
+  , s2n_connection_get_alert :: Ptr S2nConnection -> IO (Either S2nError CInt)
+  , s2n_connection_get_handshake_type_name :: Ptr S2nConnection -> IO (Either S2nError CString)
+  , s2n_connection_get_last_message_name :: Ptr S2nConnection -> IO (Either S2nError CString)
+  , s2n_async_pkey_op_perform :: Ptr S2nAsyncPkeyOp -> Ptr S2nCertPrivateKey -> IO (Either S2nError CInt)
+  -- ^ __Async Private Key Operations__
+  , s2n_async_pkey_op_apply :: Ptr S2nAsyncPkeyOp -> Ptr S2nConnection -> IO (Either S2nError CInt)
+  , s2n_async_pkey_op_free :: Ptr S2nAsyncPkeyOp -> IO (Either S2nError CInt)
+  , s2n_async_pkey_op_get_op_type :: Ptr S2nAsyncPkeyOp -> Ptr S2nAsyncPkeyOpType -> IO (Either S2nError CInt)
+  , s2n_async_pkey_op_get_input_size :: Ptr S2nAsyncPkeyOp -> Ptr Word32 -> IO (Either S2nError CInt)
+  , s2n_async_pkey_op_get_input :: Ptr S2nAsyncPkeyOp -> Ptr Word8 -> Word32 -> IO (Either S2nError CInt)
+  , s2n_async_pkey_op_set_output :: Ptr S2nAsyncPkeyOp -> Ptr Word8 -> Word32 -> IO (Either S2nError CInt)
+  , s2n_connection_set_server_max_early_data_size :: Ptr S2nConnection -> Word32 -> IO (Either S2nError CInt)
+  -- ^ __Early Data__
+  , s2n_connection_set_server_early_data_context :: Ptr S2nConnection -> Ptr Word8 -> Word16 -> IO (Either S2nError CInt)
+  , s2n_connection_get_early_data_status :: Ptr S2nConnection -> Ptr S2nEarlyDataStatus -> IO (Either S2nError CInt)
+  , s2n_connection_get_remaining_early_data_size :: Ptr S2nConnection -> Ptr Word32 -> IO (Either S2nError CInt)
+  , s2n_connection_get_max_early_data_size :: Ptr S2nConnection -> Ptr Word32 -> IO (Either S2nError CInt)
+  , s2n_send_early_data :: Ptr S2nConnection -> Ptr Word8 -> CSsize -> Ptr CSsize -> Ptr S2nBlockedStatus -> IO (Either S2nError CInt)
+  , s2n_recv_early_data :: Ptr S2nConnection -> Ptr Word8 -> CSsize -> Ptr CSsize -> Ptr S2nBlockedStatus -> IO (Either S2nError CInt)
+  , s2n_offered_early_data_get_context_length :: Ptr S2nOfferedEarlyData -> Ptr Word16 -> IO (Either S2nError CInt)
+  , s2n_offered_early_data_get_context :: Ptr S2nOfferedEarlyData -> Ptr Word8 -> Word16 -> IO (Either S2nError CInt)
+  , s2n_offered_early_data_reject :: Ptr S2nOfferedEarlyData -> IO (Either S2nError CInt)
+  , s2n_offered_early_data_accept :: Ptr S2nOfferedEarlyData -> IO (Either S2nError CInt)
+  , s2n_connection_serialization_length :: Ptr S2nConnection -> Ptr Word32 -> IO (Either S2nError CInt)
+  -- ^ __Connection Serialization__
+  , s2n_connection_serialize :: Ptr S2nConnection -> Ptr Word8 -> Word32 -> IO (Either S2nError CInt)
+  , s2n_connection_deserialize :: Ptr S2nConnection -> Ptr Word8 -> Word32 -> IO (Either S2nError CInt)
+  }
diff --git a/test/MemorySafety.hs b/test/MemorySafety.hs
new file mode 100644
--- /dev/null
+++ b/test/MemorySafety.hs
@@ -0,0 +1,499 @@
+{-# LANGUAGE NumericUnderscores #-}
+
+{- |
+Module      : Main
+Description : Memory safety tests for s2n-tls FFI bindings
+
+This test invokes all FFI functions to check for segfaults and memory errors.
+Each function name is printed before invocation so crashes can be identified.
+-}
+module Main where
+
+import Control.Exception (SomeException, catch)
+import Control.Monad (void)
+import Data.Word (Word16, Word32, Word8)
+import Foreign.C.String (withCString)
+import Foreign.C.Types (CInt (..))
+import Foreign.Marshal.Alloc (alloca, allocaBytes)
+import Foreign.Marshal.Array (allocaArray, pokeArray)
+import Foreign.Ptr (Ptr, nullFunPtr, nullPtr)
+import Foreign.Storable (poke)
+import System.IO (hFlush, stdout)
+
+import S2nTls.Ffi
+import S2nTls.Ffi.Types
+
+-- | Print function name and flush, then invoke it. Catches exceptions (only segfaults crash).
+invoke :: String -> IO a -> IO ()
+invoke name action = do
+    putStrLn $ "  " ++ name
+    hFlush stdout
+    result <- (Right <$> action) `catch` \(e :: SomeException) -> pure (Left e)
+    case result of
+        Right _ -> pure ()
+        Left _ -> pure () -- Exceptions are OK, we only care about segfaults
+
+main :: IO ()
+main = withS2nTlsFfi Linked $ \ffi -> do
+    putStrLn "=== s2n-tls Memory Safety Tests ==="
+    hFlush stdout
+
+    runInitTests ffi
+    runErrorTests ffi
+    runStackTraceTests ffi
+    runConfigTests ffi
+    runCertChainTests ffi
+    runConnectionTests ffi
+    runPskTests ffi
+    runCleanupTests ffi
+
+    putStrLn "=== All tests completed ==="
+    hFlush stdout
+
+--------------------------------------------------------------------------------
+-- Initialization Tests
+--------------------------------------------------------------------------------
+
+runInitTests :: S2nTlsFfi -> IO ()
+runInitTests ffi = do
+    putStrLn "\n[Initialization]"
+    hFlush stdout
+    invoke "s2n_init" $ s2n_init ffi
+    invoke "s2n_crypto_disable_init" $ s2n_crypto_disable_init ffi
+    invoke "s2n_disable_atexit" $ s2n_disable_atexit ffi
+    invoke "s2n_get_openssl_version" $ s2n_get_openssl_version ffi
+    alloca $ \ptr ->
+        invoke "s2n_get_fips_mode" $ s2n_get_fips_mode ffi ptr
+
+--------------------------------------------------------------------------------
+-- Error Handling Tests
+--------------------------------------------------------------------------------
+
+runErrorTests :: S2nTlsFfi -> IO ()
+runErrorTests ffi = do
+    putStrLn "\n[Error Handling]"
+    hFlush stdout
+    invoke "s2n_errno_location" $ s2n_errno_location ffi
+    invoke "s2n_error_get_type" $ s2n_error_get_type ffi 0
+    withCString "EN" $ \lang -> do
+        invoke "s2n_strerror" $ s2n_strerror ffi 0 lang
+        invoke "s2n_strerror_debug" $ s2n_strerror_debug ffi 0 lang
+    invoke "s2n_strerror_name" $ s2n_strerror_name ffi 0
+    invoke "s2n_strerror_source" $ s2n_strerror_source ffi 0
+
+--------------------------------------------------------------------------------
+-- Stack Trace Tests
+--------------------------------------------------------------------------------
+
+runStackTraceTests :: S2nTlsFfi -> IO ()
+runStackTraceTests ffi = do
+    putStrLn "\n[Stack Traces]"
+    hFlush stdout
+    invoke "s2n_stack_traces_enabled" $ s2n_stack_traces_enabled ffi
+    invoke "s2n_stack_traces_enabled_set" $ s2n_stack_traces_enabled_set ffi 0
+    invoke "s2n_calculate_stacktrace" $ s2n_calculate_stacktrace ffi
+    allocaBytes 8 $ \ptr ->
+        invoke "s2n_get_stacktrace" $ s2n_get_stacktrace ffi ptr
+    invoke "s2n_free_stacktrace" $ s2n_free_stacktrace ffi
+
+--------------------------------------------------------------------------------
+-- Config Tests
+--------------------------------------------------------------------------------
+
+runConfigTests :: S2nTlsFfi -> IO ()
+runConfigTests ffi = do
+    putStrLn "\n[Config]"
+    hFlush stdout
+    invoke "s2n_config_new" $ s2n_config_new ffi
+    invoke "s2n_config_new_minimal" $ s2n_config_new_minimal ffi
+
+    -- Run config operation tests with a fresh config
+    withConfig ffi $ \cfg -> do
+        putStrLn "\n[Config Operations]"
+        hFlush stdout
+        invoke "s2n_config_set_wall_clock" $ s2n_config_set_wall_clock ffi cfg nullFunPtr nullPtr
+        invoke "s2n_config_set_monotonic_clock" $ s2n_config_set_monotonic_clock ffi cfg nullFunPtr nullPtr
+        invoke "s2n_config_set_cache_store_callback" $ s2n_config_set_cache_store_callback ffi cfg nullFunPtr nullPtr
+        invoke "s2n_config_set_cache_retrieve_callback" $ s2n_config_set_cache_retrieve_callback ffi cfg nullFunPtr nullPtr
+        invoke "s2n_config_set_cache_delete_callback" $ s2n_config_set_cache_delete_callback ffi cfg nullFunPtr nullPtr
+        invoke "s2n_config_wipe_trust_store" $ s2n_config_wipe_trust_store ffi cfg
+        invoke "s2n_config_load_system_certs" $ s2n_config_load_system_certs ffi cfg
+        withCString "/nonexistent" $ \path ->
+            invoke "s2n_config_set_verification_ca_location" $ s2n_config_set_verification_ca_location ffi cfg path nullPtr
+        withCString "-----BEGIN CERTIFICATE-----\ntest\n-----END CERTIFICATE-----" $ \pem ->
+            invoke "s2n_config_add_pem_to_trust_store" $ s2n_config_add_pem_to_trust_store ffi cfg pem
+        invoke "s2n_config_set_cert_authorities_from_trust_store" $ s2n_config_set_cert_authorities_from_trust_store ffi cfg
+        invoke "s2n_config_set_verify_after_sign" $ s2n_config_set_verify_after_sign ffi cfg (S2nVerifyAfterSign 0)
+        invoke "s2n_config_set_check_stapled_ocsp_response" $ s2n_config_set_check_stapled_ocsp_response ffi cfg 0
+        invoke "s2n_config_disable_x509_time_verification" $ s2n_config_disable_x509_time_verification ffi cfg
+        invoke "s2n_config_disable_x509_verification" $ s2n_config_disable_x509_verification ffi cfg
+        invoke "s2n_config_set_max_cert_chain_depth" $ s2n_config_set_max_cert_chain_depth ffi cfg 10
+        invoke "s2n_config_set_verify_host_callback" $ s2n_config_set_verify_host_callback ffi cfg nullFunPtr nullPtr
+        withCString "-----BEGIN DH PARAMETERS-----\ntest\n-----END DH PARAMETERS-----" $ \dh ->
+            invoke "s2n_config_add_dhparams" $ s2n_config_add_dhparams ffi cfg dh
+        withCString "default_tls13" $ \policy ->
+            invoke "s2n_config_set_cipher_preferences" $ s2n_config_set_cipher_preferences ffi cfg policy
+        allocaArray 10 $ \protoPtr ->
+            invoke "s2n_config_append_protocol_preference" $ s2n_config_append_protocol_preference ffi cfg protoPtr 5
+        withCString "h2" $ \proto1 ->
+            allocaArray 1 $ \protos -> do
+                pokeArray protos [proto1]
+                invoke "s2n_config_set_protocol_preferences" $ s2n_config_set_protocol_preferences ffi cfg protos 1
+        invoke "s2n_config_set_status_request_type" $ s2n_config_set_status_request_type ffi cfg (S2nStatusRequestType 0)
+        invoke "s2n_config_set_ct_support_level" $ s2n_config_set_ct_support_level ffi cfg (S2nCtSupportLevel 0)
+        invoke "s2n_config_set_alert_behavior" $ s2n_config_set_alert_behavior ffi cfg (S2nAlertBehavior 0)
+        allocaBytes 16 $ \buf ->
+            invoke "s2n_config_set_extension_data" $ s2n_config_set_extension_data ffi cfg (S2nTlsExtensionType 0) buf 16
+        invoke "s2n_config_send_max_fragment_length" $ s2n_config_send_max_fragment_length ffi cfg (S2nMaxFragLen 1)
+        invoke "s2n_config_accept_max_fragment_length" $ s2n_config_accept_max_fragment_length ffi cfg
+        invoke "s2n_config_set_session_state_lifetime" $ s2n_config_set_session_state_lifetime ffi cfg 3600
+        invoke "s2n_config_set_session_tickets_onoff" $ s2n_config_set_session_tickets_onoff ffi cfg 1
+        invoke "s2n_config_set_session_cache_onoff" $ s2n_config_set_session_cache_onoff ffi cfg 1
+        invoke "s2n_config_set_ticket_encrypt_decrypt_key_lifetime" $ s2n_config_set_ticket_encrypt_decrypt_key_lifetime ffi cfg 3600
+        invoke "s2n_config_set_ticket_decrypt_key_lifetime" $ s2n_config_set_ticket_decrypt_key_lifetime ffi cfg 7200
+        allocaBytes 16 $ \keyName -> allocaBytes 32 $ \keyData ->
+            invoke "s2n_config_add_ticket_crypto_key" $ s2n_config_add_ticket_crypto_key ffi cfg keyName 16 keyData 32 0
+        invoke "s2n_config_require_ticket_forward_secrecy" $ s2n_config_require_ticket_forward_secrecy ffi cfg 1
+        invoke "s2n_config_set_send_buffer_size" $ s2n_config_set_send_buffer_size ffi cfg 16384
+        invoke "s2n_config_set_recv_multi_record" $ s2n_config_set_recv_multi_record ffi cfg 1
+        invoke "s2n_config_set_client_hello_cb" $ s2n_config_set_client_hello_cb ffi cfg nullFunPtr nullPtr
+        invoke "s2n_config_set_client_hello_cb_mode" $ s2n_config_set_client_hello_cb_mode ffi cfg (S2nClientHelloCbMode 0)
+        invoke "s2n_config_set_max_blinding_delay" $ s2n_config_set_max_blinding_delay ffi cfg 30
+        alloca $ \authType ->
+            invoke "s2n_config_get_client_auth_type" $ s2n_config_get_client_auth_type ffi cfg authType
+        invoke "s2n_config_set_client_auth_type" $ s2n_config_set_client_auth_type ffi cfg (S2nCertAuthType 0)
+        invoke "s2n_config_set_initial_ticket_count" $ s2n_config_set_initial_ticket_count ffi cfg 2
+        invoke "s2n_config_set_psk_mode" $ s2n_config_set_psk_mode ffi cfg (S2nPskMode 0)
+        invoke "s2n_config_set_psk_selection_callback" $ s2n_config_set_psk_selection_callback ffi cfg nullFunPtr nullPtr
+        invoke "s2n_config_set_async_pkey_callback" $ s2n_config_set_async_pkey_callback ffi cfg nullFunPtr
+        invoke "s2n_config_set_async_pkey_validation_mode" $ s2n_config_set_async_pkey_validation_mode ffi cfg (S2nAsyncPkeyValidationMode 0)
+        invoke "s2n_config_set_session_ticket_cb" $ s2n_config_set_session_ticket_cb ffi cfg nullFunPtr nullPtr
+        invoke "s2n_config_set_key_log_cb" $ s2n_config_set_key_log_cb ffi cfg nullFunPtr nullPtr
+        invoke "s2n_config_enable_cert_req_dss_legacy_compat" $ s2n_config_enable_cert_req_dss_legacy_compat ffi cfg
+        invoke "s2n_config_set_server_max_early_data_size" $ s2n_config_set_server_max_early_data_size ffi cfg 16384
+        invoke "s2n_config_set_early_data_cb" $ s2n_config_set_early_data_cb ffi cfg nullFunPtr
+        invoke "s2n_config_set_ctx" $ s2n_config_set_ctx ffi cfg nullPtr
+        alloca $ \ctxPtr ->
+            invoke "s2n_config_get_ctx" $ s2n_config_get_ctx ffi cfg ctxPtr
+        allocaArray 10 $ \groups -> alloca $ \count ->
+            invoke "s2n_config_get_supported_groups" $ s2n_config_get_supported_groups ffi cfg groups 10 count
+        invoke "s2n_config_set_serialization_version" $ s2n_config_set_serialization_version ffi cfg (S2nSerializationVersion 0)
+        invoke "s2n_config_set_cert_tiebreak_callback" $ s2n_config_set_cert_tiebreak_callback ffi cfg nullFunPtr
+        withCString "" $ \emptyPath ->
+            invoke "s2n_config_add_cert_chain_and_key" $ s2n_config_add_cert_chain_and_key ffi cfg emptyPath emptyPath
+        invoke "s2n_config_free_dhparams" $ s2n_config_free_dhparams ffi cfg
+        invoke "s2n_config_free_cert_chain_and_key" $ s2n_config_free_cert_chain_and_key ffi cfg
+
+    -- Test s2n_config_free separately
+    result <- s2n_config_new ffi
+    case result of
+        Left _ -> pure ()
+        Right cfg -> invoke "s2n_config_free" $ s2n_config_free ffi cfg
+
+-- | Helper to run tests with a fresh config
+withConfig :: S2nTlsFfi -> (Ptr S2nConfig -> IO ()) -> IO ()
+withConfig ffi action = do
+    result <- s2n_config_new ffi
+    case result of
+        Left _ -> putStrLn "  [skipped - config creation failed]"
+        Right cfg -> do
+            action cfg
+            void $ s2n_config_free ffi cfg
+
+--------------------------------------------------------------------------------
+-- Certificate Chain Tests
+--------------------------------------------------------------------------------
+
+runCertChainTests :: S2nTlsFfi -> IO ()
+runCertChainTests ffi = do
+    putStrLn "\n[Certificate Chain]"
+    hFlush stdout
+    invoke "s2n_cert_chain_and_key_new" $ s2n_cert_chain_and_key_new ffi
+
+    withCertChain ffi $ \cert -> do
+        withCString "-----BEGIN CERTIFICATE-----\ntest\n-----END CERTIFICATE-----" $ \pemCert ->
+            withCString "-----BEGIN PRIVATE KEY-----\ntest\n-----END PRIVATE KEY-----" $ \pemKey ->
+                invoke "s2n_cert_chain_and_key_load_pem" $ s2n_cert_chain_and_key_load_pem ffi cert pemCert pemKey
+        allocaBytes 100 $ \certBuf -> allocaBytes 100 $ \keyBuf ->
+            invoke "s2n_cert_chain_and_key_load_pem_bytes" $ s2n_cert_chain_and_key_load_pem_bytes ffi cert certBuf 100 keyBuf 100
+        allocaBytes 100 $ \buf ->
+            invoke "s2n_cert_chain_and_key_load_public_pem_bytes" $ s2n_cert_chain_and_key_load_public_pem_bytes ffi cert buf 100
+        invoke "s2n_cert_chain_and_key_set_ctx" $ s2n_cert_chain_and_key_set_ctx ffi cert nullPtr
+        invoke "s2n_cert_chain_and_key_get_ctx" $ s2n_cert_chain_and_key_get_ctx ffi cert
+        invoke "s2n_cert_chain_and_key_get_private_key" $ s2n_cert_chain_and_key_get_private_key ffi cert
+        allocaBytes 100 $ \buf ->
+            invoke "s2n_cert_chain_and_key_set_ocsp_data" $ s2n_cert_chain_and_key_set_ocsp_data ffi cert buf 100
+        allocaBytes 100 $ \buf ->
+            invoke "s2n_cert_chain_and_key_set_sct_list" $ s2n_cert_chain_and_key_set_sct_list ffi cert buf 100
+        alloca $ \lengthPtr ->
+            invoke "s2n_cert_chain_get_length" $ s2n_cert_chain_get_length ffi cert lengthPtr
+        alloca $ \certPtr ->
+            invoke "s2n_cert_chain_get_cert" $ s2n_cert_chain_get_cert ffi cert certPtr 0
+
+    -- Test s2n_cert_chain_and_key_free separately
+    result <- s2n_cert_chain_and_key_new ffi
+    case result of
+        Left _ -> pure ()
+        Right cert -> invoke "s2n_cert_chain_and_key_free" $ s2n_cert_chain_and_key_free ffi cert
+
+-- | Helper to run tests with a fresh cert chain
+withCertChain :: S2nTlsFfi -> (Ptr S2nCertChainAndKey -> IO ()) -> IO ()
+withCertChain ffi action = do
+    result <- s2n_cert_chain_and_key_new ffi
+    case result of
+        Left _ -> putStrLn "  [skipped - cert chain creation failed]"
+        Right cert -> do
+            action cert
+            void $ s2n_cert_chain_and_key_free ffi cert
+
+--------------------------------------------------------------------------------
+-- Connection Tests
+--------------------------------------------------------------------------------
+
+runConnectionTests :: S2nTlsFfi -> IO ()
+runConnectionTests ffi = do
+    putStrLn "\n[Connection]"
+    hFlush stdout
+    invoke "s2n_connection_new (client)" $ s2n_connection_new ffi S2nClient
+    invoke "s2n_connection_new (server)" $ s2n_connection_new ffi S2nServer
+
+    withConnection ffi $ \conn -> do
+        putStrLn "\n[Connection Operations]"
+        hFlush stdout
+
+        -- Config assignment
+        cfgResult <- s2n_config_new ffi
+        case cfgResult of
+            Left _ -> pure ()
+            Right cfg -> do
+                invoke "s2n_connection_set_config" $ s2n_connection_set_config ffi conn cfg
+                void $ s2n_config_free ffi cfg
+
+        invoke "s2n_connection_set_ctx" $ s2n_connection_set_ctx ffi conn nullPtr
+        invoke "s2n_connection_get_ctx" $ s2n_connection_get_ctx ffi conn
+        invoke "s2n_client_hello_cb_done" $ s2n_client_hello_cb_done ffi conn
+        invoke "s2n_connection_server_name_extension_used" $ s2n_connection_server_name_extension_used ffi conn
+        invoke "s2n_connection_get_client_hello" $ s2n_connection_get_client_hello ffi conn
+        allocaBytes 100 $ \buf ->
+            invoke "s2n_client_hello_parse_message" $ s2n_client_hello_parse_message ffi buf 100
+        invoke "s2n_connection_set_fd" $ s2n_connection_set_fd ffi conn (-1)
+        invoke "s2n_connection_set_read_fd" $ s2n_connection_set_read_fd ffi conn (-1)
+        invoke "s2n_connection_set_write_fd" $ s2n_connection_set_write_fd ffi conn (-1)
+        alloca $ \fdPtr -> do
+            invoke "s2n_connection_get_read_fd" $ s2n_connection_get_read_fd ffi conn fdPtr
+            invoke "s2n_connection_get_write_fd" $ s2n_connection_get_write_fd ffi conn fdPtr
+        invoke "s2n_connection_use_corked_io" $ s2n_connection_use_corked_io ffi conn
+        invoke "s2n_connection_set_recv_ctx" $ s2n_connection_set_recv_ctx ffi conn nullPtr
+        invoke "s2n_connection_set_send_ctx" $ s2n_connection_set_send_ctx ffi conn nullPtr
+        invoke "s2n_connection_set_recv_cb" $ s2n_connection_set_recv_cb ffi conn nullFunPtr
+        invoke "s2n_connection_set_send_cb" $ s2n_connection_set_send_cb ffi conn nullFunPtr
+        invoke "s2n_connection_prefer_throughput" $ s2n_connection_prefer_throughput ffi conn
+        invoke "s2n_connection_prefer_low_latency" $ s2n_connection_prefer_low_latency ffi conn
+        invoke "s2n_connection_set_recv_buffering" $ s2n_connection_set_recv_buffering ffi conn 1
+        invoke "s2n_peek_buffered" $ s2n_peek_buffered ffi conn
+        invoke "s2n_connection_set_dynamic_buffers" $ s2n_connection_set_dynamic_buffers ffi conn 1
+        invoke "s2n_connection_set_dynamic_record_threshold" $ s2n_connection_set_dynamic_record_threshold ffi conn 1400 100
+        invoke "s2n_connection_set_verify_host_callback" $ s2n_connection_set_verify_host_callback ffi conn nullFunPtr nullPtr
+        invoke "s2n_connection_set_blinding" $ s2n_connection_set_blinding ffi conn S2nSelfServiceBlinding
+        invoke "s2n_connection_get_delay" $ s2n_connection_get_delay ffi conn
+        withCString "default_tls13" $ \policy ->
+            invoke "s2n_connection_set_cipher_preferences" $ s2n_connection_set_cipher_preferences ffi conn policy
+        invoke "s2n_connection_request_key_update" $ s2n_connection_request_key_update ffi conn (S2nPeerKeyUpdate 0)
+        allocaBytes 10 $ \proto ->
+            invoke "s2n_connection_append_protocol_preference" $ s2n_connection_append_protocol_preference ffi conn proto 5
+        withCString "h2" $ \proto1 ->
+            allocaArray 1 $ \protos -> do
+                pokeArray protos [proto1]
+                invoke "s2n_connection_set_protocol_preferences" $ s2n_connection_set_protocol_preferences ffi conn protos 1
+        withCString "example.com" $ \name ->
+            invoke "s2n_set_server_name" $ s2n_set_server_name ffi conn name
+        invoke "s2n_get_server_name" $ s2n_get_server_name ffi conn
+        invoke "s2n_get_application_protocol" $ s2n_get_application_protocol ffi conn
+        alloca $ \lenPtr -> do
+            invoke "s2n_connection_get_ocsp_response" $ s2n_connection_get_ocsp_response ffi conn lenPtr
+            invoke "s2n_connection_get_sct_list" $ s2n_connection_get_sct_list ffi conn lenPtr
+        alloca $ \blocked -> do
+            invoke "s2n_negotiate" $ s2n_negotiate ffi conn blocked
+            allocaBytes 100 $ \buf -> do
+                invoke "s2n_send" $ s2n_send ffi conn buf 100 blocked
+                invoke "s2n_recv" $ s2n_recv ffi conn buf 100 blocked
+            invoke "s2n_shutdown" $ s2n_shutdown ffi conn blocked
+            invoke "s2n_shutdown_send" $ s2n_shutdown_send ffi conn blocked
+        invoke "s2n_peek" $ s2n_peek ffi conn
+        alloca $ \authType ->
+            invoke "s2n_connection_get_client_auth_type" $ s2n_connection_get_client_auth_type ffi conn authType
+        invoke "s2n_connection_set_client_auth_type" $ s2n_connection_set_client_auth_type ffi conn (S2nCertAuthType 0)
+        alloca $ \certPtr -> alloca $ \lenPtr ->
+            invoke "s2n_connection_get_client_cert_chain" $ s2n_connection_get_client_cert_chain ffi conn certPtr lenPtr
+        invoke "s2n_connection_client_cert_used" $ s2n_connection_client_cert_used ffi conn
+        invoke "s2n_connection_add_new_tickets_to_send" $ s2n_connection_add_new_tickets_to_send ffi conn 1
+        alloca $ \ticketsPtr ->
+            invoke "s2n_connection_get_tickets_sent" $ s2n_connection_get_tickets_sent ffi conn ticketsPtr
+        invoke "s2n_connection_set_server_keying_material_lifetime" $ s2n_connection_set_server_keying_material_lifetime ffi conn 86400
+        allocaBytes 256 $ \buf -> do
+            invoke "s2n_connection_set_session" $ s2n_connection_set_session ffi conn buf 256
+            invoke "s2n_connection_get_session" $ s2n_connection_get_session ffi conn buf 256
+        invoke "s2n_connection_get_session_ticket_lifetime_hint" $ s2n_connection_get_session_ticket_lifetime_hint ffi conn
+        invoke "s2n_connection_get_session_length" $ s2n_connection_get_session_length ffi conn
+        invoke "s2n_connection_get_session_id_length" $ s2n_connection_get_session_id_length ffi conn
+        allocaBytes 32 $ \buf ->
+            invoke "s2n_connection_get_session_id" $ s2n_connection_get_session_id ffi conn buf 32
+        invoke "s2n_connection_is_session_resumed" $ s2n_connection_is_session_resumed ffi conn
+        invoke "s2n_connection_is_ocsp_stapled" $ s2n_connection_is_ocsp_stapled ffi conn
+        alloca $ \sigAlg -> do
+            invoke "s2n_connection_get_selected_signature_algorithm" $ s2n_connection_get_selected_signature_algorithm ffi conn sigAlg
+            invoke "s2n_connection_get_selected_client_cert_signature_algorithm" $ s2n_connection_get_selected_client_cert_signature_algorithm ffi conn sigAlg
+        alloca $ \hashAlg -> do
+            invoke "s2n_connection_get_selected_digest_algorithm" $ s2n_connection_get_selected_digest_algorithm ffi conn hashAlg
+            invoke "s2n_connection_get_selected_client_cert_digest_algorithm" $ s2n_connection_get_selected_client_cert_digest_algorithm ffi conn hashAlg
+        invoke "s2n_connection_get_selected_cert" $ s2n_connection_get_selected_cert ffi conn
+        certResult <- s2n_cert_chain_and_key_new ffi
+        case certResult of
+            Left _ -> pure ()
+            Right peerCert -> do
+                invoke "s2n_connection_get_peer_cert_chain" $ s2n_connection_get_peer_cert_chain ffi conn peerCert
+                void $ s2n_cert_chain_and_key_free ffi peerCert
+        invoke "s2n_connection_get_wire_bytes_in" $ s2n_connection_get_wire_bytes_in ffi conn
+        invoke "s2n_connection_get_wire_bytes_out" $ s2n_connection_get_wire_bytes_out ffi conn
+        invoke "s2n_connection_get_client_protocol_version" $ s2n_connection_get_client_protocol_version ffi conn
+        invoke "s2n_connection_get_server_protocol_version" $ s2n_connection_get_server_protocol_version ffi conn
+        invoke "s2n_connection_get_actual_protocol_version" $ s2n_connection_get_actual_protocol_version ffi conn
+        invoke "s2n_connection_get_client_hello_version" $ s2n_connection_get_client_hello_version ffi conn
+        invoke "s2n_connection_get_cipher" $ s2n_connection_get_cipher ffi conn
+        alloca $ \sniMatch ->
+            invoke "s2n_connection_get_certificate_match" $ s2n_connection_get_certificate_match ffi conn sniMatch
+        allocaBytes 48 $ \buf ->
+            invoke "s2n_connection_get_master_secret" $ s2n_connection_get_master_secret ffi conn buf 48
+        allocaBytes 32 $ \label -> allocaBytes 32 $ \context -> allocaBytes 32 $ \output ->
+            invoke "s2n_connection_tls_exporter" $ s2n_connection_tls_exporter ffi conn label 32 context 32 output 32
+        alloca $ \first -> alloca $ \second ->
+            invoke "s2n_connection_get_cipher_iana_value" $ s2n_connection_get_cipher_iana_value ffi conn first second
+        withCString "default_tls13" $ \policy ->
+            invoke "s2n_connection_is_valid_for_cipher_preferences" $ s2n_connection_is_valid_for_cipher_preferences ffi conn policy
+        invoke "s2n_connection_get_curve" $ s2n_connection_get_curve ffi conn
+        invoke "s2n_connection_get_kem_name" $ s2n_connection_get_kem_name ffi conn
+        invoke "s2n_connection_get_kem_group_name" $ s2n_connection_get_kem_group_name ffi conn
+        alloca $ \groupName ->
+            invoke "s2n_connection_get_key_exchange_group" $ s2n_connection_get_key_exchange_group ffi conn groupName
+        invoke "s2n_connection_get_alert" $ s2n_connection_get_alert ffi conn
+        invoke "s2n_connection_get_handshake_type_name" $ s2n_connection_get_handshake_type_name ffi conn
+        invoke "s2n_connection_get_last_message_name" $ s2n_connection_get_last_message_name ffi conn
+        invoke "s2n_connection_set_psk_mode" $ s2n_connection_set_psk_mode ffi conn (S2nPskMode 0)
+        alloca $ \lenPtr ->
+            invoke "s2n_connection_get_negotiated_psk_identity_length" $ s2n_connection_get_negotiated_psk_identity_length ffi conn lenPtr
+        allocaBytes 32 $ \buf ->
+            invoke "s2n_connection_get_negotiated_psk_identity" $ s2n_connection_get_negotiated_psk_identity ffi conn buf 32
+        invoke "s2n_connection_set_server_max_early_data_size" $ s2n_connection_set_server_max_early_data_size ffi conn 16384
+        allocaBytes 100 $ \ctx ->
+            invoke "s2n_connection_set_server_early_data_context" $ s2n_connection_set_server_early_data_context ffi conn ctx 100
+        alloca $ \status ->
+            invoke "s2n_connection_get_early_data_status" $ s2n_connection_get_early_data_status ffi conn status
+        alloca $ \remaining ->
+            invoke "s2n_connection_get_remaining_early_data_size" $ s2n_connection_get_remaining_early_data_size ffi conn remaining
+        alloca $ \maxSize ->
+            invoke "s2n_connection_get_max_early_data_size" $ s2n_connection_get_max_early_data_size ffi conn maxSize
+        alloca $ \blocked -> allocaBytes 100 $ \buf -> alloca $ \written -> do
+            invoke "s2n_send_early_data" $ s2n_send_early_data ffi conn buf 100 written blocked
+            invoke "s2n_recv_early_data" $ s2n_recv_early_data ffi conn buf 100 written blocked
+        alloca $ \lenPtr ->
+            invoke "s2n_connection_serialization_length" $ s2n_connection_serialization_length ffi conn lenPtr
+        allocaBytes 1024 $ \buf -> do
+            invoke "s2n_connection_serialize" $ s2n_connection_serialize ffi conn buf 1024
+            invoke "s2n_connection_deserialize" $ s2n_connection_deserialize ffi conn buf 1024
+        invoke "s2n_connection_free_handshake" $ s2n_connection_free_handshake ffi conn
+        invoke "s2n_connection_release_buffers" $ s2n_connection_release_buffers ffi conn
+        invoke "s2n_connection_wipe" $ s2n_connection_wipe ffi conn
+
+    -- Test s2n_connection_free separately
+    result <- s2n_connection_new ffi S2nClient
+    case result of
+        Left _ -> pure ()
+        Right conn -> invoke "s2n_connection_free" $ s2n_connection_free ffi conn
+
+-- | Helper to run tests with a fresh connection
+withConnection :: S2nTlsFfi -> (Ptr S2nConnection -> IO ()) -> IO ()
+withConnection ffi action = do
+    result <- s2n_connection_new ffi S2nClient
+    case result of
+        Left _ -> putStrLn "  [skipped - connection creation failed]"
+        Right conn -> do
+            action conn
+            void $ s2n_connection_free ffi conn
+
+--------------------------------------------------------------------------------
+-- PSK Tests
+--------------------------------------------------------------------------------
+
+runPskTests :: S2nTlsFfi -> IO ()
+runPskTests ffi = do
+    putStrLn "\n[PSK]"
+    hFlush stdout
+    invoke "s2n_external_psk_new" $ s2n_external_psk_new ffi
+
+    withPsk ffi $ \psk -> do
+        allocaBytes 32 $ \identity ->
+            invoke "s2n_psk_set_identity" $ s2n_psk_set_identity ffi psk identity 32
+        allocaBytes 32 $ \secret ->
+            invoke "s2n_psk_set_secret" $ s2n_psk_set_secret ffi psk secret 32
+        invoke "s2n_psk_set_hmac" $ s2n_psk_set_hmac ffi psk S2nPskHmacSha256
+        invoke "s2n_psk_configure_early_data" $ s2n_psk_configure_early_data ffi psk 16384 33 34
+        allocaBytes 10 $ \proto ->
+            invoke "s2n_psk_set_application_protocol" $ s2n_psk_set_application_protocol ffi psk proto 10
+        allocaBytes 32 $ \ctx ->
+            invoke "s2n_psk_set_early_data_context" $ s2n_psk_set_early_data_context ffi psk ctx 32
+
+    -- Test s2n_connection_append_psk
+    pskResult <- s2n_external_psk_new ffi
+    connResult <- s2n_connection_new ffi S2nClient
+    case (pskResult, connResult) of
+        (Right psk, Right conn) -> do
+            invoke "s2n_connection_append_psk" $ s2n_connection_append_psk ffi conn psk
+            void $ s2n_connection_free ffi conn
+            alloca $ \pskPtr -> do
+                poke pskPtr psk
+                void $ s2n_psk_free ffi pskPtr
+        _ -> pure ()
+
+    -- Test s2n_psk_free
+    pskResult2 <- s2n_external_psk_new ffi
+    case pskResult2 of
+        Left _ -> pure ()
+        Right psk ->
+            alloca $ \pskPtr -> do
+                poke pskPtr psk
+                invoke "s2n_psk_free" $ s2n_psk_free ffi pskPtr
+
+    -- Test offered PSK functions
+    invoke "s2n_offered_psk_new" $ s2n_offered_psk_new ffi
+    offeredResult <- s2n_offered_psk_new ffi
+    case offeredResult of
+        Left _ -> pure ()
+        Right offered -> do
+            alloca $ \identityPtr -> alloca $ \lenPtr ->
+                invoke "s2n_offered_psk_get_identity" $ s2n_offered_psk_get_identity ffi offered identityPtr lenPtr
+            alloca $ \offeredPtr -> do
+                poke offeredPtr offered
+                invoke "s2n_offered_psk_free" $ s2n_offered_psk_free ffi offeredPtr
+
+-- | Helper to run tests with a fresh PSK
+withPsk :: S2nTlsFfi -> (Ptr S2nPsk -> IO ()) -> IO ()
+withPsk ffi action = do
+    result <- s2n_external_psk_new ffi
+    case result of
+        Left _ -> putStrLn "  [skipped - PSK creation failed]"
+        Right psk -> do
+            action psk
+            alloca $ \pskPtr -> do
+                poke pskPtr psk
+                void $ s2n_psk_free ffi pskPtr
+
+--------------------------------------------------------------------------------
+-- Cleanup Tests
+--------------------------------------------------------------------------------
+
+runCleanupTests :: S2nTlsFfi -> IO ()
+runCleanupTests ffi = do
+    putStrLn "\n[Cleanup]"
+    hFlush stdout
+    invoke "s2n_cleanup" $ s2n_cleanup ffi
+    invoke "s2n_cleanup_final" $ s2n_cleanup_final ffi
