# `ADS.AMS`
[🔗](https://github.com/tallakt/yaads/blob/v0.1.0/lib/ads/ams.ex#L1)

AMS packets, which carry every ADS command and response: a 32-byte AMS header with the addresses
of both ends, the command, flags, the length of the data, an error code and an invoke id, then
the data. All numbers are little-endian.

Over TCP, to port 48898, each packet has the 6-byte AMS/TCP header in front: two reserved bytes,
which must be 0, and the length of the packet. Inside Secure ADS's TLS, on port 8016, packets go
without it, as the AMS header holds their length too. `encode/2` and `decode/3` take `:tcp` or
`:tls` for the one or the other.

    iex> packet = %ADS.AMS{target: <<5, 36, 69, 176, 1, 1>>, target_port: 851,
    ...>   source: <<10, 0, 0, 7, 1, 1>>, source_port: 32905, command: 4, invoke_id: 1}
    iex> frame = ADS.AMS.encode(packet)
    iex> byte_size(frame)
    38
    iex> {:ok, ^packet, ""} = ADS.AMS.decode(frame)

A request has state flags `0x0004`, an ADS command, and a response `0x0005`; the client picks
the invoke id, and the device copies it into its response.

# `framing`

```elixir
@type framing() :: :tcp | :tls
```

With the AMS/TCP header in front, as over TCP, or without, as inside Secure ADS.

# `t`

```elixir
@type t() :: %ADS.AMS{
  command: 0..65535,
  data: binary(),
  error_code: 0..4_294_967_295,
  invoke_id: 0..4_294_967_295,
  source: ADS.NetId.t(),
  source_port: ADS.ams_port(),
  state_flags: 0..65535,
  target: ADS.NetId.t(),
  target_port: ADS.ams_port()
}
```

# `decode`

```elixir
@spec decode(binary(), framing(), non_neg_integer()) ::
  {:ok, t(), binary()}
  | {:discard, binary()}
  | :more
  | {:error, :invalid_length}
```

The first packet in what has come over a connection so far:

  * `{:ok, packet, rest}` for a whole packet
  * `:more` for the start of one
  * `{:discard, rest}` for a frame that isn't an AMS packet, with reserved bytes other than 0 in
    its AMS/TCP header, or a length that doesn't agree with its AMS header's
  * `{:error, :invalid_length}` for one longer than `max_data` bytes of data allows, or too short
    for an AMS header, after which there's no telling where the next starts

The start of a packet, and one too short:

    iex> ADS.AMS.decode(<<0, 0, 32, 0, 0, 0, 1, 2, 3>>)
    :more
    iex> ADS.AMS.decode(<<0, 0, 12, 0, 0, 0>> <> <<0::96>>)
    {:error, :invalid_length}

# `encode`

```elixir
@spec encode(t(), framing()) :: binary()
```

A packet as bytes to send.

# `request_flags`

```elixir
@spec request_flags() :: 4
```

The state flags of a request, `0x0004`.

# `response?`

```elixir
@spec response?(t()) :: boolean()
```

Whether a packet is a response, by the lowest bit of its state flags.

# `response_flags`

```elixir
@spec response_flags() :: 5
```

The state flags of a response, `0x0005`.

---

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