# `MobDev.Release.OpenSSL.CryptoNif`
[🔗](https://github.com/genericjam/mob_dev/blob/master/lib/mob_dev/release/openssl/crypto_nif.ex#L1)

Replaces `scripts/release/openssl/build_crypto_static_*.sh` (×4).
Compiles OTP's crypto NIF C sources with `-DSTATIC_ERLANG_NIF` for one
target ABI and archives the result as `crypto.a`. Pairs with
`MobDev.Release.OpenSSL` (the OpenSSL build itself) to produce the
two `.a` files that get static-linked into the user app's main
native binary:

    $OPENSSL_PREFIX/lib/libcrypto.a   ← from MobDev.Release.OpenSSL
    $OTP_SRC/lib/crypto/priv/lib/<arch>/crypto.a   ← from this module

## Why static-link the crypto NIF?

Different reasons on each platform; the artifact is the same.

  * **Android.** dlopen'd children inherit `RTLD_LOCAL` by default,
    which hides the parent's `enif_*` symbols from `crypto.so`.
    `crypto.so`'s `on_load` then fails with "cannot locate symbol".
    Static linking sidesteps that — the BEAM finds
    `crypto_nif_init` via `dlsym(RTLD_DEFAULT)` instead of dlopen.
  * **iOS.** App Store forbids loading unsigned dylib/dlopen — every
    NIF must be present in the final signed binary. Same artifact.

## Per-target deltas (the spec)

All four targets compile the same 30 source files (`@sources`) with a
shared base CFLAGS list (`@base_cflags`). The deltas are:

| Target        | Arch dir                        | Toolchain          | Extra CFLAGS                                  | nm symbol         |
|---------------|---------------------------------|--------------------|-----------------------------------------------|-------------------|
| android_arm64 | aarch64-unknown-linux-android   | NDK clang/llvm-ar  | Android hardening: branch-protect, stack-clash, _GNU_SOURCE | `crypto_nif_init`  |
| android_arm32 | arm-unknown-linux-androideabi   | NDK clang/llvm-ar  | Android hardening + `-march=armv7-a -mfloat-abi=softfp -mthumb` | `crypto_nif_init`  |
| ios_sim       | aarch64-apple-iossimulator      | xcrun (sim SDK)    | iOS minimal — no Android hardening            | `_crypto_nif_init` |
| ios_device    | aarch64-apple-ios               | xcrun (device SDK) | iOS minimal — no Android hardening            | `_crypto_nif_init` |

## Phases

Each `build/2` call:
  1. Precheck — OTP source + OpenSSL prefix exist; Android/iOS
     toolchain reachable.
  2. Compile — for each of @sources, run `<cc> <cflags> -c -o obj src`.
  3. Archive — `<ar> rcs crypto.a obj1 obj2 ...` then `<ranlib> crypto.a`.
  4. Verify — `<nm> crypto.a`, scan output for the expected symbol.
     Symbol missing is a `:precondition_failed` (means our compile
     didn't actually produce `crypto_nif_init` — usually because the
     OTP source moved out from under us).

# `base_cflags`

```elixir
@spec base_cflags() :: [String.t()]
```

Base CFLAGS shared across all targets. Public for testing.

# `build`

```elixir
@spec build(
  atom(),
  keyword()
) :: {:ok, map()} | MobDev.Release.Errors.t()
```

Compile + archive + verify the crypto NIF for one target. Returns
`{:ok, info}` naming the produced archive, or a tagged error.

Options:
  * `:otp_src` — OTP source checkout (default: `$OTP_SRC` env or
    `~/code/otp`)
  * `:openssl_prefix` — OpenSSL install dir (default: target's
    `default_prefix`)
  * `:ndk_root` — Android NDK root (Android targets only)

# `cflags`

```elixir
@spec cflags(MobDev.Release.OpenSSL.CryptoNif.Target.t(), Path.t(), Path.t()) :: [
  String.t()
]
```

Assemble the full CFLAGS list for a target, given OpenSSL prefix +
OTP source path. Pure function for testability — silent flag drops
are the exact regression class this module exists to prevent.

Order: base CFLAGS + per-target extras + include paths. Includes
carry the arch_dir suffix on OTP-internal headers because OTP
per-arch's `erl_int_sizes_config.h` lives there.

# `check_symbol_present`

```elixir
@spec check_symbol_present(binary(), String.t(), Path.t()) ::
  :ok | MobDev.Release.Errors.t()
```

Parse `nm` output and confirm the expected `crypto_nif_init` symbol
is exported (`T` flag in nm's output). Returns `:ok` or a tagged
precondition_failed.

Public for testing — the shell version did `nm <archive> | grep -E
' T crypto_nif_init$' | head -3` and silently let release ship if
the grep returned 0 lines.  This module's behaviour: missing symbol
is a hard failure with a clear message.

# `sources`

```elixir
@spec sources() :: [String.t()]
```

Source files compiled for every target. Public so tests can pin the surface.

# `target_spec`

```elixir
@spec target_spec(atom()) :: MobDev.Release.OpenSSL.CryptoNif.Target.t()
```

Per-target spec. Public so tests can lock down the surface
(especially the `extra_cflags` lists — silent drops there would
silently weaken released binaries).

# `targets`

```elixir
@spec targets() :: [atom()]
```

All known crypto-NIF targets. Same set as `MobDev.Release.OpenSSL.targets/0`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
