defmodule Membrane.RTC.Engine.Endpoint.WebRTC.RTPMunger.Cache do @moduledoc false # Cache for RTPMunger # Stores information about mapping of sequence numbers from one space to another # It is a moving window with a size defined by difference of first and last sequence number present in the cache # !!! WARNING !!! # This module doesn't handle out-of-order entries, there isn't even a check as it's not meant to be used outside RTPMunger. # RTPMunger is responsible for ensuring that entries are added in appropriate order use Bunch.Access @max_seq_num 2 ** 16 # History size is determined by the window size of SRTP Encryptor, which happens to have to be # at least 64 @history_size 64 defstruct cache: Qex.new() @type t() :: %__MODULE__{ cache: Qex.t({original_seq_num :: non_neg_integer(), mapped_seq_num :: non_neg_integer()}) } @spec new() :: t() def new(), do: %__MODULE__{} @spec push(t(), non_neg_integer(), non_neg_integer()) :: t() def push(%__MODULE__{} = state, from, to) do new_state = Map.update!(state, :cache, &Qex.push(&1, {from, to})) if Enum.empty?(state.cache) do new_state else {_last, last} = Qex.last!(state.cache) remove_outdated_entries(new_state, last) end end @spec get_and_remove(t(), non_neg_integer()) :: {:ok, non_neg_integer(), t()} | {:error, :not_found} def get_and_remove(%__MODULE__{} = state, from) do state.cache |> Enum.split_while(fn {a, _b} -> a != from end) |> case do {head, [{^from, to} | rest]} -> cache = Qex.new(head ++ rest) state = Map.put(state, :cache, cache) {:ok, to, state} _otherwise -> {:error, :not_found} end end @spec remove_outdated_entries(t(), non_neg_integer()) :: t() def remove_outdated_entries(%__MODULE__{} = state, last_mapping) do window_size = get_window_size(state, last_mapping) if window_size >= @history_size do {_entry, cache} = Qex.pop(state.cache) remove_outdated_entries(%{state | cache: cache}, last_mapping) else state end end defp get_window_size(%__MODULE__{cache: cache} = _state, last_mapping) do case Qex.first(cache) do {:value, {_first, first_mapping}} -> rem(last_mapping - first_mapping + @max_seq_num, @max_seq_num) :empty -> 0 end end end