diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2261097..a26d7e3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -66,9 +66,9 @@ jobs: - uses: actions/cache@v1 with: path: priv/plts - key: ${{ runner.os }}-plt-v1-${{ env.MIX_ENV }} + key: ${{ runner.os }}-plt-v2-${{ env.MIX_ENV }} restore-keys: | - ${{ runner.os }}-plt-v1 + ${{ runner.os }}-plt-v2 - name: Dialyzer run: mix dialyzer --format short diff --git a/README.md b/README.md index bc1d0ee..1863bfd 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,9 @@ iex> MyBlock.decode(bits) %MyBlock{header: "begin", leftover: "", s1: 3, s2: -3, tail: "end"} ``` +> See `Bitcraft.BitBlock.defblock/3` and `Bitcraft.BitBlock.segment/3` + for more information. + ### Working with dynamic blocks For this example let's define an IPv4 datagram, which has a dynamic part: @@ -199,6 +202,66 @@ iex> IpDatagram.decode(bits, :erlang.bit_size(bits), &IpDatagram.calc_size/3) } ``` +## Arrays + +Sometimes we may also want to parse a segment of bits as an array, for example, +suppose we have a dynamic segment but we want to parse it as a list of integers +of 16 bits, what variates is the amount of them (the length of the array). +So, if the size of the segment is calculated as 64 bits, we expect an array +of 4 integers of 16 bits. + +First of all, let us define a block with an array-type segment: + +```elixir +defmodule TestBlock do + import Bitcraft.BitBlock + + defblock "test-block" do + segment(:a, 8) + segment(:b, 8) + array(:list, type: :integer, element_size: 16, sign: :signed) + end + + # Size resolver for dynamic segments invoked during the decoding + def calc_size(%__MODULE__{a: a, b: b}, :list, acc) do + {a + b, acc} + end +end +``` + +As you may notice, the field `:list` is defined as array-type in the form: + +```elixir +array(:list, type: :integer, element_size: 16, sign: :signed) +``` + +The first argument is the name of the segment, then we pass the options. +The `type: :integer` defines the the type for the array elements, and +`element_size: 16` defines that each element of the array must have +16 bits size. The rest of the options are the same and apply to the +array elements. + +> See `Bitcraft.BitBlock.array/2` and `Bitcraft.BitBlock.segment/3`. + +Now, let's try it out: + +```elixir +iex> data = %TestBlock{ +...> a: 32, +...> b: 32, +...> list: %Bitcraft.BitBlock.DynamicSegment{value: [1, 2, 3, 4], size: 64} +} +iex> encoded = TestBlock.encode(data) +<<32, 32, 0, 1, 0, 2, 0, 3, 0, 4>> +iex> TestBlock.decode(encoded, %{}, &TestBlock.calc_size/3) +%TestBlock{ + a: 32, + b: 32, + leftover: "", + list: %Bitcraft.BitBlock.DynamicSegment{size: 64, value: [1, 2, 3, 4]} +} +``` + ## Contributing Contributions to Bitcraft are very welcome and appreciated! diff --git a/coveralls.json b/coveralls.json index 64a72cb..c7cab4c 100644 --- a/coveralls.json +++ b/coveralls.json @@ -6,6 +6,7 @@ "skip_files": [ "lib/bitcraft/helpers.ex", + "lib/bitcraft/bit_block.ex", "test/support/*" ] } diff --git a/lib/bitcraft.ex b/lib/bitcraft.ex index 50f93d8..5627b47 100644 --- a/lib/bitcraft.ex +++ b/lib/bitcraft.ex @@ -6,13 +6,68 @@ defmodule Bitcraft do use Bitwise use Bitcraft.Helpers - @typedoc "Codable data types" - @type bit_data :: integer | float | binary | bitstring | byte | char + # Base data types for binaries + @type base_type :: + integer + | float + | binary + | bitstring + | byte + | char + + @typedoc "Segment type" + @type segment_type :: base_type | Bitcraft.BitBlock.Array.t() + + @typedoc "Codable segment type" + @type codable_segment_type :: base_type | [base_type] ## API - @spec encode_bits(bit_data, Keyword.t()) :: bitstring - def encode_bits(input, opts \\ []) do + @doc """ + Encodes the given `input` into a bitstring. + + ## Options + + * `:size` - The size in bits for the input to encode. The default + value depend on the type, for integer is 8, for float is 63, and for + other data types is `nil`. If the `input` is a list, this option is + skipped, since it is handled as array and the size will be + `array_length * element_size`. + + * `:type` - The segment type given by `Bitcraft.segment_type()`. + Defaults to `:integer`. + + * `:sign` - If the input is an integer, defines if it is `:signed` + or `:unsigned`. Defaults to `:unsigned`. + + * `:endian` - Applies to `utf32`, `utf16`, `float`, `integer`. + Defines the endianness, `:big` or `:little`. Defaults to `:big`. + + ## Example + + iex> Bitcraft.encode_segment(15) + <<15>> + + iex> Bitcraft.encode_segment(255, size: 4) + <<15::size(4)>> + + iex> Bitcraft.encode_segment(-3.3, size: 64, type: :float) + <<192, 10, 102, 102, 102, 102, 102, 102>> + + iex> Bitcraft.encode_segment("hello", type: :binary) + "hello" + + iex> Bitcraft.encode_segment(<<1, 2, 3>>, type: :bits) + <<1, 2, 3>> + + iex> Bitcraft.encode_segment([1, -2, 3], type: %Bitcraft.BitBlock.Array{ + ...> type: :integer, element_size: 4}, + ...> sign: :signed + ...> ) + <<30, 3::size(4)>> + """ + @spec encode_segment(codable_segment_type, Keyword.t()) :: bitstring + def encode_segment(input, opts \\ []) do type = Keyword.get(opts, :type, :integer) sign = Keyword.get(opts, :sign, :unsigned) endian = Keyword.get(opts, :endian, :big) @@ -25,9 +80,69 @@ defmodule Bitcraft do true -> size end - encode_bits(input, size, type, sign, endian) + encode_segment(input, size, type, sign, endian) end + @doc """ + Returns a tuple `{decoded value, leftover}` where the first element is the + decoded value from the given `input` (according to the given `otps` too) + and the second element is the leftover. + + ## Options + + * `:size` - The size in bits to decode. Defaults to `byte_size(input) * 8`. + If the type is `Bitcraft.BitBlock.Array.()`, the size should match with + `array_length * element_size`. + + * `:type` - The segment type given by `Bitcraft.segment_type()`. + Defaults to `:integer`. + + * `:sign` - If the input is an integer, defines if it is `:signed` + or `:unsigned`. Defaults to `:unsigned`. + + * `:endian` - Applies to `utf32`, `utf16`, `float`, `integer`. + Defines the endianness, `:big` or `:little`. Defaults to `:big`. + + ## Example + + iex> 3 + ...> |> Bitcraft.encode_segment(size: 4) + ...> |> Bitcraft.decode_segment(size: 4) + {3, ""} + + iex> -3.3 + ...> |> Bitcraft.encode_segment(size: 64, type: :float, sign: :signed) + ...> |> Bitcraft.decode_segment(size: 64, type: :float, sign: :signed) + {-3.3, ""} + + iex> "test" + ...> |> Bitcraft.encode_segment(type: :binary) + ...> |> Bitcraft.decode_segment(size: 4, type: :binary) + {"test", ""} + + iex> <<1, 2, 3, 4>> + ...> |> Bitcraft.encode_segment(type: :bits) + ...> |> Bitcraft.decode_segment(size: 32, type: :bits) + {<<1, 2, 3, 4>>, ""} + + iex> alias Bitcraft.BitBlock.Array + iex> [1, 2] + ...> |> Bitcraft.encode_segment(type: %Array{}) + ...> |> Bitcraft.decode_segment(size: 16, type: %Array{}) + {[1, 2], ""} + iex> [3.3, -7.7, 9.9] + ...> |> Bitcraft.encode_segment( + ...> type: %Array{type: :float, element_size: 64}, + ...> sign: :signed + ...> ) + ...> |> Bitcraft.decode_segment( + ...> size: 192, + ...> type: %Array{type: :float, element_size: 64}, + ...> sign: :signed + ...> ) + {[3.3, -7.7, 9.9], ""} + """ + @spec decode_segment(bitstring, Keyword.t()) :: {codable_segment_type, bitstring} def decode_segment(input, opts \\ []) do type = Keyword.get(opts, :type, :integer) sign = Keyword.get(opts, :sign, :unsigned) @@ -37,6 +152,17 @@ defmodule Bitcraft do decode_segment(input, size, type, sign, endian) end + @doc """ + Returns the number of `1`s in binary representation of the given `integer`. + + ## Example + + iex> Bitcraft.count_ones(15) + 4 + + iex> Bitcraft.count_ones(255) + 8 + """ @spec count_ones(integer) :: integer def count_ones(integer) when is_integer(integer) do count_ones(integer, 0) diff --git a/lib/bitcraft/bit_block.ex b/lib/bitcraft/bit_block.ex index 5beb447..b5da082 100644 --- a/lib/bitcraft/bit_block.ex +++ b/lib/bitcraft/bit_block.ex @@ -143,20 +143,18 @@ defmodule Bitcraft.BitBlock do ## Working with typespecs - When defining a block with `defblock/3`, the struct is generated along with - the typespec `t/0` for it. - """ + By default, the typespec `t/0` is generated but in the simplest form: - defmodule DynamicSegment do - @moduledoc """ - Defines a dynamic segment within a bit-block. - """ + @type t :: %__MODULE__{} - @enforce_keys [:value, :size] - defstruct [:value, :size] + If you want to provide a more accurate typespec for you block adding the + typespecs for each of the segments on it, you can set the option `:typespec` + to `false` when defining the block, like so: - @type t :: %__MODULE__{value: term, size: non_neg_integer} - end + defblock "my-block", typespec: false do + ... + end + """ import Record @@ -222,12 +220,12 @@ defmodule Bitcraft.BitBlock do iex> bits = MyBlock.encode(block) iex> MyBlock.decode(bits) """ - @callback decode(input :: bitstring, acc :: term, dynamic_size_resolver) :: t + @callback decode(input :: bitstring, acc :: term, dynamic_size_resolver) :: term ## API alias __MODULE__ - alias __MODULE__.DynamicSegment + alias __MODULE__.{Array, DynamicSegment} @doc """ Defines a bit-block struct with a name and segment definitions. @@ -249,17 +247,12 @@ defmodule Bitcraft.BitBlock do segments = Module.get_attribute(__MODULE__, :block_segments, []) - {struct_segments, type_segments} = + struct_segments = Enum.reduce( segments ++ [block_segment(name: :leftover, default: <<>>)], - {[], []}, - fn block_segment( - name: name, - type: type, - default: default - ), - {acc1, acc2} -> - {[{name, default} | acc1], [{name, BitBlock.typespec(type)} | acc2]} + [], + fn block_segment(name: name, type: type, default: default), acc -> + [{name, default} | acc] end ) @@ -267,10 +260,10 @@ defmodule Bitcraft.BitBlock do @enforce_keys Keyword.get(opts, :enforce_keys, []) defstruct struct_segments - # define type - @type t :: %__MODULE__{ - unquote_splicing(type_segments) - } + # maybe define default data type + if Keyword.get(opts, :typespec, true) == true do + @type t :: %__MODULE__{} + end # build encoding expressions for encode/decode functions {bit_expr, map_expr} = BitBlock.build_encoding_exprs(segments, "", "") @@ -278,9 +271,7 @@ defmodule Bitcraft.BitBlock do ## Encoding Functions @doc false - def decode(bits, acc \\ nil, fun \\ nil) - - def decode(unquote(bit_expr), _, nil) do + def decode(unquote(bit_expr)) do Map.put(unquote(map_expr), :__struct__, __MODULE__) end @@ -381,7 +372,7 @@ defmodule Bitcraft.BitBlock do block_segment(name: name, size: :dynamic, type: type, sign: sign, endian: endian), acc -> case Map.fetch!(data, name) do %DynamicSegment{value: value, size: size} -> - value = Bitcraft.encode_bits(value, size, type, sign, endian) + value = Bitcraft.encode_segment(value, size, type, sign, endian) <> nil -> @@ -397,7 +388,7 @@ defmodule Bitcraft.BitBlock do value = data |> Map.fetch!(name) - |> Bitcraft.encode_bits(size, type, sign, endian) + |> Bitcraft.encode_segment(size, type, sign, endian) <> end) @@ -432,6 +423,42 @@ defmodule Bitcraft.BitBlock do end end + @doc """ + Same as `segment/3`, but automatically generates a **dynamic** + segment with the type `Bitcraft.BitBlock.Array.t()`. + + The size of the array-type segment in bits has to be calculated + dynamically during the decoding, and the length of the array will + be `segment_size/element_size`. This process is performs automatically + during the decoding. hence, it is important to set the right + `element_size` and also implement properly the callback to calculate + the segment size. See `Bitcraft.BitBlock.dynamic_size_resolver/3`. + + ## Options + + Options are the same as `segment/3`, and additionally: + + * `:element_size` - The size in bits of each array element. + Defaults to `8`. + + **NOTE:** The `:type` is the same as `segment/3` BUT it applies to the + array element. + """ + defmacro array(name, opts \\ []) do + {type, opts} = Keyword.pop(opts, :type, :integer) + {size, opts} = Keyword.pop(opts, :element_size, 8) + opts = [type: %Array{type: type, element_size: size}] ++ opts + + quote do + BitBlock.__segment__( + __MODULE__, + unquote(name), + :dynamic, + unquote(Macro.escape(opts)) + ) + end + end + @doc """ This is a helper function used internally for building a block segment. """ @@ -500,15 +527,6 @@ defmodule Bitcraft.BitBlock do build_encoding_exprs(segments, bin, map <> "#{name}: #{inspect(default)}, ") end - @doc """ - Helper to resolve the type for a block segment. - """ - @spec typespec(seg_type | nil) :: atom - def typespec(nil), do: :integer - def typespec(type) when type in [:binary, :bytes, :utf8, :utf16, :utf32], do: :binary - def typespec(type) when type in [:bitstring, :bits], do: :bitstring - def typespec(type) when type in [:integer, :float], do: type - ## Private # Helper function used internally for building bitstring modifier. diff --git a/lib/bitcraft/bit_block/array.ex b/lib/bitcraft/bit_block/array.ex new file mode 100644 index 0000000..f5ce7c4 --- /dev/null +++ b/lib/bitcraft/bit_block/array.ex @@ -0,0 +1,12 @@ +defmodule Bitcraft.BitBlock.Array do + @moduledoc """ + Defines the type array. + """ + + defstruct type: :integer, element_size: 8 + + @type t :: %__MODULE__{ + type: Bitcraft.BitBlock.seg_type(), + element_size: non_neg_integer + } +end diff --git a/lib/bitcraft/bit_block/dynamic_segment.ex b/lib/bitcraft/bit_block/dynamic_segment.ex new file mode 100644 index 0000000..418c740 --- /dev/null +++ b/lib/bitcraft/bit_block/dynamic_segment.ex @@ -0,0 +1,10 @@ +defmodule Bitcraft.BitBlock.DynamicSegment do + @moduledoc """ + Defines a dynamic segment within a bit-block. + """ + + @enforce_keys [:value, :size] + defstruct [:value, :size] + + @type t :: %__MODULE__{value: term, size: non_neg_integer} +end diff --git a/lib/bitcraft/helpers.ex b/lib/bitcraft/helpers.ex index 1a39605..ed41c3d 100644 --- a/lib/bitcraft/helpers.ex +++ b/lib/bitcraft/helpers.ex @@ -24,9 +24,10 @@ defmodule Bitcraft.Helpers do float_exprs = float_exprs(endian_opts) bin_exprs = bin_exprs() unicode_exprs = unicode_exprs(endian_opts) + array_exprs = array_exprs() {dec_exprs, enc_exprs} = - [int_exprs, float_exprs, bin_exprs, unicode_exprs] + [int_exprs, float_exprs, array_exprs, bin_exprs, unicode_exprs] |> List.flatten() |> Enum.unzip() @@ -51,7 +52,7 @@ defmodule Bitcraft.Helpers do """ enc = """ - def encode_bits(var, size, :integer, :#{sign}, :#{endian}) do + def encode_segment(var, size, :integer, :#{sign}, :#{endian}) do <> end """ @@ -70,7 +71,7 @@ defmodule Bitcraft.Helpers do """ enc = """ - def encode_bits(var, size, :float, _, :#{endian}) do + def encode_segment(var, size, :float, _, :#{endian}) do <> end """ @@ -89,7 +90,7 @@ defmodule Bitcraft.Helpers do """ enc = """ - def encode_bits(var, _, :#{type}, _, _) do + def encode_segment(var, _, :#{type}, _, _) do <> end """ @@ -126,7 +127,7 @@ defmodule Bitcraft.Helpers do end enc = """ - def encode_bits(var, _, :#{type}, _, :#{endian}) do + def encode_segment(var, _, :#{type}, _, :#{endian}) do if is_integer(var) do <> else @@ -138,4 +139,28 @@ defmodule Bitcraft.Helpers do {dec, enc} end end + + defp array_exprs do + dec = """ + def decode_segment(bits, sz, %Bitcraft.BitBlock.Array{type: t, element_size: esz}, sign, endian) do + {array, rest} = + Enum.reduce(1..(div(sz, esz)), {[], bits}, fn _, {lst, bin} -> + {value, bin} = Bitcraft.decode_segment(bin, esz, t, sign, endian) + {[value | lst], bin} + end) + + {Enum.reverse(array), rest} + end + """ + + enc = """ + def encode_segment(array, _, %Bitcraft.BitBlock.Array{type: t, element_size: s}, sign, endian) do + for elem <- array, into: <<>> do + encode_segment(elem, s, t, sign, endian) + end + end + """ + + {dec, enc} + end end diff --git a/test/bitcraft/bit_block_test.exs b/test/bitcraft/bit_block_test.exs index 0a21171..1a184ee 100644 --- a/test/bitcraft/bit_block_test.exs +++ b/test/bitcraft/bit_block_test.exs @@ -33,8 +33,8 @@ defmodule Bitcraft.BitBlockTest do b: 5, c: -10_000, tail: 120, - d: %DynamicSegment{size: 8, value: 128}, - e: %DynamicSegment{size: 16, value: -536}, + d: %DynamicSegment{size: 4, value: 2}, + e: %DynamicSegment{size: 16, value: [1, -1, 2, -2]}, leftover: "", extra: nil } diff --git a/test/bitcraft_test.exs b/test/bitcraft_test.exs index ccecacc..4a84824 100644 --- a/test/bitcraft_test.exs +++ b/test/bitcraft_test.exs @@ -3,65 +3,100 @@ defmodule BitcraftTest do use ExUnit.Case doctest Bitcraft - describe "encode_bits/2 & decode_segment/2" do + alias Bitcraft.BitBlock.Array + + describe "encode_segment/2 & decode_segment/2" do test "integers" do - assert 5 |> Bitcraft.encode_bits() |> Bitcraft.decode_segment() == {5, ""} - assert 3 |> Bitcraft.encode_bits(size: 4) |> Bitcraft.decode_segment(size: 4) == {3, ""} + assert 5 |> Bitcraft.encode_segment() |> Bitcraft.decode_segment() == {5, ""} + assert 3 |> Bitcraft.encode_segment(size: 4) |> Bitcraft.decode_segment(size: 4) == {3, ""} assert -3 - |> Bitcraft.encode_bits(size: 4, sign: :signed) + |> Bitcraft.encode_segment(size: 4, sign: :signed) |> Bitcraft.decode_segment(size: 4, sign: :signed) == {-3, ""} assert -3 - |> Bitcraft.encode_bits(size: 4, sign: :signed, endian: :little) + |> Bitcraft.encode_segment(size: 4, sign: :signed, endian: :little) |> Bitcraft.decode_segment(size: 4, sign: :signed, endian: :little) == {-3, ""} end test "floats" do - assert 5.5 |> Bitcraft.encode_bits(type: :float) |> Bitcraft.decode_segment(type: :float) == - {5.5, ""} + assert 5.5 + |> Bitcraft.encode_segment(type: :float) + |> Bitcraft.decode_segment(type: :float) == {5.5, ""} assert 3.3 - |> Bitcraft.encode_bits(size: 64, type: :float) + |> Bitcraft.encode_segment(size: 64, type: :float) |> Bitcraft.decode_segment(size: 64, type: :float) == {3.3, ""} assert -3.3 - |> Bitcraft.encode_bits(size: 64, type: :float, sign: :signed) + |> Bitcraft.encode_segment(size: 64, type: :float, sign: :signed) |> Bitcraft.decode_segment(size: 64, type: :float, sign: :signed) == {-3.3, ""} assert -3.3 - |> Bitcraft.encode_bits(size: 64, type: :float, sign: :signed, endian: :little) + |> Bitcraft.encode_segment(size: 64, type: :float, sign: :signed, endian: :little) |> Bitcraft.decode_segment(size: 64, type: :float, sign: :signed, endian: :little) == {-3.3, ""} end test "binaries/bits" do assert "test" - |> Bitcraft.encode_bits(type: :binary) + |> Bitcraft.encode_segment(type: :binary) |> Bitcraft.decode_segment(size: 4, type: :binary) == {"test", ""} assert <<1, 2, 3, 4>> - |> Bitcraft.encode_bits(type: :bits) - |> Bitcraft.decode_segment(size: 4, type: :binary) == {<<1, 2, 3, 4>>, ""} + |> Bitcraft.encode_segment(type: :bits) + |> Bitcraft.decode_segment(size: 32, type: :bits) == {<<1, 2, 3, 4>>, ""} end test "utf8/utf16/utf32" do assert ?x - |> Bitcraft.encode_bits(type: :utf8) + |> Bitcraft.encode_segment(type: :utf8) |> Bitcraft.decode_segment(type: :utf8) == {"x", ""} assert "x" - |> Bitcraft.encode_bits(type: :utf8) + |> Bitcraft.encode_segment(type: :utf8) |> Bitcraft.decode_segment(type: :utf8) == {"x", ""} assert "foo" - |> Bitcraft.encode_bits(type: :utf16) + |> Bitcraft.encode_segment(type: :utf16) |> Bitcraft.decode_segment(type: :utf16) == {"foo", ""} assert "foo" - |> Bitcraft.encode_bits(type: :utf32) + |> Bitcraft.encode_segment(type: :utf32) |> Bitcraft.decode_segment(type: :utf32) == {"foo", ""} end + + test "array" do + assert [1, 2, 3] + |> Bitcraft.encode_segment(type: %Array{}) + |> Bitcraft.decode_segment(size: 24, type: %Array{}) == {[1, 2, 3], ""} + + assert [250, 252, 255] + |> Bitcraft.encode_segment(type: %Array{element_size: 4}) + |> Bitcraft.decode_segment(size: 12, type: %Array{element_size: 4}) == + {[10, 12, 15], ""} + + assert [3.3, -7.7, 9.9] + |> Bitcraft.encode_segment( + type: %Array{type: :float, element_size: 64}, + sign: :signed + ) + |> Bitcraft.decode_segment( + size: 192, + type: %Array{type: :float, element_size: 64}, + sign: :signed + ) == {[3.3, -7.7, 9.9], ""} + + assert ["hello", "world"] + |> Bitcraft.encode_segment(type: %Array{type: :binary, element_size: 5}) + |> Bitcraft.decode_segment(size: 10, type: %Array{type: :binary, element_size: 5}) == + {["hello", "world"], ""} + + assert [<<1, 2, 3>>, <<4, 5, 6>>] + |> Bitcraft.encode_segment(type: %Array{type: :bits, element_size: 24}) + |> Bitcraft.decode_segment(size: 48, type: %Array{type: :bits, element_size: 24}) == + {[<<1, 2, 3>>, <<4, 5, 6>>], ""} + end end describe "count_ones/1" do diff --git a/test/support/test_blocks.ex b/test/support/test_blocks.ex index 2d20f30..fe32273 100644 --- a/test/support/test_blocks.ex +++ b/test/support/test_blocks.ex @@ -30,44 +30,43 @@ defmodule Bitcraft.TestBlocks do alias Bitcraft.BitBlock.DynamicSegment - defblock "test-block-1" do + @type t :: %__MODULE__{} + + defblock "test-block-1", typespec: false do segment(:header, 4, type: :binary) segment(:a, 4, default: 1) segment(:b, 8, default: 1) segment(:c, 16, default: 1, sign: :signed) segment(:tail, 8, type: :utf8) segment(:d, :dynamic) - segment(:e, :dynamic, sign: :signed) + array(:e, type: :integer, element_size: 4, sign: :signed) segment(:extra) end - def callback(%__MODULE__{a: a, b: b}, :d, _acc) do - offset = Bitcraft.count_ones(a * b) * 2 - {offset, %{offset: offset}} + def callback(%__MODULE__{a: a, b: b}, :d, acc) do + d_size = Bitcraft.count_ones(a * b) + {d_size, Map.put(acc, :nd, d_size)} end - def callback(%__MODULE__{}, :e, %{offset: offset} = acc) do - {offset * 2, acc} + def callback(%__MODULE__{}, :e, %{nd: nd} = acc) do + {nd * 4, acc} end def sample do - s1 = Bitcraft.count_ones(15) * 2 - s2 = s1 * 2 - %__MODULE__{ header: "test", a: 3, b: 5, c: -10_000, + tail: ?x, d: %DynamicSegment{ - value: 128, - size: s1 + value: 2, + size: 4 }, e: %DynamicSegment{ - value: 65_000, - size: s2 - }, - tail: ?x + value: [1, -1, 2, -2], + size: 16 + } } end end