packages feed

s2n-tls-ffi-0.1.0.0: README.md

# 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