# `Lightstreamer.Protocol`
[🔗](https://github.com/ktec/lightstreamer/blob/v0.1.0/lib/lightstreamer/protocol.ex#L1)

Pure encoders for TLCP client requests, plus the percent-encoding shared by
the whole codec.

Each function returns iodata for exactly one WebSocket text message:
a request-name line and a parameter line, both CR-LF-terminated (`wsok/0`
being the protocol's single exception, with no parameter line at all).
No sockets, no processes — `Lightstreamer.Session` sends what these return.

# `bind_session`

```elixir
@spec bind_session(String.t(), opts) :: iodata()
when opts: [keepalive_millis: pos_integer(), inactivity_millis: pos_integer()]
```

Encodes a `bind_session` request, rebinding an existing session onto a new
WebSocket (the `LOOP` flow).

## Options

  * `:keepalive_millis` - as in `create_session/1`
  * `:inactivity_millis` - as in `create_session/1`

## Examples

    iex> IO.iodata_to_binary(Lightstreamer.Protocol.bind_session("Sid123"))
    "bind_session\r\nLS_session=Sid123\r\n"

# `create_session`

```elixir
@spec create_session(opts) :: iodata()
when opts: [
       adapter_set: String.t(),
       user: String.t(),
       password: String.t(),
       keepalive_millis: pos_integer(),
       inactivity_millis: pos_integer()
     ]
```

Encodes a `create_session` request.

Always sends the fixed `LS_cid` and an empty `LS_supported_diffs`, refusing
the `^P`/`^T` diff formats this library cannot decode.

## Options

  * `:adapter_set` - the adapter set to open the session on (server default:
    `"DEFAULT"` when omitted)
  * `:user` - user credential; semantics are defined by the server's
    Metadata Adapter
  * `:password` - password credential
  * `:keepalive_millis` - requested keepalive interval; the server replies
    with the authoritative value in `CONOK`
  * `:inactivity_millis` - declared maximum client silence, committing us
    to outbound heartbeats

## Examples

    iex> IO.iodata_to_binary(Lightstreamer.Protocol.create_session(adapter_set: "DEMO"))
    "create_session\r\nLS_cid=mgQkwtwdysogQz2BJ4Ji%20kOj2Bg&LS_adapter_set=DEMO&LS_supported_diffs=\r\n"

# `heartbeat`

```elixir
@spec heartbeat() :: iodata()
```

The `heartbeat` pseudo-request, keeping a quiet connection alive when
`LS_inactivity_millis` was advertised. It has no parameters, so the empty
parameter line still carries its terminator.

## Examples

    iex> IO.iodata_to_binary(Lightstreamer.Protocol.heartbeat())
    "heartbeat\r\n\r\n"

# `percent_decode`

```elixir
@spec percent_decode(binary()) :: binary()
```

Decodes `%XX` percent-encoding (either hex case) in a server-sent value.

## Examples

    iex> Lightstreamer.Protocol.percent_decode("aa%7Cbb%3A%20cc")
    "aa|bb: cc"

# `percent_encode`

```elixir
@spec percent_encode(String.t()) :: String.t()
```

Percent-encodes a parameter value.

TLCP reserves exactly CR, LF, `&`, `=`, `%` and `+` in parameter values;
we additionally encode space (further encoding is explicitly permitted by
the protocol, and it keeps wire lines unambiguous to the eye).

## Examples

    iex> Lightstreamer.Protocol.percent_encode("a=b&c d")
    "a%3Db%26c%20d"

# `subscribe`

```elixir
@spec subscribe(pos_integer(), opts) :: iodata()
when opts: [
       sub_id: pos_integer(),
       items: [String.t()],
       fields: [String.t()],
       mode: :merge,
       data_adapter: String.t(),
       snapshot: boolean(),
       max_frequency: :unlimited | :unfiltered | float()
     ]
```

Encodes a `control` request adding a subscription.

`LS_session` is omitted: on a WebSocket it defaults to the last bound
session.

## Options

  * `:sub_id` - required; client-assigned progressive subscription ID,
    unique within the session
  * `:items` - required; item names, becoming the space-separated `LS_group`
  * `:fields` - required; field names, becoming the space-separated
    `LS_schema`
  * `:mode` - subscription mode; only `:merge` in v0.1 (default `:merge`)
  * `:data_adapter` - data adapter name (server default: `"DEFAULT"`)
  * `:snapshot` - whether to request the initial snapshot
  * `:max_frequency` - `:unlimited`, `:unfiltered`, or updates per second
    as a float

## Examples

    iex> IO.iodata_to_binary(Lightstreamer.Protocol.subscribe(1, sub_id: 1, items: ["item1"], fields: ["last_price"]))
    "control\r\nLS_reqId=1&LS_op=add&LS_subId=1&LS_group=item1&LS_schema=last_price&LS_mode=MERGE\r\n"

# `unsubscribe`

```elixir
@spec unsubscribe(pos_integer(), pos_integer()) :: iodata()
```

Encodes a `control` request deleting a subscription.

## Examples

    iex> IO.iodata_to_binary(Lightstreamer.Protocol.unsubscribe(9, 2))
    "control\r\nLS_reqId=9&LS_op=delete&LS_subId=2\r\n"

# `wsok`

```elixir
@spec wsok() :: iodata()
```

The `wsok` handshake request — the first message on every new WebSocket.

Uniquely, it has no parameter line (not even an empty one).

## Examples

    iex> IO.iodata_to_binary(Lightstreamer.Protocol.wsok())
    "wsok\r\n"

---

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