ADS.AMS (yaads v0.1.0)

Copy Markdown View Source

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.

Summary

Types

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

t()

Functions

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

A packet as bytes to send.

The state flags of a request, 0x0004.

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

The state flags of a response, 0x0005.

Types

framing()

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

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

t()

@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()
}

Functions

decode(buffer, framing \\ :tcp, max_data \\ 16_777_216)

@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(packet, framing \\ :tcp)

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

A packet as bytes to send.

request_flags()

@spec request_flags() :: 4

The state flags of a request, 0x0004.

response?(ams)

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

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

response_flags()

@spec response_flags() :: 5

The state flags of a response, 0x0005.