defmodule BitcoinLib.Signing.Psbt.CompactInteger do @moduledoc """ Extracts a variable length integer from a binary based on https://en.bitcoin.it/wiki/Protocol_documentation#Variable_length_integer """ defstruct [:value, :size, :remaining] alias BitcoinLib.Signing.Psbt.CompactInteger @_8_bits_code 0xFC @_16_bits_code 0xFD @_32_bits_code 0xFE @_64_bits_code 0xFF @max_8_bits_value @_8_bits_code @max_16_bits_value 0xFFFF @max_32_bits_value 0xFFFFFFFF @max_64_bits_value 0xFFFFFFFFFFFFFFFF # TODO: doctests @doc """ Encodes an integer according to the spec linked to in the moduledoc """ @spec encode(integer(), :big_endian | :little_endian) :: bitstring() def encode(value, endianness \\ :little_endian) def encode(value, _) when value <= @max_8_bits_value do <> end def encode(value, :little_endian) when value <= @max_16_bits_value do <<@_16_bits_code::8, value::little-16>> end def encode(value, :little_endian) when value <= @max_32_bits_value do <<@_32_bits_code::8, value::little-32>> end def encode(value, :little_endian) when value <= @max_64_bits_value do <<@_64_bits_code::8, value::little-64>> end def encode(value, :big_endian) when value <= @max_16_bits_value do <<0xFD::8, value::16>> end def encode(value, :big_endian) when value <= @max_32_bits_value do <<0xFE::8, value::32>> end def encode(value, :big_endian) when value <= @max_64_bits_value do <<0xFF::8, value::64>> end @doc """ Extracts a variable length integer from a bitstring according to the spec linked in the moduledoc, returns a tuple containing the integer with the remaining of the bitstring """ @spec extract_from(bitstring(), :big_endian | :little_endian) :: %CompactInteger{} def extract_from(data, endianness \\ :little_endian) def extract_from(<<@_16_bits_code::8, data::bitstring>>, endianness) do data |> extract_size(16, endianness) end def extract_from(<<@_32_bits_code::8, data::bitstring>>, endianness) do data |> extract_size(32, endianness) end def extract_from(<<@_64_bits_code::8, data::bitstring>>, endianness) do data |> extract_size(64, endianness) end def extract_from(<>, _endianness) do %CompactInteger{value: value, size: 8, remaining: remaining} end defp extract_size(data, length, :big_endian) do case data do <> -> %CompactInteger{value: value, size: length, remaining: <<>>} <> -> %CompactInteger{value: value, size: length, remaining: remaining} end end defp extract_size(data, length, :little_endian) do case data do <> -> %CompactInteger{value: value, size: length, remaining: <<>>} <> -> %CompactInteger{value: value, size: length, remaining: remaining} end end end