# `IEC60870.ASDU`
[🔗](https://github.com/Ultraluv/ex_iec60870/blob/main/lib/iec60870/asdu.ex#L1)

Application Service Data Unit (ASDU) shared by IEC 60870-5-101 and IEC 60870-5-104.
Matches `cs101_asdu.c` in `lib60870-C`.

Supports:
- Discrete IOA encoding (`is_sequence: false`, `SQ = 0`)
- Compact sequential IOA encoding (`is_sequence: true`, `SQ = 1`)
- Configurable `COT` (1 or 2 bytes), `CA` (1 or 2 bytes), `IOA` (1, 2, or 3 bytes)
- Strict VSQ and single-object type validation matching `lib60870-C`

# `t`

```elixir
@type t() :: %IEC60870.ASDU{
  ca: non_neg_integer(),
  cot: IEC60870.Types.COT.name() | IEC60870.Types.COT.code(),
  is_negative: boolean(),
  is_sequence: boolean(),
  is_test: boolean(),
  oa: non_neg_integer(),
  objects: [IEC60870.InformationObject.t()],
  raw_payload: nil | binary(),
  type_id: nil | IEC60870.Types.TypeID.name() | IEC60870.Types.TypeID.code()
}
```

# `add_object`

```elixir
@spec add_object(t(), IEC60870.InformationObject.t(), IEC60870.AppLayerParameters.t()) ::
  {:ok, t()} | {:error, atom()}
```

Adds an Information Object to the ASDU, validating type consistency, single-object constraints,
sequence address continuity (`SQ=1`), and maximum ASDU byte size.
Matches `CS101_ASDU_addInformationObject` in `cs101_asdu.c`.

# `decode`

```elixir
@spec decode(binary(), IEC60870.AppLayerParameters.t()) ::
  {:ok, t()} | {:error, term()}
```

Decodes an ASDU binary buffer into `%IEC60870.ASDU{}` according to `AppLayerParameters`.
Performs strict header and VSQ bounds checks matching `CS101_ASDU_createFromBufferEx`.

# `encode`

```elixir
@spec encode(t(), IEC60870.AppLayerParameters.t()) ::
  {:ok, binary()} | {:error, term()}
```

Encodes an `%IEC60870.ASDU{}` into binary according to `AppLayerParameters`.

# `encode!`

```elixir
@spec encode!(t(), IEC60870.AppLayerParameters.t()) :: binary()
```

Encodes an ASDU and raises on error.

# `new`

Creates a new ASDU.
Supports both `ASDU.new(type_id, kw_opts)` and `ASDU.new(cot, ca, kw_opts)`.

# `new`

```elixir
@spec new(IEC60870.Types.COT.t(), non_neg_integer(), keyword()) :: t()
```

---

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