# `SSL.Fingerprint`
[🔗](https://github.com/gsmlg-dev/http_fetch/blob/v0.16.7/apps/ex_ssl/lib/ssl/fingerprint.ex#L1)

JA3 and JA4 observation of exact, naked ClientHello handshake bytes.

Transport is explicitly `:tcp` or `:quic`; it is never inferred from ALPN.
Unknown IDs and original ordering are retained in `observation`. Only the
analytical projections omit GREASE and sort JA4 fields. Observation does not
validate a profile's negotiability or authenticate a peer. ECH bytes, if
present, describe the visible outer hello only, never an encrypted inner hello.

`new/1` and `feed/2` observe one fragmented ClientHello, bounded to 65,535
body bytes. Completion emits one result; trailing input is rejected. Empty
input and terminal empty feeds emit nothing. Errors discard the observer;
start a new observer for another ClientHello.

# `result`

```elixir
@type result() :: %{
  transport: :tcp | :quic,
  source: :visible_client_hello,
  observation: map(),
  ja3: %{raw: binary(), hash: binary()},
  ja4: %{
    prefix: binary(),
    cipher_raw: binary(),
    extension_raw: binary(),
    raw: binary(),
    hash: binary()
  }
}
```

# `t`

```elixir
@opaque t()
```

# `client_hello`

```elixir
@spec client_hello(binary(), :tcp | :quic) :: {:ok, result()} | {:error, term()}
```

# `feed`

```elixir
@spec feed(t(), binary()) :: {:ok, t(), [result()]} | {:error, term()}
```

# `new`

```elixir
@spec new(:tcp | :quic) :: {:ok, t()} | {:error, :invalid_transport}
```

---

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