diff --git a/lib/crc/pure.ex b/lib/crc/pure.ex new file mode 100644 index 0000000..c746d46 --- /dev/null +++ b/lib/crc/pure.ex @@ -0,0 +1,609 @@ +defmodule CRC.Pure do + @moduledoc """ + Pure Elixir CRC calculation module. + + Calculates CRC (Cyclic Redundancy Check) checksums for binary data using + a bit-by-bit algorithm implemented entirely in Elixir with no native + dependencies. Supports all standard CRC models (3-bit through 64-bit) + as well as custom model definitions and the SICK variant. + + ## Usage + + Calculate a CRC using a built-in model: + + iex> CRC.Pure.calculate("123456789", :crc_32) + 0xCBF43926 + + Pipe-friendly — data flows left to right: + + read_data() |> CRC.Pure.calculate(:crc_16) |> do_something() + + Use the multi-part API for streaming or incremental calculation: + + iex> resource = CRC.Pure.init(:crc_16) + iex> resource = CRC.Pure.update(resource, "1234") + iex> resource = CRC.Pure.update(resource, "56789") + iex> CRC.Pure.final(resource) + 0xBB3D + + Define a custom model at runtime: + + CRC.Pure.calculate("123456789", %{ + width: 16, + poly: 0x1021, + init: 0xFFFF, + refin: false, + refout: false, + xorout: 0x0000 + }) + + Extend a built-in model with overrides: + + CRC.Pure.calculate("123456789", %{extend: :crc_16_ccitt_false, init: 0x1D0F}) + + ## Models + + Use `list/0` to get all available model keys. Model parameters follow the + Rocksoft Model CRC Algorithm specification with the addition of the `sick` + flag for the non-standard SICK sensor variant. + """ + + @behaviour :crc_algorithm + + import Bitwise + + alias CRC.Pure.Models + + @doc """ + Calculates a CRC checksum for `input` using the given `params`. + + This is the pipe-friendly version with `input` as the first argument. + See `calc/2` for details on valid `params`. + + ## Examples + + iex> CRC.Pure.calculate("123456789", :crc_32) + 0xCBF43926 + + iex> CRC.Pure.calculate("123456789", %{extend: :crc_16_ccitt_false, init: 0x1D0F}) + 0xE5CC + """ + def calculate(input, params) do + calc(params, input) + end + + @doc """ + Calculates a CRC checksum for `iodata` using the given `params`. + + `params` can be: + + * an atom — a built-in model key (e.g., `:crc_32`, `:crc_16_usb`) + * a map — with keys `:width`, `:poly`, `:init`, `:refin`, `:refout`, `:xorout` + and optionally `:check`, `:residue`, `:sick` + * a map with `:extend` — extends a built-in model with overrides + + Returns the CRC value as a non-negative integer. + + Prefer `calculate/2` for pipeline usage — this function exists to satisfy + the `:crc_algorithm` behaviour callback. + + ## Examples + + iex> CRC.Pure.calc(:crc_32, "123456789") + 0xCBF43926 + """ + @impl true + def calc(params, iodata) do + resource = init(params) + resource = update(resource, iodata) + final(resource) + end + + @doc """ + Initializes a CRC calculation resource. + + `params` accepts the same types as `calc/2`: an atom for a built-in model, + a map with full model parameters, or a map with `:extend` to override a + built-in model. + + Returns a resource that can be passed to `update/2` and `final/1` for + incremental CRC calculation, or to `info/1` and `residue/1` for inspection. + + ## Examples + + iex> resource = CRC.Pure.init(:crc_32) + iex> resource = CRC.Pure.update(resource, "123456789") + iex> CRC.Pure.final(resource) + 0xCBF43926 + """ + @impl true + def init(key) when is_atom(key) do + case Models.get(key) do + nil -> :erlang.error({:badarg, [key]}) + model -> init(model) + end + end + + def init(%{extend: base_key} = params) when is_atom(base_key) do + case Models.get(base_key) do + nil -> + :erlang.error({:badarg, [params]}) + + base_model -> + merged = Map.merge(base_model, Map.delete(params, :extend)) + init(merged) + end + end + + def init( + %{ + width: width, + poly: poly, + init: init_val, + refin: refin, + refout: refout, + xorout: xorout + } = params + ) + when is_boolean(refin) and is_boolean(refout) do + validate_non_neg_integers!(params, width, poly, init_val, xorout) + + check = Map.get(params, :check, 0) + residue = Map.get(params, :residue, 0) + sick = Map.get(params, :sick, false) + + masks = compute_masks(width) + validate_params!(params, masks, check, residue) + + build_resource(params, masks, check, residue, sick) + end + + def init(bad_params) do + :erlang.error({:badarg, [bad_params]}) + end + + defp compute_masks(width) do + msb_mask = 1 <<< (width - 1) + crc_mask = 1 ||| (msb_mask - 1) <<< 1 + crc_shift = if width < 8, do: 8 - width, else: 0 + {msb_mask, crc_mask, crc_shift} + end + + defp validate_non_neg_integers!(params, width, poly, init_val, xorout) do + unless is_integer(width) and width >= 0 and + is_integer(poly) and poly >= 0 and + is_integer(init_val) and init_val >= 0 and + is_integer(xorout) and xorout >= 0 do + :erlang.error({:badarg, [params]}) + end + end + + defp validate_params!(params, {_msb_mask, crc_mask, _crc_shift}, check, residue) do + %{poly: poly, init: init_val, xorout: xorout} = params + + if poly > crc_mask or init_val > crc_mask or xorout > crc_mask or + check > crc_mask or residue > crc_mask do + :erlang.error({:badarg, [params]}) + end + end + + defp build_resource(params, {msb_mask, crc_mask, crc_shift}, check, residue, sick) do + %{width: width, poly: poly, init: init_val, refin: refin, refout: refout, xorout: xorout} = + params + + resource = %{ + __struct__: __MODULE__, + width: width, + poly: poly, + init: init_val, + refin: refin, + refout: refout, + xorout: xorout, + check: check, + residue: residue, + sick: sick, + msb_mask: msb_mask, + crc_mask: crc_mask, + crc_shift: crc_shift, + value: 0, + extra: 0 + } + + if sick do + {value, extra} = sick_init(resource) + %{resource | value: value, extra: extra} + else + %{resource | value: do_crc_init(init_val, 0, width, poly, msb_mask, crc_mask)} + end + end + + @doc """ + Updates an in-progress CRC calculation with additional data. + + Takes a `resource` returned by `init/1` or a previous call to `update/2`, + and `iodata` to process. Returns an updated resource. + + Can be called multiple times to process data incrementally. Call `final/1` + on the returned resource to get the final CRC value. + + ## Examples + + iex> resource = CRC.Pure.init(:crc_16) + iex> resource = CRC.Pure.update(resource, "1234") + iex> resource = CRC.Pure.update(resource, "56789") + iex> CRC.Pure.final(resource) + 0xBB3D + """ + @impl true + def update(resource = %{__struct__: __MODULE__, value: value, sick: false}, iodata) do + %{resource | value: do_crc_update(resource, value, IO.iodata_to_binary(iodata))} + end + + def update( + resource = %{__struct__: __MODULE__, value: value, extra: extra, sick: true}, + iodata + ) do + {new_value, new_extra} = + do_sick_update(resource, {value, extra}, IO.iodata_to_binary(iodata)) + + %{resource | value: new_value, extra: new_extra} + end + + @doc """ + Finalizes a CRC calculation and returns the checksum value. + + Takes a `resource` from `update/2` and applies the final processing steps + (bit flushing, output reflection, and XOR) to produce the CRC value. + + Returns the CRC checksum as a non-negative integer. + + ## Examples + + iex> resource = CRC.Pure.init(:crc_32) + iex> resource = CRC.Pure.update(resource, "123456789") + iex> CRC.Pure.final(resource) + 0xCBF43926 + """ + @impl true + def final(resource = %{__struct__: __MODULE__, value: value, sick: false}) do + do_crc_final(resource, value) + end + + def final(resource = %{__struct__: __MODULE__, value: value, sick: true}) do + do_sick_final(resource, value) + end + + @doc """ + Returns the model parameters for a CRC resource or model key. + + When given a resource (from `init/1`), extracts the model parameters. + When given an atom or map, initializes the resource first then extracts. + + Returns a map with keys: `:width`, `:poly`, `:init`, `:refin`, `:refout`, + `:xorout`, `:check`, `:residue`, and `:sick`. + + ## Examples + + iex> info = CRC.Pure.info(:crc_32) + iex> info.width + 32 + iex> info.poly + 0x04C11DB7 + """ + @impl true + def info(%{ + __struct__: __MODULE__, + width: width, + poly: poly, + init: init_val, + refin: refin, + refout: refout, + xorout: xorout, + check: check, + residue: residue, + sick: sick + }) do + %{ + width: width, + poly: poly, + init: init_val, + refin: refin, + refout: refout, + xorout: xorout, + check: check, + residue: residue, + sick: sick + } + end + + def info(params) do + info(init(params)) + end + + @doc """ + Calculates the residue value for a CRC model. + + The residue is the CRC value that results when the check value is appended + to the test string "123456789" and the CRC is recalculated. For well-formed + CRC models, this is a constant that can be used to verify data integrity. + + Accepts a resource (from `init/1`), an atom model key, or a map with model + parameters. + + ## Examples + + iex> CRC.Pure.residue(:crc_32) + 0xDEBB20E3 + """ + @impl true + def residue(resource = %{__struct__: __MODULE__, sick: false}) do + do_crc_residue(resource) + end + + def residue( + resource = %{ + __struct__: __MODULE__, + width: width, + refout: refout, + xorout: xorout0, + sick: true + } + ) do + xorout = + if refout do + crc_reflect(xorout0, width) + else + xorout0 + end + + copy = %{resource | init: 0, xorout: 0, value: 0} + {crc, _extra} = do_sick_update(copy, {0, 0}, <>) + do_sick_final(copy, crc) + end + + def residue(params) do + residue(init(params)) + end + + @doc """ + Returns a list of all available CRC model keys. + + These keys can be passed to `calc/2`, `init/1`, `info/1`, and `residue/1`. + + ## Examples + + iex> :crc_32 in CRC.Pure.list() + true + """ + def list do + Models.root_keys() + end + + # Bit reflection + + @doc false + def crc_reflect(reg, width) + when is_integer(reg) and reg >= 0 and is_integer(width) and width >= 0 do + res = reg &&& 0x01 + do_crc_reflect(res, reg, 0, width - 1) + end + + defp do_crc_reflect(res, _reg, max, max), do: res + + defp do_crc_reflect(res0, reg0, i, max) do + reg1 = reg0 >>> 1 + res1 = res0 <<< 1 + res2 = res1 ||| (reg1 &&& 0x01) + do_crc_reflect(res2, reg1, i + 1, max) + end + + # CRC init — process the init value through Width rounds + + defp do_crc_init(crc, width, width, _poly, _msb_mask, crc_mask) do + crc &&& crc_mask + end + + defp do_crc_init(crc0, i, width, poly, msb_mask, crc_mask) do + bit = crc0 &&& 0x01 + + crc1 = + if bit == 0 do + crc0 + else + bxor(crc0, poly) + end + + crc2 = crc1 >>> 1 + + crc3 = + if bit == 0 do + crc2 + else + crc2 ||| msb_mask + end + + do_crc_init(crc3, i + 1, width, poly, msb_mask, crc_mask) + end + + # CRC update — process input data byte by byte, bit by bit + + defp do_crc_update(_resource, crc, <<>>), do: crc + + defp do_crc_update( + resource = %{poly: poly, refin: false, msb_mask: msb_mask, crc_mask: crc_mask}, + crc, + <> + ) do + crc1 = do_crc_update_once(crc, octet, 0, 8, poly, msb_mask, crc_mask) + do_crc_update(resource, crc1, rest) + end + + defp do_crc_update( + resource = %{poly: poly, refin: true, msb_mask: msb_mask, crc_mask: crc_mask}, + crc, + <> + ) do + octet = crc_reflect(octet0, 8) + crc1 = do_crc_update_once(crc, octet, 0, 8, poly, msb_mask, crc_mask) + do_crc_update(resource, crc1, rest) + end + + defp do_crc_update_once(crc, _octet, max, max, _poly, _msb_mask, _crc_mask), do: crc + + defp do_crc_update_once(crc0, octet, i, max, poly, msb_mask, crc_mask) do + bit = crc0 &&& msb_mask + crc1 = (crc0 <<< 1 &&& crc_mask) ||| (octet >>> (7 - i) &&& 0x01) + + crc2 = + if bit == 0 do + crc1 + else + bxor(crc1, poly) + end + + do_crc_update_once(crc2, octet, i + 1, max, poly, msb_mask, crc_mask) + end + + # CRC final — flush remaining bits and apply refout/xorout + + defp do_crc_final( + %{ + width: width, + poly: poly, + refout: refout, + xorout: xorout, + msb_mask: msb_mask, + crc_mask: crc_mask + }, + crc + ) do + do_crc_final_loop(crc, 0, width, poly, refout, xorout, msb_mask, crc_mask) + end + + defp do_crc_final_loop(crc0, width, width, _poly, _refout = false, xorout, _msb_mask, crc_mask) do + bxor(crc0, xorout) &&& crc_mask + end + + defp do_crc_final_loop(crc0, width, width, _poly, _refout = true, xorout, _msb_mask, crc_mask) do + crc1 = crc_reflect(crc0, width) + bxor(crc1, xorout) &&& crc_mask + end + + defp do_crc_final_loop(crc0, i, width, poly, refout, xorout, msb_mask, crc_mask) do + bit = crc0 &&& msb_mask + crc1 = crc0 <<< 1 &&& crc_mask + + crc2 = + if bit == 0 do + crc1 + else + bxor(crc1, poly) + end + + do_crc_final_loop(crc2, i + 1, width, poly, refout, xorout, msb_mask, crc_mask) + end + + # CRC residue calculation + + defp do_crc_residue( + resource = %{ + __struct__: __MODULE__, + width: width, + refin: refin, + refout: refout, + xorout: xorout0 + } + ) do + copy = %{resource | init: 0, xorout: 0, value: 0} + + xorout = + if refout do + crc_reflect(xorout0, width) + else + xorout0 + end + + residue = do_crc_residue_loop(copy, xorout) + + if refin do + crc_reflect(residue, width) + else + residue + end + end + + defp do_crc_residue_loop( + %{ + width: width, + poly: poly, + init: init_val, + xorout: xorout, + msb_mask: msb_mask, + crc_mask: crc_mask + }, + message + ) do + do_crc_residue_step(bxor(init_val, message), 0, width, poly, xorout, msb_mask, crc_mask) + end + + defp do_crc_residue_step(remainder, max, max, _poly, xorout, _msb_mask, crc_mask) do + bxor(remainder, xorout) &&& crc_mask + end + + defp do_crc_residue_step(remainder, i, max, poly, xorout, msb_mask, crc_mask) do + if (remainder &&& msb_mask) == 0 do + do_crc_residue_step(remainder <<< 1, i + 1, max, poly, xorout, msb_mask, crc_mask) + else + do_crc_residue_step( + bxor(remainder <<< 1, poly), + i + 1, + max, + poly, + xorout, + msb_mask, + crc_mask + ) + end + end + + # SICK variant + + defp sick_init(%{width: 16, init: init_val}), do: {init_val, 0} + + defp sick_init(params), do: :erlang.error({:badarg, [params]}) + + defp do_sick_update(_resource, {crc, prev_byte}, <<>>), do: {crc, prev_byte} + + defp do_sick_update( + resource = %{ + width: 16, + poly: poly, + msb_mask: msb_mask, + crc_mask: crc_mask, + crc_shift: crc_shift + }, + {crc0, prev_byte0}, + <> + ) do + next_byte1 = 0x00FF &&& next_byte0 + + crc1 = + if (crc0 &&& msb_mask <<< crc_shift) == 0 do + crc0 <<< 1 + else + bxor(crc0 <<< 1, poly <<< crc_shift) + end + + crc2 = bxor(crc1, next_byte1 ||| prev_byte0) + prev_byte1 = next_byte1 <<< 8 + do_sick_update(resource, {crc2 &&& crc_mask, prev_byte1}, rest) + end + + defp do_sick_final(%{width: 16}, crc) do + low_byte = (crc &&& 0xFF00) >>> 8 + high_byte = (crc &&& 0x00FF) <<< 8 + low_byte ||| high_byte + end + + defp do_sick_final(params, crc), do: :erlang.error({:badarg, [params, crc]}) +end diff --git a/lib/crc/pure/models.ex b/lib/crc/pure/models.ex new file mode 100644 index 0000000..1fee02d --- /dev/null +++ b/lib/crc/pure/models.ex @@ -0,0 +1,1380 @@ +defmodule CRC.Pure.Models do + @moduledoc false + + import Bitwise + + # All CRC model definitions, extracted from the C NIF model tables. + # Each root model maps to its parameters. Aliases are stored in a separate map. + + @models %{ + # 3-bit + crc_3_gsm: %{ + width: 3, + poly: 0x03, + init: 0x00, + refin: false, + refout: false, + xorout: 0x07, + check: 0x04, + residue: 0x02, + sick: false, + name: "CRC-3/GSM" + }, + crc_3_rohc: %{ + width: 3, + poly: 0x03, + init: 0x07, + refin: true, + refout: true, + xorout: 0x00, + check: 0x06, + residue: 0x00, + sick: false, + name: "CRC-3/ROHC" + }, + # 4-bit + crc_4_interlaken: %{ + width: 4, + poly: 0x03, + init: 0x0F, + refin: false, + refout: false, + xorout: 0x0F, + check: 0x0B, + residue: 0x02, + sick: false, + name: "CRC-4/INTERLAKEN" + }, + crc_4_itu: %{ + width: 4, + poly: 0x03, + init: 0x00, + refin: true, + refout: true, + xorout: 0x00, + check: 0x07, + residue: 0x00, + sick: false, + name: "CRC-4/ITU" + }, + # 5-bit + crc_5_epc: %{ + width: 5, + poly: 0x09, + init: 0x09, + refin: false, + refout: false, + xorout: 0x00, + check: 0x00, + residue: 0x00, + sick: false, + name: "CRC-5/EPC" + }, + crc_5_itu: %{ + width: 5, + poly: 0x15, + init: 0x00, + refin: true, + refout: true, + xorout: 0x00, + check: 0x07, + residue: 0x00, + sick: false, + name: "CRC-5/ITU" + }, + crc_5_usb: %{ + width: 5, + poly: 0x05, + init: 0x1F, + refin: true, + refout: true, + xorout: 0x1F, + check: 0x19, + residue: 0x06, + sick: false, + name: "CRC-5/USB" + }, + # 6-bit + crc_6_cdma2000_a: %{ + width: 6, + poly: 0x27, + init: 0x3F, + refin: false, + refout: false, + xorout: 0x00, + check: 0x0D, + residue: 0x00, + sick: false, + name: "CRC-6/CDMA2000-A" + }, + crc_6_cdma2000_b: %{ + width: 6, + poly: 0x07, + init: 0x3F, + refin: false, + refout: false, + xorout: 0x00, + check: 0x3B, + residue: 0x00, + sick: false, + name: "CRC-6/CDMA2000-B" + }, + crc_6_darc: %{ + width: 6, + poly: 0x19, + init: 0x00, + refin: true, + refout: true, + xorout: 0x00, + check: 0x26, + residue: 0x00, + sick: false, + name: "CRC-6/DARC" + }, + crc_6_gsm: %{ + width: 6, + poly: 0x2F, + init: 0x00, + refin: false, + refout: false, + xorout: 0x3F, + check: 0x13, + residue: 0x3A, + sick: false, + name: "CRC-6/GSM" + }, + crc_6_itu: %{ + width: 6, + poly: 0x03, + init: 0x00, + refin: true, + refout: true, + xorout: 0x00, + check: 0x06, + residue: 0x00, + sick: false, + name: "CRC-6/ITU" + }, + # 7-bit + crc_7: %{ + width: 7, + poly: 0x09, + init: 0x00, + refin: false, + refout: false, + xorout: 0x00, + check: 0x75, + residue: 0x00, + sick: false, + name: "CRC-7" + }, + crc_7_rohc: %{ + width: 7, + poly: 0x4F, + init: 0x7F, + refin: true, + refout: true, + xorout: 0x00, + check: 0x53, + residue: 0x00, + sick: false, + name: "CRC-7/ROHC" + }, + crc_7_umts: %{ + width: 7, + poly: 0x45, + init: 0x00, + refin: false, + refout: false, + xorout: 0x00, + check: 0x61, + residue: 0x00, + sick: false, + name: "CRC-7/UMTS" + }, + # 8-bit + crc_8: %{ + width: 8, + poly: 0x07, + init: 0x00, + refin: false, + refout: false, + xorout: 0x00, + check: 0xF4, + residue: 0x00, + sick: false, + name: "CRC-8" + }, + crc_8_autosar: %{ + width: 8, + poly: 0x2F, + init: 0xFF, + refin: false, + refout: false, + xorout: 0xFF, + check: 0xDF, + residue: 0x42, + sick: false, + name: "CRC-8/AUTOSAR" + }, + crc_8_bluetooth: %{ + width: 8, + poly: 0xA7, + init: 0x00, + refin: true, + refout: true, + xorout: 0x00, + check: 0x26, + residue: 0x00, + sick: false, + name: "CRC-8/BLUETOOTH" + }, + crc_8_cdma2000: %{ + width: 8, + poly: 0x9B, + init: 0xFF, + refin: false, + refout: false, + xorout: 0x00, + check: 0xDA, + residue: 0x00, + sick: false, + name: "CRC-8/CDMA2000" + }, + crc_8_darc: %{ + width: 8, + poly: 0x39, + init: 0x00, + refin: true, + refout: true, + xorout: 0x00, + check: 0x15, + residue: 0x00, + sick: false, + name: "CRC-8/DARC" + }, + crc_8_dvb_s2: %{ + width: 8, + poly: 0xD5, + init: 0x00, + refin: false, + refout: false, + xorout: 0x00, + check: 0xBC, + residue: 0x00, + sick: false, + name: "CRC-8/DVB-S2" + }, + crc_8_ebu: %{ + width: 8, + poly: 0x1D, + init: 0xFF, + refin: true, + refout: true, + xorout: 0x00, + check: 0x97, + residue: 0x00, + sick: false, + name: "CRC-8/EBU" + }, + crc_8_gsm_a: %{ + width: 8, + poly: 0x1D, + init: 0x00, + refin: false, + refout: false, + xorout: 0x00, + check: 0x37, + residue: 0x00, + sick: false, + name: "CRC-8/GSM-A" + }, + crc_8_gsm_b: %{ + width: 8, + poly: 0x49, + init: 0x00, + refin: false, + refout: false, + xorout: 0xFF, + check: 0x94, + residue: 0x53, + sick: false, + name: "CRC-8/GSM-B" + }, + crc_8_i_code: %{ + width: 8, + poly: 0x1D, + init: 0xFD, + refin: false, + refout: false, + xorout: 0x00, + check: 0x7E, + residue: 0x00, + sick: false, + name: "CRC-8/I-CODE" + }, + crc_8_itu: %{ + width: 8, + poly: 0x07, + init: 0x00, + refin: false, + refout: false, + xorout: 0x55, + check: 0xA1, + residue: 0xAC, + sick: false, + name: "CRC-8/ITU" + }, + crc_8_koop: %{ + width: 8, + poly: 0x4D, + init: 0xFF, + refin: true, + refout: true, + xorout: 0xFF, + check: 0xD8, + residue: 0x15, + sick: false, + name: "CRC-8/KOOP" + }, + crc_8_lte: %{ + width: 8, + poly: 0x9B, + init: 0x00, + refin: false, + refout: false, + xorout: 0x00, + check: 0xEA, + residue: 0x00, + sick: false, + name: "CRC-8/LTE" + }, + crc_8_maxim: %{ + width: 8, + poly: 0x31, + init: 0x00, + refin: true, + refout: true, + xorout: 0x00, + check: 0xA1, + residue: 0x00, + sick: false, + name: "CRC-8/MAXIM" + }, + crc_8_opensafety: %{ + width: 8, + poly: 0x2F, + init: 0x00, + refin: false, + refout: false, + xorout: 0x00, + check: 0x3E, + residue: 0x00, + sick: false, + name: "CRC-8/OPENSAFETY" + }, + crc_8_rohc: %{ + width: 8, + poly: 0x07, + init: 0xFF, + refin: true, + refout: true, + xorout: 0x00, + check: 0xD0, + residue: 0x00, + sick: false, + name: "CRC-8/ROHC" + }, + crc_8_sae_j1850: %{ + width: 8, + poly: 0x1D, + init: 0xFF, + refin: false, + refout: false, + xorout: 0xFF, + check: 0x4B, + residue: 0xC4, + sick: false, + name: "CRC-8/SAE-J1850" + }, + crc_8_wcdma: %{ + width: 8, + poly: 0x9B, + init: 0x00, + refin: true, + refout: true, + xorout: 0x00, + check: 0x25, + residue: 0x00, + sick: false, + name: "CRC-8/WCDMA" + }, + # 10-bit + crc_10: %{ + width: 10, + poly: 0x0233, + init: 0x0000, + refin: false, + refout: false, + xorout: 0x0000, + check: 0x0199, + residue: 0x0000, + sick: false, + name: "CRC-10" + }, + crc_10_cdma2000: %{ + width: 10, + poly: 0x03D9, + init: 0x03FF, + refin: false, + refout: false, + xorout: 0x0000, + check: 0x0233, + residue: 0x0000, + sick: false, + name: "CRC-10/CDMA2000" + }, + crc_10_gsm: %{ + width: 10, + poly: 0x0175, + init: 0x0000, + refin: false, + refout: false, + xorout: 0x03FF, + check: 0x012A, + residue: 0x00C6, + sick: false, + name: "CRC-10/GSM" + }, + # 11-bit + crc_11: %{ + width: 11, + poly: 0x0385, + init: 0x001A, + refin: false, + refout: false, + xorout: 0x0000, + check: 0x05A3, + residue: 0x0000, + sick: false, + name: "CRC-11" + }, + crc_11_umts: %{ + width: 11, + poly: 0x0307, + init: 0x0000, + refin: false, + refout: false, + xorout: 0x0000, + check: 0x0061, + residue: 0x0000, + sick: false, + name: "CRC-11/UMTS" + }, + # 12-bit + crc_12_cdma2000: %{ + width: 12, + poly: 0x0F13, + init: 0x0FFF, + refin: false, + refout: false, + xorout: 0x0000, + check: 0x0D4D, + residue: 0x0000, + sick: false, + name: "CRC-12/CDMA2000" + }, + crc_12_dect: %{ + width: 12, + poly: 0x080F, + init: 0x0000, + refin: false, + refout: false, + xorout: 0x0000, + check: 0x0F5B, + residue: 0x0000, + sick: false, + name: "CRC-12/DECT" + }, + crc_12_gsm: %{ + width: 12, + poly: 0x0D31, + init: 0x0000, + refin: false, + refout: false, + xorout: 0x0FFF, + check: 0x0B34, + residue: 0x0178, + sick: false, + name: "CRC-12/GSM" + }, + crc_12_umts: %{ + width: 12, + poly: 0x080F, + init: 0x0000, + refin: false, + refout: true, + xorout: 0x0000, + check: 0x0DAF, + residue: 0x0000, + sick: false, + name: "CRC-12/UMTS" + }, + # 13-bit + crc_13_bbc: %{ + width: 13, + poly: 0x1CF5, + init: 0x0000, + refin: false, + refout: false, + xorout: 0x0000, + check: 0x04FA, + residue: 0x0000, + sick: false, + name: "CRC-13/BBC" + }, + # 14-bit + crc_14_darc: %{ + width: 14, + poly: 0x0805, + init: 0x0000, + refin: true, + refout: true, + xorout: 0x0000, + check: 0x082D, + residue: 0x0000, + sick: false, + name: "CRC-14/DARC" + }, + crc_14_gsm: %{ + width: 14, + poly: 0x202D, + init: 0x0000, + refin: false, + refout: false, + xorout: 0x3FFF, + check: 0x30AE, + residue: 0x031E, + sick: false, + name: "CRC-14/GSM" + }, + # 15-bit + crc_15: %{ + width: 15, + poly: 0x4599, + init: 0x0000, + refin: false, + refout: false, + xorout: 0x0000, + check: 0x059E, + residue: 0x0000, + sick: false, + name: "CRC-15" + }, + crc_15_mpt1327: %{ + width: 15, + poly: 0x6815, + init: 0x0000, + refin: false, + refout: false, + xorout: 0x0001, + check: 0x2566, + residue: 0x6815, + sick: false, + name: "CRC-15/MPT1327" + }, + # 16-bit + crc_16: %{ + width: 16, + poly: 0x8005, + init: 0x0000, + refin: true, + refout: true, + xorout: 0x0000, + check: 0xBB3D, + residue: 0x0000, + sick: false, + name: "CRC-16" + }, + crc_16_a: %{ + width: 16, + poly: 0x1021, + init: 0xC6C6, + refin: true, + refout: true, + xorout: 0x0000, + check: 0xBF05, + residue: 0x0000, + sick: false, + name: "CRC-16/A" + }, + crc_16_aug_ccitt: %{ + width: 16, + poly: 0x1021, + init: 0x1D0F, + refin: false, + refout: false, + xorout: 0x0000, + check: 0xE5CC, + residue: 0x0000, + sick: false, + name: "CRC-16/AUG-CCITT" + }, + crc_16_buypass: %{ + width: 16, + poly: 0x8005, + init: 0x0000, + refin: false, + refout: false, + xorout: 0x0000, + check: 0xFEE8, + residue: 0x0000, + sick: false, + name: "CRC-16/BUYPASS" + }, + crc_16_ccitt: %{ + width: 16, + poly: 0x1021, + init: 0x0000, + refin: true, + refout: true, + xorout: 0x0000, + check: 0x2189, + residue: 0x0000, + sick: false, + name: "CRC-16/CCITT" + }, + crc_16_ccitt_false: %{ + width: 16, + poly: 0x1021, + init: 0xFFFF, + refin: false, + refout: false, + xorout: 0x0000, + check: 0x29B1, + residue: 0x0000, + sick: false, + name: "CRC-16/CCITT-FALSE" + }, + crc_16_cdma2000: %{ + width: 16, + poly: 0xC867, + init: 0xFFFF, + refin: false, + refout: false, + xorout: 0x0000, + check: 0x4C06, + residue: 0x0000, + sick: false, + name: "CRC-16/CDMA2000" + }, + crc_16_cms: %{ + width: 16, + poly: 0x8005, + init: 0xFFFF, + refin: false, + refout: false, + xorout: 0x0000, + check: 0xAEE7, + residue: 0x0000, + sick: false, + name: "CRC-16/CMS" + }, + crc_16_dds_110: %{ + width: 16, + poly: 0x8005, + init: 0x800D, + refin: false, + refout: false, + xorout: 0x0000, + check: 0x9ECF, + residue: 0x0000, + sick: false, + name: "CRC-16/DDS-110" + }, + crc_16_dect_r: %{ + width: 16, + poly: 0x0589, + init: 0x0000, + refin: false, + refout: false, + xorout: 0x0001, + check: 0x007E, + residue: 0x0589, + sick: false, + name: "CRC-16/DECT-R" + }, + crc_16_dect_x: %{ + width: 16, + poly: 0x0589, + init: 0x0000, + refin: false, + refout: false, + xorout: 0x0000, + check: 0x007F, + residue: 0x0000, + sick: false, + name: "CRC-16/DECT-X" + }, + crc_16_dnp: %{ + width: 16, + poly: 0x3D65, + init: 0x0000, + refin: true, + refout: true, + xorout: 0xFFFF, + check: 0xEA82, + residue: 0x66C5, + sick: false, + name: "CRC-16/DNP" + }, + crc_16_en_13757: %{ + width: 16, + poly: 0x3D65, + init: 0x0000, + refin: false, + refout: false, + xorout: 0xFFFF, + check: 0xC2B7, + residue: 0xA366, + sick: false, + name: "CRC-16/EN-13757" + }, + crc_16_genibus: %{ + width: 16, + poly: 0x1021, + init: 0xFFFF, + refin: false, + refout: false, + xorout: 0xFFFF, + check: 0xD64E, + residue: 0x1D0F, + sick: false, + name: "CRC-16/GENIBUS" + }, + crc_16_gsm: %{ + width: 16, + poly: 0x1021, + init: 0x0000, + refin: false, + refout: false, + xorout: 0xFFFF, + check: 0xCE3C, + residue: 0x1D0F, + sick: false, + name: "CRC-16/GSM" + }, + crc_16_lj1200: %{ + width: 16, + poly: 0x6F63, + init: 0x0000, + refin: false, + refout: false, + xorout: 0x0000, + check: 0xBDF4, + residue: 0x0000, + sick: false, + name: "CRC-16/LJ1200" + }, + crc_16_mcrf4xx: %{ + width: 16, + poly: 0x1021, + init: 0xFFFF, + refin: true, + refout: true, + xorout: 0x0000, + check: 0x6F91, + residue: 0x0000, + sick: false, + name: "CRC-16/MCRF4XX" + }, + crc_16_modbus: %{ + width: 16, + poly: 0x8005, + init: 0xFFFF, + refin: true, + refout: true, + xorout: 0x0000, + check: 0x4B37, + residue: 0x0000, + sick: false, + name: "CRC-16/MODBUS" + }, + crc_16_opensafety_a: %{ + width: 16, + poly: 0x5935, + init: 0x0000, + refin: false, + refout: false, + xorout: 0x0000, + check: 0x5D38, + residue: 0x0000, + sick: false, + name: "CRC-16/OPENSAFETY-A" + }, + crc_16_opensafety_b: %{ + width: 16, + poly: 0x755B, + init: 0x0000, + refin: false, + refout: false, + xorout: 0x0000, + check: 0x20FE, + residue: 0x0000, + sick: false, + name: "CRC-16/OPENSAFETY-B" + }, + crc_16_profibus: %{ + width: 16, + poly: 0x1DCF, + init: 0xFFFF, + refin: false, + refout: false, + xorout: 0xFFFF, + check: 0xA819, + residue: 0xE394, + sick: false, + name: "CRC-16/PROFIBUS" + }, + crc_16_riello: %{ + width: 16, + poly: 0x1021, + init: 0xB2AA, + refin: true, + refout: true, + xorout: 0x0000, + check: 0x63D0, + residue: 0x0000, + sick: false, + name: "CRC-16/RIELLO" + }, + crc_16_sick: %{ + width: 16, + poly: 0x8005, + init: 0x0000, + refin: false, + refout: false, + xorout: 0x0000, + check: 0x56A6, + residue: 0x0000, + sick: true, + name: "CRC-16/SICK" + }, + crc_16_t10_dif: %{ + width: 16, + poly: 0x8BB7, + init: 0x0000, + refin: false, + refout: false, + xorout: 0x0000, + check: 0xD0DB, + residue: 0x0000, + sick: false, + name: "CRC-16/T10-DIF" + }, + crc_16_teledisk: %{ + width: 16, + poly: 0xA097, + init: 0x0000, + refin: false, + refout: false, + xorout: 0x0000, + check: 0x0FB3, + residue: 0x0000, + sick: false, + name: "CRC-16/TELEDISK" + }, + crc_16_tms37157: %{ + width: 16, + poly: 0x1021, + init: 0x89EC, + refin: true, + refout: true, + xorout: 0x0000, + check: 0x26B1, + residue: 0x0000, + sick: false, + name: "CRC-16/TMS37157" + }, + crc_16_usb: %{ + width: 16, + poly: 0x8005, + init: 0xFFFF, + refin: true, + refout: true, + xorout: 0xFFFF, + check: 0xB4C8, + residue: 0xB001, + sick: false, + name: "CRC-16/USB" + }, + crc_16_x_25: %{ + width: 16, + poly: 0x1021, + init: 0xFFFF, + refin: true, + refout: true, + xorout: 0xFFFF, + check: 0x906E, + residue: 0xF0B8, + sick: false, + name: "CRC-16/X-25" + }, + crc_16_xmodem: %{ + width: 16, + poly: 0x1021, + init: 0x0000, + refin: false, + refout: false, + xorout: 0x0000, + check: 0x31C3, + residue: 0x0000, + sick: false, + name: "CRC-16/XMODEM" + }, + # 17-bit + crc_17_can_fd: %{ + width: 17, + poly: 0x0001685B, + init: 0x00000000, + refin: false, + refout: false, + xorout: 0x00000000, + check: 0x00004F03, + residue: 0x00000000, + sick: false, + name: "CRC-17/CAN-FD" + }, + # 21-bit + crc_21_can_fd: %{ + width: 21, + poly: 0x00102899, + init: 0x00000000, + refin: false, + refout: false, + xorout: 0x00000000, + check: 0x000ED841, + residue: 0x00000000, + sick: false, + name: "CRC-21/CAN-FD" + }, + # 24-bit + crc_24: %{ + width: 24, + poly: 0x00864CFB, + init: 0x00B704CE, + refin: false, + refout: false, + xorout: 0x00000000, + check: 0x0021CF02, + residue: 0x00000000, + sick: false, + name: "CRC-24" + }, + crc_24_ble: %{ + width: 24, + poly: 0x0000065B, + init: 0x00555555, + refin: true, + refout: true, + xorout: 0x00000000, + check: 0x00C25A56, + residue: 0x00000000, + sick: false, + name: "CRC-24/BLE" + }, + crc_24_flexray_a: %{ + width: 24, + poly: 0x005D6DCB, + init: 0x00FEDCBA, + refin: false, + refout: false, + xorout: 0x00000000, + check: 0x007979BD, + residue: 0x00000000, + sick: false, + name: "CRC-24/FLEXRAY-A" + }, + crc_24_flexray_b: %{ + width: 24, + poly: 0x005D6DCB, + init: 0x00ABCDEF, + refin: false, + refout: false, + xorout: 0x00000000, + check: 0x001F23B8, + residue: 0x00000000, + sick: false, + name: "CRC-24/FLEXRAY-B" + }, + crc_24_interlaken: %{ + width: 24, + poly: 0x00328B63, + init: 0x00FFFFFF, + refin: false, + refout: false, + xorout: 0x00FFFFFF, + check: 0x00B4F3E6, + residue: 0x00144E63, + sick: false, + name: "CRC-24/INTERLAKEN" + }, + crc_24_lte_a: %{ + width: 24, + poly: 0x00864CFB, + init: 0x00000000, + refin: false, + refout: false, + xorout: 0x00000000, + check: 0x00CDE703, + residue: 0x00000000, + sick: false, + name: "CRC-24/LTE-A" + }, + crc_24_lte_b: %{ + width: 24, + poly: 0x00800063, + init: 0x00000000, + refin: false, + refout: false, + xorout: 0x00000000, + check: 0x0023EF52, + residue: 0x00000000, + sick: false, + name: "CRC-24/LTE-B" + }, + # 30-bit + crc_30_cdma: %{ + width: 30, + poly: 0x2030B9C7, + init: 0x3FFFFFFF, + refin: false, + refout: false, + xorout: 0x3FFFFFFF, + check: 0x04C34ABF, + residue: 0x34EFA55A, + sick: false, + name: "CRC-30/CDMA" + }, + # 31-bit + crc_31_philips: %{ + width: 31, + poly: 0x04C11DB7, + init: 0x7FFFFFFF, + refin: false, + refout: false, + xorout: 0x7FFFFFFF, + check: 0x0CE9E46C, + residue: 0x4EAF26F1, + sick: false, + name: "CRC-31/PHILIPS" + }, + # 32-bit + crc_32: %{ + width: 32, + poly: 0x04C11DB7, + init: 0xFFFFFFFF, + refin: true, + refout: true, + xorout: 0xFFFFFFFF, + check: 0xCBF43926, + residue: 0xDEBB20E3, + sick: false, + name: "CRC-32" + }, + crc_32_autosar: %{ + width: 32, + poly: 0xF4ACFB13, + init: 0xFFFFFFFF, + refin: true, + refout: true, + xorout: 0xFFFFFFFF, + check: 0x1697D06A, + residue: 0x904CDDBF, + sick: false, + name: "CRC-32/AUTOSAR" + }, + crc_32_bzip2: %{ + width: 32, + poly: 0x04C11DB7, + init: 0xFFFFFFFF, + refin: false, + refout: false, + xorout: 0xFFFFFFFF, + check: 0xFC891918, + residue: 0xC704DD7B, + sick: false, + name: "CRC-32/BZIP2" + }, + crc_32_jamcrc: %{ + width: 32, + poly: 0x04C11DB7, + init: 0xFFFFFFFF, + refin: true, + refout: true, + xorout: 0x00000000, + check: 0x340BC6D9, + residue: 0x00000000, + sick: false, + name: "CRC-32/JAMCRC" + }, + crc_32_mpeg_2: %{ + width: 32, + poly: 0x04C11DB7, + init: 0xFFFFFFFF, + refin: false, + refout: false, + xorout: 0x00000000, + check: 0x0376E6E7, + residue: 0x00000000, + sick: false, + name: "CRC-32/MPEG-2" + }, + crc_32_posix: %{ + width: 32, + poly: 0x04C11DB7, + init: 0x00000000, + refin: false, + refout: false, + xorout: 0xFFFFFFFF, + check: 0x765E7680, + residue: 0xC704DD7B, + sick: false, + name: "CRC-32/POSIX" + }, + crc_32_xfer: %{ + width: 32, + poly: 0x000000AF, + init: 0x00000000, + refin: false, + refout: false, + xorout: 0x00000000, + check: 0xBD0BE338, + residue: 0x00000000, + sick: false, + name: "CRC-32/XFER" + }, + crc_32c: %{ + width: 32, + poly: 0x1EDC6F41, + init: 0xFFFFFFFF, + refin: true, + refout: true, + xorout: 0xFFFFFFFF, + check: 0xE3069283, + residue: 0xB798B438, + sick: false, + name: "CRC-32C" + }, + crc_32d: %{ + width: 32, + poly: 0xA833982B, + init: 0xFFFFFFFF, + refin: true, + refout: true, + xorout: 0xFFFFFFFF, + check: 0x87315576, + residue: 0x45270551, + sick: false, + name: "CRC-32D" + }, + crc_32q: %{ + width: 32, + poly: 0x814141AB, + init: 0x00000000, + refin: false, + refout: false, + xorout: 0x00000000, + check: 0x3010BF7F, + residue: 0x00000000, + sick: false, + name: "CRC-32Q" + }, + # 40-bit + crc_40_gsm: %{ + width: 40, + poly: 0x0000000004820009, + init: 0x0000000000000000, + refin: false, + refout: false, + xorout: 0x000000FFFFFFFFFF, + check: 0x000000D4164FC646, + residue: 0x000000C4FF8071FF, + sick: false, + name: "CRC-40/GSM" + }, + # 64-bit + crc_64: %{ + width: 64, + poly: 0x42F0E1EBA9EA3693, + init: 0x0000000000000000, + refin: false, + refout: false, + xorout: 0x0000000000000000, + check: 0x6C40DF5F0B497347, + residue: 0x0000000000000000, + sick: false, + name: "CRC-64" + }, + crc_64_go_iso: %{ + width: 64, + poly: 0x000000000000001B, + init: 0xFFFFFFFFFFFFFFFF, + refin: true, + refout: true, + xorout: 0xFFFFFFFFFFFFFFFF, + check: 0xB90956C775A41001, + residue: 0x5300000000000000, + sick: false, + name: "CRC-64/GO-ISO" + }, + crc_64_jones: %{ + width: 64, + poly: 0xAD93D23594C935A9, + init: 0xFFFFFFFFFFFFFFFF, + refin: true, + refout: true, + xorout: 0x0000000000000000, + check: 0xCAA717168609F281, + residue: 0x0000000000000000, + sick: false, + name: "CRC-64/JONES" + }, + crc_64_we: %{ + width: 64, + poly: 0x42F0E1EBA9EA3693, + init: 0xFFFFFFFFFFFFFFFF, + refin: false, + refout: false, + xorout: 0xFFFFFFFFFFFFFFFF, + check: 0x62EC59E3F1A4F00A, + residue: 0xFCACBEBD5931A992, + sick: false, + name: "CRC-64/WE" + }, + crc_64_xz: %{ + width: 64, + poly: 0x42F0E1EBA9EA3693, + init: 0xFFFFFFFFFFFFFFFF, + refin: true, + refout: true, + xorout: 0xFFFFFFFFFFFFFFFF, + check: 0x995DC9BBDF1939FA, + residue: 0x49958C9ABD7D353F, + sick: false, + name: "CRC-64/XZ" + } + } + + # Alias -> root key mapping + @aliases %{ + arc: :crc_16, + crc_16_arc: :crc_16, + crc_16_lha: :crc_16, + crc_ibm: :crc_16, + crc_a: :crc_16_a, + crc_16_spi_fujitsu: :crc_16_aug_ccitt, + crc_16_umts: :crc_16_buypass, + crc_16_verifone: :crc_16_buypass, + crc_16_ccitt_true: :crc_16_ccitt, + crc_16_kermit: :crc_16_ccitt, + crc_ccitt: :crc_16_ccitt, + kermit: :crc_16_ccitt, + crc_16_darc: :crc_16_genibus, + crc_16_epc: :crc_16_genibus, + crc_16_i_code: :crc_16_genibus, + modbus: :crc_16_modbus, + crc_16_iec_61158_2: :crc_16_profibus, + sick: :crc_16_sick, + crc_16_b: :crc_16_x_25, + crc_16_ibm_sdlc: :crc_16_x_25, + crc_16_iso_hdlc: :crc_16_x_25, + crc_b: :crc_16_x_25, + x_25: :crc_16_x_25, + crc_16_acorn: :crc_16_xmodem, + crc_16_lte: :crc_16_xmodem, + xmodem: :crc_16_xmodem, + zmodem: :crc_16_xmodem, + crc_5: :crc_5_usb, + dow_crc: :crc_8_maxim, + x_crc_12: :crc_12_dect, + crc_12_3gpp: :crc_12_umts, + crc_24_openpgp: :crc_24, + crc_32_adccp: :crc_32, + pkzip: :crc_32, + b_crc_32: :crc_32_bzip2, + crc_32_aal5: :crc_32_bzip2, + crc_32_dect_b: :crc_32_bzip2, + jamcrc: :crc_32_jamcrc, + cksum: :crc_32_posix, + xfer: :crc_32_xfer, + crc_32_castagnoli: :crc_32c, + crc_32_interlaken: :crc_32c, + crc_32_iscsi: :crc_32c, + crc_64_ecma_182: :crc_64, + crc_64_go_ecma: :crc_64_xz + } + + @doc false + def all, do: @models + + @doc false + def get(key) when is_atom(key) do + case Map.get(@models, key) do + nil -> + case Map.get(@aliases, key) do + nil -> nil + root_key -> Map.get(@models, root_key) + end + + model -> + model + end + end + + @doc false + def resolve_key(key) when is_atom(key) do + if Map.has_key?(@models, key) do + key + else + Map.get(@aliases, key) + end + end + + @doc false + def root_keys, do: Map.keys(@models) + + @doc false + def init_resource(params) when is_map(params) do + width = Map.fetch!(params, :width) + poly = Map.fetch!(params, :poly) + init = Map.fetch!(params, :init) + refin = Map.fetch!(params, :refin) + refout = Map.fetch!(params, :refout) + xorout = Map.fetch!(params, :xorout) + check = Map.get(params, :check, 0) + residue = Map.get(params, :residue, 0) + sick = Map.get(params, :sick, false) + + msb_mask = 1 <<< (width - 1) + crc_mask = 1 ||| (msb_mask - 1) <<< 1 + crc_shift = if width < 8, do: 8 - width, else: 0 + + %{ + width: width, + poly: poly, + init: init, + refin: refin, + refout: refout, + xorout: xorout, + check: check, + residue: residue, + sick: sick, + msb_mask: msb_mask, + crc_mask: crc_mask, + crc_shift: crc_shift, + value: 0, + extra: 0 + } + end +end diff --git a/test/crc_pure_test.exs b/test/crc_pure_test.exs new file mode 100644 index 0000000..e05460a --- /dev/null +++ b/test/crc_pure_test.exs @@ -0,0 +1,422 @@ +defmodule CRCPureTest do + use ExUnit.Case + use PropCheck + import Bitwise + + doctest CRC.Pure + + @test_data "123456789" + + # -- Functionality ----------------------------------------------------------- + + describe "calculate/2" do + test "CRC-32 with known input" do + assert CRC.Pure.calculate(@test_data, :crc_32) == 0xCBF43926 + end + + test "CRC-16 with known input" do + assert CRC.Pure.calculate(@test_data, :crc_16) == 0xBB3D + end + + test "CRC-8 with known input" do + assert CRC.Pure.calculate(@test_data, :crc_8) == 0xF4 + end + + test "CRC-64/XZ with known input" do + assert CRC.Pure.calculate(@test_data, :crc_64_xz) == 0x995DC9BBDF1939FA + end + + test "SICK CRC with known input" do + assert CRC.Pure.calculate(@test_data, :crc_16_sick) == 0x56A6 + end + + test "with large input" do + large_input = :binary.copy(@test_data, 1024 * 40 + 1) + assert CRC.Pure.calculate(large_input, :crc_32) == :crc_fast.calc(:crc_32, large_input) + end + + test "with empty binary" do + assert CRC.Pure.calculate(<<>>, :crc_32) == :crc_fast.calc(:crc_32, <<>>) + end + + test "with iolist" do + iolist = ["1234", "56789"] + assert CRC.Pure.calculate(iolist, :crc_32) == CRC.Pure.calculate(@test_data, :crc_32) + end + end + + describe "init/1 and multi-part calculation" do + test "streaming produces same result as one-shot" do + one_shot = CRC.Pure.calculate(@test_data, :crc_32) + + streamed = + CRC.Pure.init(:crc_32) + |> CRC.Pure.update("1234") + |> CRC.Pure.update("56789") + |> CRC.Pure.final() + + assert streamed == one_shot + end + + test "byte-at-a-time produces same result" do + one_shot = CRC.Pure.calculate(@test_data, :crc_16) + + resource = CRC.Pure.init(:crc_16) + + resource = + @test_data + |> :binary.bin_to_list() + |> Enum.reduce(resource, fn byte, acc -> CRC.Pure.update(acc, <>) end) + + assert CRC.Pure.final(resource) == one_shot + end + + test "init with custom model map" do + model = %{ + width: 16, + poly: 0x8005, + init: 0x0000, + refin: true, + refout: true, + xorout: 0x0000 + } + + assert CRC.Pure.calc(model, @test_data) == :crc_fast.calc(model, @test_data) + end + + test "init with extend" do + extended = %{extend: :crc_16_ccitt_false, init: 0x1D0F} + assert CRC.Pure.calc(extended, @test_data) == :crc_fast.calc(extended, @test_data) + end + end + + describe "info/1" do + test "returns model parameters from atom" do + info = CRC.Pure.info(:crc_32) + assert info.width == 32 + assert info.poly == 0x04C11DB7 + assert info.init == 0xFFFFFFFF + assert info.refin == true + assert info.refout == true + assert info.xorout == 0xFFFFFFFF + end + + test "returns model parameters from resource" do + resource = CRC.Pure.init(:crc_16) + info = CRC.Pure.info(resource) + assert info.width == 16 + end + + test "returns sick flag" do + info = CRC.Pure.info(:crc_16_sick) + assert info.sick == true + end + end + + describe "residue/1" do + test "CRC-32 residue" do + assert CRC.Pure.residue(:crc_32) == 0xDEBB20E3 + end + + test "matches check value for all models" do + for key <- CRC.Pure.list() do + info = CRC.Pure.info(key) + + assert CRC.Pure.calc(key, @test_data) == info.check, + "check mismatch for #{key}" + end + end + end + + describe "list/0" do + test "returns a non-empty list of atoms" do + models = CRC.Pure.list() + assert is_list(models) + assert length(models) > 0 + assert Enum.all?(models, &is_atom/1) + end + + test "all models are initializable" do + for key <- CRC.Pure.list() do + resource = CRC.Pure.init(key) + assert is_map(resource), "failed to init #{key}" + end + end + end + + # -- Bad parameters ---------------------------------------------------------- + + describe "init/1 with bad parameters" do + test "raises on unknown atom" do + assert_raise ArgumentError, fn -> + CRC.Pure.init(:not_a_real_model) + end + end + + test "raises on unknown extend base" do + assert_raise ArgumentError, fn -> + CRC.Pure.init(%{extend: :not_a_real_model, init: 0x00}) + end + end + + test "raises on non-integer width" do + assert_raise ArgumentError, fn -> + CRC.Pure.init(%{ + width: "16", + poly: 0x8005, + init: 0x0000, + refin: true, + refout: true, + xorout: 0x0000 + }) + end + end + + test "raises on negative width" do + assert_raise ArgumentError, fn -> + CRC.Pure.init(%{ + width: -1, + poly: 0x8005, + init: 0x0000, + refin: true, + refout: true, + xorout: 0x0000 + }) + end + end + + test "raises on non-boolean refin" do + assert_raise ArgumentError, fn -> + CRC.Pure.init(%{ + width: 16, + poly: 0x8005, + init: 0x0000, + refin: 1, + refout: true, + xorout: 0x0000 + }) + end + end + + test "raises on non-boolean refout" do + assert_raise ArgumentError, fn -> + CRC.Pure.init(%{ + width: 16, + poly: 0x8005, + init: 0x0000, + refin: true, + refout: "yes", + xorout: 0x0000 + }) + end + end + + test "raises on missing required keys" do + assert_raise ArgumentError, fn -> + CRC.Pure.init(%{width: 16, poly: 0x8005}) + end + end + + test "raises on poly exceeding width" do + assert_raise ArgumentError, fn -> + CRC.Pure.init(%{ + width: 8, + poly: 0xFFFF, + init: 0x00, + refin: false, + refout: false, + xorout: 0x00 + }) + end + end + + test "raises on non-map, non-atom input" do + assert_raise ArgumentError, fn -> + CRC.Pure.init(42) + end + end + end + + # -- Property tests ---------------------------------------------------------- + + describe "properties" do + property "matches crc_fast for all known models" do + models = Map.keys(:crc_nif.crc_list()) + + forall {model, input} in {oneof(models), binary()} do + CRC.Pure.calc(model, input) === :crc_fast.calc(model, input) + end + end + + property "matches crc_fast for random custom models" do + model_gen_unsafe = + let { + width, + poly, + init, + refin, + refout, + xorout + } <- { + integer(1, 64), + such_that(n <- integer(), when: n > 0), + integer(), + boolean(), + boolean(), + integer() + } do + msb_mask = 1 <<< (width - 1) + crc_mask = 1 ||| (msb_mask - 1) <<< 1 + + %{ + width: width, + poly: poly &&& crc_mask, + init: init &&& crc_mask, + refin: refin, + refout: refout, + xorout: xorout &&& crc_mask + } + end + + model_gen = + such_that(%{poly: poly} <- model_gen_unsafe, when: poly > 0 and rem(poly, 2) != 0) + + forall {model, input} <- {model_gen, binary()} do + CRC.Pure.calc(model, input) === :crc_fast.calc(model, input) + end + end + + property "matches crc_fast for random SICK models" do + model_gen_unsafe = + let { + width, + poly, + init, + refin, + refout, + xorout + } <- { + return(16), + such_that(n <- integer(), when: n > 0), + integer(), + boolean(), + boolean(), + integer() + } do + msb_mask = 1 <<< (width - 1) + crc_mask = 1 ||| (msb_mask - 1) <<< 1 + + %{ + width: width, + poly: poly &&& crc_mask, + init: init &&& crc_mask, + refin: refin, + refout: refout, + xorout: xorout &&& crc_mask, + sick: true + } + end + + model_gen = + such_that(%{poly: poly} <- model_gen_unsafe, when: poly > 0 and rem(poly, 2) != 0) + + forall {model, input} <- {model_gen, binary()} do + CRC.Pure.calc(model, input) === :crc_fast.calc(model, input) + end + end + + property "streaming matches one-shot for all models" do + models = Map.keys(:crc_nif.crc_list()) + + forall {model, part1, part2} in {oneof(models), binary(), binary()} do + one_shot = CRC.Pure.calc(model, <>) + + streamed = + model + |> CRC.Pure.init() + |> CRC.Pure.update(part1) + |> CRC.Pure.update(part2) + |> CRC.Pure.final() + + one_shot === streamed + end + end + end + + # -- RevEng verification ---------------------------------------------------- + + if System.get_env("REVENG_BIN") do + describe "reveng verification" do + property "CRC.Pure matches reveng for all known models" do + models = Map.keys(:crc_nif.crc_list()) + + # Remove unsupported RevEng models + models = + models -- + [ + :crc_8_koop, + :crc_16_sick, + :crc_64_jones + ] + + infos = + for model <- models, into: %{}, do: {model, :crc_fast.info(:crc_fast.init(model))} + + names = + for {model, %{name: name}} <- infos, into: %{} do + name = + case name do + "CRC-16/A" -> "CRC-A" + "CRC-16/MODBUS" -> "MODBUS" + "CRC-16/X-25" -> "X-25" + "CRC-16/XMODEM" -> "XMODEM" + "CRC-32/JAMCRC" -> "JAMCRC" + "CRC-32/XFER" -> "XFER" + _ -> name + end + + {model, name} + end + + sizes = for {model, %{bits: bits}} <- infos, into: %{}, do: {model, bits} + widths = for {model, %{width: width}} <- infos, into: %{}, do: {model, width} + + forall {model, input} in {oneof(models), binary()} do + command = + "#{System.get_env("REVENG_BIN")} -c -m \"#{names[model]}\" \"#{Base.encode16(input)}\"" + + results = + command + |> :erlang.binary_to_list() + |> :os.cmd() + |> :erlang.list_to_binary() + |> String.trim() + + results = + case results do + <<"0x", rest::binary>> -> rest + _ -> results + end + + crc_le = :erlang.binary_to_integer(results, 16) + pure_result = CRC.Pure.calc(model, input) + + if pure_result === crc_le do + true + else + size = + if rem(widths[model], 8) != 0 do + sizes[model] + else + widths[model] + end + + crc_le_bin = <> + <> = crc_le_bin + pure_result === crc_be + end + end + end + end + end +end diff --git a/test/crc_test.exs b/test/crc_test.exs index 6f5a2f0..7615cf4 100644 --- a/test/crc_test.exs +++ b/test/crc_test.exs @@ -76,12 +76,21 @@ defmodule CRCTest do assert :crc_algorithm.verify_residue(:crc_slow, %{display: :failed}) == [] end + test "CRC.Pure module verifies all checks" do + assert :crc_algorithm.verify_check(CRC.Pure, %{display: :failed}) == [] + end + + test "CRC.Pure module verifies all residues" do + assert :crc_algorithm.verify_residue(CRC.Pure, %{display: :failed}) == [] + end + property "matching CRC for known models" do models = Map.keys(:crc_nif.crc_list()) forall {model, input} in {oneof(models), binary()} do :crc_slow.calc(model, input) === :crc_fast.calc(model, input) and - :crc_fast.calc(model, input) === :crc_pure.calc(model, input) + :crc_fast.calc(model, input) === :crc_pure.calc(model, input) and + :crc_pure.calc(model, input) === CRC.Pure.calc(model, input) end end @@ -119,7 +128,8 @@ defmodule CRCTest do forall {model, input} <- {model_gen, binary()} do :crc_slow.calc(model, input) === :crc_fast.calc(model, input) and - :crc_fast.calc(model, input) === :crc_pure.calc(model, input) + :crc_fast.calc(model, input) === :crc_pure.calc(model, input) and + :crc_pure.calc(model, input) === CRC.Pure.calc(model, input) end end @@ -158,7 +168,8 @@ defmodule CRCTest do forall {model, input} <- {model_gen, binary()} do :crc_slow.calc(model, input) === :crc_fast.calc(model, input) and - :crc_fast.calc(model, input) === :crc_pure.calc(model, input) + :crc_fast.calc(model, input) === :crc_pure.calc(model, input) and + :crc_pure.calc(model, input) === CRC.Pure.calc(model, input) end end @@ -291,9 +302,11 @@ defmodule CRCTest do fast_challenge = :crc_fast.calc(model, input) pure_challenge = :crc_pure.calc(model, input) slow_challenge = :crc_slow.calc(model, input) + elixir_pure_challenge = CRC.Pure.calc(model, input) if fast_challenge === crc_le do - fast_challenge === crc_le and pure_challenge === crc_le and slow_challenge === crc_le + fast_challenge === crc_le and pure_challenge === crc_le and + slow_challenge === crc_le and elixir_pure_challenge === crc_le else size = if rem(widths[model], 8) != 0 do @@ -304,7 +317,9 @@ defmodule CRCTest do crc_le_bin = <> <> = crc_le_bin - fast_challenge === crc_be and pure_challenge === crc_be and slow_challenge === crc_be + + fast_challenge === crc_be and pure_challenge === crc_be and + slow_challenge === crc_be and elixir_pure_challenge === crc_be end end end