lib/cbor/decoder.ex

defmodule CBOR.Decoder do
  def decode(binary) do
    decode(binary, header(binary))
  end

  def decode(_binary, {mt, :indefinite, rest}) do
    case mt do
      2 -> mark_as_bytes(decode_string_indefinite(rest, 2, []))
      3 -> decode_string_indefinite(rest, 3, [])
      4 -> decode_array_indefinite(rest, [])
      5 -> decode_map_indefinite(rest, %{})
    end
  end

  def decode(bin, {mt, value, rest}) do
    case mt do
      0 -> {value, rest}
      1 -> {-value - 1, rest}
      2 -> mark_as_bytes(decode_string(rest, value))
      3 -> decode_string(rest, value)
      4 -> decode_array(value, rest)
      5 -> decode_map(value, rest)
      6 -> decode_other(value, decode(rest))
      7 -> decode_float(bin, value, rest)
    end
  end

  defp header(<<mt::size(3), val::size(5), rest::binary>>) when val < 24 do
    {mt, val, rest}
  end

  defp header(<<mt::size(3), 24::size(5), val::size(8), rest::binary>>) do
    {mt, val, rest}
  end

  defp header(<<mt::size(3), 25::size(5), val::size(16), rest::binary>>) do
    {mt, val, rest}
  end

  defp header(<<mt::size(3), 26::size(5), val::size(32), rest::binary>>) do
    {mt, val, rest}
  end

  defp header(<<mt::size(3), 27::size(5), val::size(64), rest::binary>>) do
    {mt, val, rest}
  end

  defp header(<<mt::size(3), 31::size(5), rest::binary>>) do
    {mt, :indefinite, rest}
  end

  defp decode_string(rest, len) do
    <<value::binary-size(len), new_rest::binary>> = rest
    {value, new_rest}
  end

  defp decode_string_indefinite(rest, actmt, acc) do
    case header(rest) do
      {7, :indefinite, new_rest} ->
        {Enum.join(Enum.reverse(acc)), new_rest}

      {^actmt, len, mid_rest} ->
        <<value::binary-size(len), new_rest::binary>> = mid_rest
        decode_string_indefinite(new_rest, actmt, [value | acc])
    end
  end

  defp decode_array(0, rest), do: {[], rest}
  defp decode_array(len, rest), do: decode_array(len, [], rest)
  defp decode_array(0, acc, bin), do: {Enum.reverse(acc), bin}
  defp decode_array(len, acc, bin) do
    {value, bin_rest} = decode(bin)
    decode_array(len - 1, [value|acc], bin_rest)
  end

  defp decode_array_indefinite(<<0xff, new_rest::binary>>, acc) do
    {Enum.reverse(acc), new_rest}
  end

  defp decode_array_indefinite(rest, acc) do
    {value, new_rest} = decode(rest)
    decode_array_indefinite(new_rest, [value | acc])
  end

  defp decode_map(0, rest), do: {%{}, rest}
  defp decode_map(len, rest), do: decode_map(len, %{}, rest)
  defp decode_map(0, acc, bin), do: {acc, bin}
  defp decode_map(len, acc, bin) do
    {key, key_rest} = decode(bin)
    {value, bin_rest} = decode(key_rest)

    decode_map(len - 1, Map.put(acc, key, value), bin_rest)
  end

  defp decode_map_indefinite(<<0xff, new_rest::binary>>, acc), do: {acc, new_rest}
  defp decode_map_indefinite(rest, acc) do
    {key, key_rest} = decode(rest)
    {value, new_rest} = decode(key_rest)
    decode_map_indefinite(new_rest, Map.put(acc, key, value))
  end

  defp decode_float(bin, value, rest) do
    case bin do
      <<0xf4, _::binary>> -> {false, rest}
      <<0xf5, _::binary>> -> {true, rest}
      <<0xf6, _::binary>> -> {nil, rest}
      <<0xf7, _::binary>> -> {:__undefined__, rest}

      <<0xf9, sign::size(1), exp::size(5), mant::size(10), _::binary>> ->
        {decode_half(sign, exp, mant), rest}

      <<0xfa, value::float-size(32), _::binary>> ->
         {value, rest}

      <<0xfa, sign::size(1), 255::size(8), mant::size(23), _::binary>> ->
        {decode_non_finite(sign, mant), rest}

      <<0xfb, value::float, _::binary >> ->
        {value, rest}

      <<0xfb, sign::size(1), 2047::size(11), mant::size(52), _::binary>> ->
        {decode_non_finite(sign, mant), rest}

      _ -> {%CBOR.Tag{tag: :simple, value: value}, rest}
    end
  end

  defp decode_other(value, {inner, rest}), do: {decode_tag(value, inner), rest}

  def decode_non_finite(0, 0), do: %CBOR.Tag{tag: :float, value: :inf}
  def decode_non_finite(1, 0), do: %CBOR.Tag{tag: :float, value: :"-inf"}
  def decode_non_finite(_, _), do: %CBOR.Tag{tag: :float, value: :nan}

  defp decode_half(sign, 31, mant), do: decode_non_finite(sign, mant)

  # 2**112 -- difference in bias
  defp decode_half(sign, exp, mant) do
    <<value::float-size(32)>> = <<sign::size(1), exp::size(8), mant::size(10), 0::size(13)>>
    value * 5192296858534827628530496329220096.0
  end

  defp decode_tag(0, value), do: decode_datetime(value)

  defp decode_tag(3, value), do: -decode_tag(2, value) - 1

  defp decode_tag(2, value) do
    case value do
      %CBOR.Tag{tag: :bytes, value: bytes} when is_binary(bytes) ->
        size = byte_size(bytes)
        <<res::unsigned-integer-size(size)-unit(8)>> = bytes
        res

      bytes when is_binary(bytes) ->
        size = byte_size(bytes)
        <<res::unsigned-integer-size(size)-unit(8)>> = bytes
        res
    end
  end

  defp decode_tag(32, value), do: URI.parse(value)
  defp decode_tag(tag, value), do: %CBOR.Tag{tag: tag, value: value}

  defp mark_as_bytes({x, rest}), do: {%CBOR.Tag{tag: :bytes, value: x}, rest}

  defp decode_datetime(value) do
    case DateTime.from_iso8601(value) do
      {:ok, datetime, _offset} -> datetime
      {:error, _reason} -> decode_date(value)
    end
  end

  defp decode_date(value) do
    case Date.from_iso8601(value) do
      {:ok, date} -> date
      {:error, _reason} -> decode_time(value)
    end
  end

  defp decode_time(value) do
    case Time.from_iso8601(value) do
      {:ok, time} -> time
      {:error, _reason} -> %CBOR.Tag{tag: 0, value: value}
    end
  end
end