defmodule Equivalex do use Bitwise @moduledoc """ polymorphic constant time comparisons Compare arbitrary structures, using constant time comparisons where possible. This might allow for checking of multiple strings in a "secure" (constant time) way, without leaking timing information. """ @doc """ determine if two structures are the same Comprisons are done bitwise on the destructured data. The only short-circuits are for structures of differing sizes. Thus, information about whether a suspect string is of the correct length may be leaked. """ @spec equal?(term,term) :: boolean def equal?(a,b), do: differing_elements(a,b,0) == 0 defp differing_elements(a,b,_c) when is_map(a) and is_map(b) do differing_elements(Map.to_list(a) |> Enum.sort, Map.to_list(b) |> Enum.sort, 0) end defp differing_elements(a,b,_c) when is_tuple(a) and is_tuple(b) do differing_elements(Tuple.to_list(a), Tuple.to_list(b), 0) end defp differing_elements([],[],c), do: c defp differing_elements([a|ra],[b|rb],c), do: differing_elements(ra, rb, c ||| comparison(a,b)) defp differing_elements(<<>>, <<>>, c), do: c defp differing_elements(<>, <>, c) do differing_elements(ra, rb, c ||| comparison(a,b)) end defp differing_elements(_a,_b,_c), do: 1 defp comparison(a,b) when (is_list(a) and is_list(b)) or (is_tuple(a) and is_tuple(b)) or (is_map(a) and is_map(b)) or (is_binary(a) and is_binary(b)), do: differing_elements(a,b,0) defp comparison(a,b), do: if a == b, do: 0, else: 1 end