Packages

BACstack is a low-level Elixir implementation for the ASHRAE standard 135, BACnet - Building Automation and Controller network. In its current published state, v0.0.x, it can (only) act as a BACnet client for other BACnet devices.

Current section

Files

Jump to
bacstack lib bacnet protocol vmac_entry.ex
Raw

lib/bacnet/protocol/vmac_entry.ex

defmodule BACnet.Protocol.VmacEntry do
  @moduledoc """
  A BACnetVMACEntry maps a virtual MAC address (VMAC) to the native MAC
  address used by the underlying data link.

  It is used by the `Virtual_MAC_Address_Table` property of the Network Port
  object (see Annex H.7 of ASHRAE 135). Virtual MAC addressing is required by
  some network types (e.g. Zigbee, certain IPv6 configurations) so that the
  BACnet network layer can use an uniform 6-octet (or smaller) address space
  while the real data-link addresses remain larger or differently formatted.

  ### ASN.1 (ASHRAE 135)

      BACnetVMACEntry ::= SEQUENCE {
          virtual-mac-address [0] OCTET STRING,  -- maximum size 6 octets
          native-mac-address  [1] OCTET STRING
      }
  """

  alias BACnet.Protocol.ApplicationTags

  @typedoc """
  A single Virtual MAC Address table entry.
  """
  @type t :: %__MODULE__{
          virtual_mac_address: binary(),
          native_mac_address: binary()
        }

  @fields [:virtual_mac_address, :native_mac_address]
  @enforce_keys @fields
  defstruct @fields

  @doc """
  Encodes a BACnetVMACEntry into application tags encoding.
  """
  @spec encode(t(), Keyword.t()) ::
          {:ok, ApplicationTags.encoding_list()} | {:error, term()}
  def encode(%__MODULE__{} = entry, _opts \\ []) do
    with true <- byte_size(entry.virtual_mac_address) <= 6,
         {:ok, vmac_tag} <-
           ApplicationTags.create_tag_encoding(0, :octet_string, entry.virtual_mac_address),
         {:ok, nmac_tag} <-
           ApplicationTags.create_tag_encoding(1, :octet_string, entry.native_mac_address) do
      {:ok, [vmac_tag, nmac_tag]}
    else
      false -> {:error, :virtual_mac_too_long}
      {:error, _err} = err -> err
    end
  end

  @doc """
  Parses a BACnetVMACEntry from application tags encoding.
  """
  @spec parse(ApplicationTags.encoding_list()) ::
          {:ok, {t(), rest :: ApplicationTags.encoding_list()}} | {:error, term()}
  def parse(tags) when is_list(tags) do
    case tags do
      [
        {:tagged, {0, vmac, _len1}},
        {:tagged, {1, nmac, _len2}}
        | rest
      ]
      when is_binary(vmac) and is_binary(nmac) and byte_size(vmac) <= 6 ->
        entry = %__MODULE__{
          virtual_mac_address: vmac,
          native_mac_address: nmac
        }

        {:ok, {entry, rest}}

      _other ->
        {:error, :invalid_tags}
    end
  end

  @doc """
  Validates whether the given VMAC entry is well-formed.
  """
  @spec valid?(t()) :: boolean()
  def valid?(%__MODULE__{
        virtual_mac_address: vmac,
        native_mac_address: nmac
      })
      when is_binary(vmac) and byte_size(vmac) <= 6 and is_binary(nmac) and byte_size(nmac) > 0 do
    true
  end

  def valid?(%__MODULE__{}), do: false
end