lib/ex_libsrtp.ex

defmodule ExLibSRTP do
  @moduledoc """
  [libsrtp](https://github.com/cisco/libsrtp) bindings for Elixir.

  The workflow goes as follows:
  - create ExLibSRTP instance with `new/0`
  - add streams with `add_stream/2`
  - protect or unprotect packets with `protect/3`, `unprotect/3`, `protect_rtcp/3`, `unprotect_rtcp/3`
  - remove streams with `remove_stream/2`
  """
  alias ExLibSRTP.{Native, Policy}

  require Record

  @opaque t :: {__MODULE__, native :: reference}

  @type ssrc_t :: 0..4_294_967_295

  defguard is_ssrc(ssrc) when ssrc in 0..4_294_967_295

  defmacrop ref(native) do
    quote do
      {unquote(__MODULE__), unquote(native)}
    end
  end

  @spec new() :: t()
  def new() do
    ref(Native.create())
  end

  @spec add_stream(t(), policy :: Policy.t()) :: :ok
  def add_stream(ref(native) = _srtp, %Policy{} = policy) do
    {ssrc_type, ssrc} = Native.marshal_ssrc(policy.ssrc)
    {keys, keys_mkis} = Native.marshal_master_keys(policy.key)

    Native.add_stream(
      native,
      ssrc_type,
      ssrc,
      keys,
      keys_mkis,
      policy.rtp,
      policy.rtcp,
      policy.window_size,
      policy.allow_repeat_tx
    )
  end

  @spec remove_stream(t(), ssrc :: ssrc_t()) :: :ok
  def remove_stream(ref(native) = _srtp, ssrc) when is_ssrc(ssrc) do
    Native.remove_stream(native, ssrc)
  end

  @spec update(t(), policy :: Policy.t()) :: :ok
  def update(ref(native), %Policy{} = policy) do
    {ssrc_type, ssrc} = Native.marshal_ssrc(policy.ssrc)
    {keys, keys_mkis} = Native.marshal_master_keys(policy.key)

    Native.update(
      native,
      ssrc_type,
      ssrc,
      keys,
      keys_mkis,
      policy.rtp,
      policy.rtcp,
      policy.window_size,
      policy.allow_repeat_tx
    )
  end

  @spec protect(t(), unprotected :: binary(), mki_index :: pos_integer() | nil) ::
          {:ok, protected :: binary()}
  def protect(srtp, unprotected, mki_index \\ nil)

  def protect(ref(native), unprotected, nil) do
    Native.protect(native, :rtp, unprotected, false, 0)
  end

  def protect(ref(native), unprotected, mki_index) when is_integer(mki_index) do
    Native.protect(native, :rtp, unprotected, true, mki_index)
  end

  @spec protect_rtcp(t(), unprotected :: binary(), mki_index :: pos_integer() | nil) ::
          {:ok, protected :: binary()}
  def protect_rtcp(srtp, unprotected, mki_index \\ nil)

  def protect_rtcp(ref(native), unprotected, nil) do
    Native.protect(native, :rtcp, unprotected, false, 0)
  end

  def protect_rtcp(ref(native), unprotected, mki_index) when is_integer(mki_index) do
    Native.protect(native, :rtcp, unprotected, true, mki_index)
  end

  @spec unprotect(t(), protected :: binary(), use_mki :: boolean()) ::
          {:ok, unprotected :: binary()} | {:error, :auth_fail | :reply_fail | :bad_mki}
  def unprotect(ref(native) = _srtp, protected, use_mki \\ false) do
    Native.unprotect(native, :rtp, protected, use_mki)
  end

  @spec unprotect_rtcp(t(), protected :: binary(), use_mki :: boolean()) ::
          {:ok, unprotected :: binary()} | {:error, :auth_fail | :reply_fail | :bad_mki}
  def unprotect_rtcp(ref(native) = _srtp, protected, use_mki \\ false) do
    Native.unprotect(native, :rtcp, protected, use_mki)
  end
end