lib/wafer/dll/tx.ex

defmodule Wafer.DLL.Tx do
  defstruct [:buffer, :state]
  alias __MODULE__

  @moduledoc """
  A bytewise transmission buffer for our data link layer.
  """

  @byte_start 0x7D
  @byte_end 0x7E
  @byte_esc 0x7F

  @type t :: %Tx{buffer: binary, state: any}

  @doc """
  Initialize a new transmission buffer from a term.
  """
  @spec init(any) :: t
  def init(term) do
    data = :erlang.term_to_binary(term)
    crc = :erlang.crc32(data)
    %Tx{buffer: <<crc::integer-size(32), data::binary>>, state: :idle}
  end

  @doc """
  Get the next byte to transmit.
  """
  @spec tx(t) :: {byte, t} | :done
  def tx(%Tx{state: :idle} = tx), do: {@byte_start, %{tx | state: :transmitting}}

  def tx(%Tx{state: :transmitting, buffer: <<@byte_start, _::binary>>} = tx),
    do: {@byte_esc, %{tx | state: :escaping}}

  def tx(%Tx{state: :transmitting, buffer: <<@byte_end, _::binary>>} = tx),
    do: {@byte_esc, %{tx | state: :escaping}}

  def tx(%Tx{state: :transmitting, buffer: <<@byte_esc, _::binary>>} = tx),
    do: {@byte_esc, %{tx | state: :escaping}}

  def tx(%Tx{state: :transmitting, buffer: <<byte, buffer::binary>>} = tx),
    do: {byte, %{tx | state: :transmitting, buffer: buffer}}

  def tx(%Tx{state: :escaping, buffer: <<byte, buffer::binary>>} = tx),
    do: {byte, %{tx | state: :escaping, buffer: buffer}}

  def tx(%Tx{buffer: <<>>, state: :transmitting} = tx), do: {@byte_end, %{tx | state: :complete}}
  def tx(%Tx{state: :complete} = tx), do: {:done, tx}

  def complete?(%Tx{state: :complete}), do: true
  def complete?(%Tx{}), do: false
end