defmodule Borsh.Decode do require Logger @moduledoc """ The `Borsh.Decode` module provides functions for decoding data serialized using the Borsh format. It defines functions for each supported data type (e.g. `:string`, `:u8`, `:i64`) that can be used to decode a value of that type from a given bitstring. The primary function in this module is `borsh_decode`, which takes a bitstring and the name of a Borsh-serialized module as arguments. It retrieves the schema for the module and uses it to decode the data contained in the bitstring. The resulting data is returned as a struct of the module. ## Usage As an example let's use the following struct previuosly serialised into borsh bitstring. ```elixir defmodule ParentStruct do @type t() :: %__MODULE__{first_name: String.t(), last_name: String.t(), age: integer} defstruct [ :first_name, :last_name, :age ] use Borsh, schema: [ first_name: :string, last_name: :string, age: :u8 ] end ``` To use the `Borsh.Decode` module, you can pass a bitstring and the name of a Borsh-serialized struct to the `borsh_decode` function. For example: ```elixir bitstr = <<5, 0, 0, 0, 66, 111, 114, 105, 115, 7, 0, 0, 0, 74, 111, 104, 110, 115, 111, 110, 58>> Borsh.Decode.borsh_decode(bitstr, ParentStruct) %ParentStruct{first_name: "Boris", last_name: "Johnson", age: 58} ``` """ @doc """ Decodes objects according to the schema into the the struct """ @spec borsh_decode(bs :: bitstring(), borsh_module :: keyword) :: struct() def borsh_decode(bs, borsh_module) do borsh_schema = borsh_module.borsh_schema() {_, res_map} = Enum.map_reduce( borsh_schema, %{bits: bs, map: struct(borsh_module)}, fn {schema_field_name, schema_field_type} = schema_item, res_map -> {decoded_result, rest_bits} = decode(res_map.bits, schema_field_type) { schema_item, %{bits: rest_bits, map: Map.put(res_map.map, schema_field_name, decoded_result)} } end ) {res_map.map, res_map.bits} end # string defp decode(<>, format) when format in [:string, [32], [64]] do <> = rest_bits {str, rest_bits} end # unsigned 8-bit integer defp decode(<>, :u8) do {u8, rest_bits} end # unsigned 16-bit integer defp decode(<>, :u16) do {u16, rest_bits} end # unsigned 32-bit integer defp decode(<>, :u32) do {u32, rest_bits} end # unsigned 64-bit integer defp decode(<>, :u64) do {u64, rest_bits} end # unsigned 128-bit integer defp decode(<>, :u128) do {u128, rest_bits} end # signed 8-bit integer defp decode(<>, :i8) do {i8, rest_bits} end # signed 16-bit integer defp decode(<>, :i16) do {i16, rest_bits} end # signed 32-bit integer defp decode(<>, :i32) do {i32, rest_bits} end # signed 64-bit integer defp decode(<>, :i64) do {i64, rest_bits} end # signed 128-bit integer defp decode(<>, :i128) do {i128, rest_bits} end # borsh struct defp decode(bits, :borsh) do Logger.error("Cannot decode borsh_struct: #{inspect(bits, pretty: true, limit: 30000)}") raise "Cannot decode borsh_struct: #{inspect(bits, pretty: true, limit: 30000)}" end # borsh struct defp decode(bits, {:borsh, module}) do module.borsh_decode(bits) end # list of borsh structs of the same type defp decode(<>, [{:borsh, module}]) do {structs, rest_bits} = Enum.reduce(1..len, {[], bits}, fn _, {structs, rest} -> {struct, rest_bits} = decode(rest, {:borsh, module}) {[struct | structs], rest_bits} end) {Enum.reverse(structs), rest_bits} end # list of borsh structs of any type defp decode(bits, l, acc \\ []) defp decode(bits, [], acc), do: {Enum.reverse(acc), bits} defp decode(<<_len::little-integer-signed-size(32), bits::binary>>, [{:borsh, module} | t], []) do {struct, rest_bits} = decode(bits, {:borsh, module}) decode(rest_bits, t, [struct]) end defp decode(bits, [{:borsh, module} | t], acc) do {struct, rest_bits} = decode(bits, {:borsh, module}) decode(rest_bits, t, [struct | acc]) end end