From ee4b4b434cf9a31073d196cb4bd334a1a081bef2 Mon Sep 17 00:00:00 2001 From: Daniel Spofford Date: Sun, 26 May 2019 11:12:50 -0500 Subject: [PATCH] Increase data structures handled by scans This was achieved by ensuring that when deserializing advertising reports, portions of the binary that cannot be deserialized should not cause a chain reaction of failure. Previously this would cause devices not to show up in the results. - ArrayedData: return an error instead of crashing - AdvertisingReport: use the result of Device.deserialize regardless of status - Device - cleanup bad doctests - simplify some serialization clauses to ensure the input binary is returned on error - Transport - improve documentation - LE is always a handler --- lib/harald/hci/arrayed_data.ex | 14 +- lib/harald/hci/event.ex | 14 +- .../hci/event/le_meta/advertising_report.ex | 5 +- .../le_meta/advertising_report/device.ex | 185 +++++++----------- lib/harald/transport.ex | 62 +++--- mix.lock | 20 +- .../event/le_meta/advertising_report_test.exs | 8 +- test/harald/hci_test.exs | 8 +- test/support/generators/hci/event.ex | 2 +- .../hci/event/le_meta/advertising_report.ex | 40 ++-- 10 files changed, 154 insertions(+), 204 deletions(-) diff --git a/lib/harald/hci/arrayed_data.ex b/lib/harald/hci/arrayed_data.ex index a668ed7..a29f380 100644 --- a/lib/harald/hci/arrayed_data.ex +++ b/lib/harald/hci/arrayed_data.ex @@ -133,8 +133,7 @@ defmodule Harald.HCI.ArrayedData do # pull a tuple off the schema - recursion base case defp deserialize([], _bin, %{field: nil} = state) do - ret = for index <- 1..state.length, do: state.data[index] - {:ok, ret} + {:ok, for(index <- 1..state.length, do: state.data[index])} end # pull a tuple off the schema - defining variable lengths @@ -190,9 +189,14 @@ defmodule Harald.HCI.ArrayedData do {field_size, rest} end) - <> = bin - data = Map.update!(state.data, index, &%{&1 | state.field => parameter}) - deserialize(schema, bin, %{state | data: data, index: index + 1, variable: variable}) + case bin do + <> -> + data = Map.update!(state.data, index, &%{&1 | state.field => parameter}) + deserialize(schema, bin, %{state | data: data, index: index + 1, variable: variable}) + + _ -> + {:error, :incomplete} + end end # pull data off the binary diff --git a/lib/harald/hci/event.ex b/lib/harald/hci/event.ex index 9aa8f47..f7f64d4 100644 --- a/lib/harald/hci/event.ex +++ b/lib/harald/hci/event.ex @@ -26,11 +26,7 @@ defmodule Harald.HCI.Event do @type serialize_ret :: {:ok, binary()} | LEMeta.serialize_ret() - @type deserialize_ret :: - {:ok, t() | [t()]} - | {:error, - {:bad_event_code - | :unhandled_event_code, event_code()}} + @type deserialize_ret :: {:ok, t() | [t()]} | {:error, binary()} @doc """ A list of modules representing implemented events. @@ -45,7 +41,6 @@ defmodule Harald.HCI.Event do def indicator, do: 4 @impl Serializable - @spec serialize(term()) :: serialize_ret() def serialize(event) Enum.each(@event_modules, fn module -> @@ -58,7 +53,6 @@ defmodule Harald.HCI.Event do def serialize(event), do: {:error, {:bad_event, event}} @impl Serializable - @spec deserialize(binary()) :: deserialize_ret() def deserialize(binary) Enum.each(@event_modules, fn module -> @@ -71,9 +65,5 @@ defmodule Harald.HCI.Event do end end) - def deserialize(<> = binary) when code in 0x00..0xFF do - {:error, {:unhandled_event_code, binary}} - end - - def deserialize(binary), do: {:error, {:bad_event_code, binary}} + def deserialize(bin) when is_binary(bin), do: {:error, bin} end diff --git a/lib/harald/hci/event/le_meta/advertising_report.ex b/lib/harald/hci/event/le_meta/advertising_report.ex index 7417e73..55d9dae 100644 --- a/lib/harald/hci/event/le_meta/advertising_report.ex +++ b/lib/harald/hci/event/le_meta/advertising_report.ex @@ -93,10 +93,9 @@ defmodule Harald.HCI.Event.LEMeta.AdvertisingReport do } """ @impl Serializable - def deserialize(<<@subevent_code, arrayed_bin::binary>> = bin) do + def deserialize(<<@subevent_code, arrayed_bin::binary>>) do case Device.deserialize(arrayed_bin) do - {:ok, devices} -> {:ok, %__MODULE__{devices: devices}} - {:error, _} -> {:error, bin} + {status, devices} -> {status, %__MODULE__{devices: devices}} end end diff --git a/lib/harald/hci/event/le_meta/advertising_report/device.ex b/lib/harald/hci/event/le_meta/advertising_report/device.ex index eaeb279..0d759ed 100644 --- a/lib/harald/hci/event/le_meta/advertising_report/device.ex +++ b/lib/harald/hci/event/le_meta/advertising_report/device.ex @@ -22,7 +22,7 @@ defmodule Harald.HCI.Event.LEMeta.AdvertisingReport.Device do """ alias Harald.HCI.{ArrayedData, Event.LEMeta.AdvertisingReport.Device} - alias Harald.{DataType.ManufacturerData, Serializable, Transport.UART.Framing} + alias Harald.{DataType.ManufacturerData, Serializable} require Harald.AssignedNumbers.GenericAccessProfile, as: GenericAccessProfile @enforce_keys [:event_type, :address_type, :address, :data, :rss] @@ -53,15 +53,15 @@ defmodule Harald.HCI.Event.LEMeta.AdvertisingReport.Device do Serializes a list of `Harald.HCI.Event.LEMeta.AdvertisingReport` structs into a LE Advertising Report Event. - iex> AdvertisingReport.serialize([ - ...> %AdvertisingReport{ + iex> Device.serialize([ + ...> %Device{ ...> event_type: 0, ...> address_type: 1, ...> address: 2, ...> data: [], ...> rss: 4 ...> }, - ...> %AdvertisingReport{ + ...> %Device{ ...> event_type: 1, ...> address_type: 2, ...> address: 5, @@ -70,7 +70,7 @@ defmodule Harald.HCI.Event.LEMeta.AdvertisingReport.Device do ...> } ...> ]) {:ok, - <<2, 2, 0, 1, 1, 2, 2, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 7, 6, 32, 1, 0, 0, 0, 2, 4, 7>>} + <<2, 0, 1, 1, 2, 2, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 7, 6, 32, 1, 0, 0, 0, 2, 4, 7>>} """ @impl Serializable def serialize(devices) when is_list(devices) do @@ -79,11 +79,11 @@ defmodule Harald.HCI.Event.LEMeta.AdvertisingReport.Device do end @doc """ - Serializes a `Harald.HCI.Event.LEMeta.AdvertisingReport.Device` structs' `:data` field into AD + Serializes a `Harald.HCI.Event.LEMeta.AdvertisingData.Device` structs' `:data` field into AD Structures. iex> serialize_device_data( - ...> %AdvertisingReport.Device{ + ...> %Device{ ...> address: 1, ...> address_type: 2, ...> data: [ @@ -102,16 +102,14 @@ defmodule Harald.HCI.Event.LEMeta.AdvertisingReport.Device do ...> rss: 2 ...> } ...> ) - [ - %Harald.HCI.Event.LEMeta.AdvertisingReport.Device{ - address: 1, - address_type: 2, - data: <<25, 255, 76, 2, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 4, 0, 5, - 6, 6, 32, 8, 0, 0, 0, 9>>, - event_type: 1, - rss: 2 - } - ] + %Device{ + address: 1, + address_type: 2, + data: <<25, 255, 76, 2, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 4, 0, 5, + 6, 6, 32, 8, 0, 0, 0, 9>>, + event_type: 1, + rss: 2 + } """ def serialize_device_data(device) do Map.update!(device, :data, fn data -> @@ -149,18 +147,12 @@ defmodule Harald.HCI.Event.LEMeta.AdvertisingReport.Device do {:ok, bin} end - def serialize_advertising_data([{:error, {:bad_length, length, data}} | ads], bin) do - serialize_advertising_data(ads, <>) + def serialize_advertising_data(ads_bin, bin) when is_binary(ads_bin) do + serialize_advertising_data([], <>) end - def serialize_advertising_data([{:error, {:unknown_type, {ad_type, ad_data}}} | ads], bin) do - data = <> - length = byte_size(data) - serialize_advertising_data(ads, <>) - end - - def serialize_advertising_data([{:early_termination, 0} | ads], bin) do - serialize_advertising_data(ads, <>) + def serialize_advertising_data([ads_bin | ads], bin) when is_binary(ads_bin) do + serialize_advertising_data(ads, <>) end def serialize_advertising_data([{"Manufacturer Specific Data", data} | ads], bin) do @@ -201,28 +193,22 @@ defmodule Harald.HCI.Event.LEMeta.AdvertisingReport.Device do serialize_advertising_data(ads, <>) end - def serialize_advertising_data([{ad_type, ad_data} | ads], bin) do - data = <> - length = byte_size(data) - serialize_advertising_data(ads, <>) - end - @doc """ Deserializes a LE Advertising Report Event. - iex> AdvertisingReport.deserialize( - ...> <<2, 2, 0, 1, 1, 2, 2, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 7, 6, 32, 1, 0, 0, 0, 2, 4, + iex> Device.deserialize( + ...> <<2, 0, 1, 1, 2, 2, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 7, 6, 32, 1, 0, 0, 0, 2, 4, ...> 7>> ...> ) {:ok, [ - %AdvertisingReport{ + %Device{ event_type: 0, address_type: 1, address: 2, data: [], rss: 4 }, - %AdvertisingReport{ + %Device{ event_type: 1, address_type: 2, address: 5, @@ -232,16 +218,14 @@ defmodule Harald.HCI.Event.LEMeta.AdvertisingReport.Device do ]} """ @impl Serializable - def deserialize(<> = bin) do - @arrayed_data_schema - |> ArrayedData.deserialize(num_reports, Device, arrayed_parameters) - |> case do - {:ok, devices} -> deserialize_advertising_datas(devices) + def deserialize(<> = bin) do + case ArrayedData.deserialize(@arrayed_data_schema, num_reports, Device, tail) do + {:ok, data} -> deserialize_advertising_datas(data) {:error, _} -> {:error, bin} end end - def deserialize(bin), do: {:error, bin} + def deserialize(bin) when is_binary(bin), do: {:error, bin} @doc """ Deserializes AD Structures into basic Bluetooth data types. @@ -264,38 +248,28 @@ defmodule Harald.HCI.Event.LEMeta.AdvertisingReport.Device do # > If the Length field is set to zero, then the Data field has zero octets. This shall only # > occur to allow an early termination of the Advertising or Scan Response data. + # # Reference Version 5.0, Vol 3, Part C, 11 - def deserialize_advertising_data(<<0, data::binary>>, {status, ads}) do - deserialize_advertising_data(data, {status, [{:early_termination, 0} | ads]}) + def deserialize_advertising_data(<<0, _::binary>> = bin, {status, ads}) do + deserialize_advertising_data(<<>>, {status, [bin | ads]}) end - def deserialize_advertising_data(<>, {_, ads}) do - # AD Structure with a non-zero length and no data - deserialize_advertising_data(<<>>, {:error, [{:error, {:bad_length, length, <<>>}} | ads]}) - end - - def deserialize_advertising_data(<> <> data, {status, ads}) do - data_length = byte_size(data) - - case length > data_length do - true -> - # AD Structure with a non-zero length and not enough data - deserialize_advertising_data( - <<>>, - {:error, [{:error, {:bad_length, length, data}} | ads]} - ) - - false -> - {0, ad_structure, rest} = Framing.binary_split(data, length) + def deserialize_advertising_data( + <>, + {status, ads} + ) do + {status, ad} = + case deserialize_ads(data) do + {:ok, ad} -> {status, ad} + {:error = e, data} -> {e, data} + end - case deserialize_ads(ad_structure) do - {:ok, ad} -> - deserialize_advertising_data(rest, {status, [ad | ads]}) + deserialize_advertising_data(rest, {status, [ad | ads]}) + end - {:error, _} = error -> - deserialize_advertising_data(rest, {:error, [error | ads]}) - end - end + def deserialize_advertising_data(bin, {_, ads}) do + # AD Structure with a non-zero length and not enough data + deserialize_advertising_data(<<>>, {:error, [bin | ads]}) end @doc """ @@ -310,62 +284,47 @@ defmodule Harald.HCI.Event.LEMeta.AdvertisingReport.Device do {:ok, {"Service Data - 32-bit UUID", %{uuid: 0, data: <<1>>}}} iex> deserialize_ads(<<0xFF>>) - {:error, {"Manufacturer Specific Data", {:error, {:unhandled_company_id, <<>>}}}} - - iex> deserialize_ads(<<0x44, 1, 2, 3>>) - {:error, {:unknown_type, {0x44, <<1, 2, 3>>}}} + {:error, {"Manufacturer Specific Data", <<>>}} """ def deserialize_ads(<>) do - bin - |> ManufacturerData.deserialize() - |> case do - {:ok, data} -> {:ok, {"Manufacturer Specific Data", data}} - {:error, _} = error -> {:error, {"Manufacturer Specific Data", error}} - end + {status, data} = ManufacturerData.deserialize(bin) + {status, {"Manufacturer Specific Data", data}} end - def deserialize_ads( - <> = bin - ) do - case tail do - << - uuid::little-size(32), - data::binary - >> -> - service_data_32 = %{ - uuid: uuid, - data: data - } + def deserialize_ads(<>) do + {status, data} = + case bin do + <> -> + service_data_32 = %{ + uuid: uuid, + data: data + } - {:ok, {"Service Data - 32-bit UUID", service_data_32}} + {:ok, service_data_32} - _ -> - {:error, bin} - end - end + _ -> + {:error, bin} + end - def deserialize_ads(<>) when type in GenericAccessProfile.ids() do - {:ok, {type, bin}} + {status, {"Service Data - 32-bit UUID", data}} end - def deserialize_ads(<>), do: {:error, {:unknown_type, {type, bin}}} + def deserialize_ads(bin) when is_binary(bin), do: {:error, bin} defp deserialize_advertising_datas(devices) do - {status, devices} = - Enum.reduce(devices, {:ok, []}, fn device, {status, devices} -> - {status, device} = update_device(device, status) - {status, [device | devices]} - end) - + {status, devices} = Enum.reduce(devices, {:ok, []}, &reduce_devices(&1, &2)) {status, Enum.reverse(devices)} end - defp update_device(device, status) do - Map.get_and_update(device, :data, fn data -> - case deserialize_advertising_data(data) do - {:ok, data} -> {status, data} - {:error, data} -> {:error, data} - end - end) + defp reduce_devices(device, {status, devices}) do + {status, device} = + Map.get_and_update(device, :data, fn data -> + case deserialize_advertising_data(data) do + {:ok, data} -> {status, data} + {:error, data} -> {:error, data} + end + end) + + {status, [device | devices]} end end diff --git a/lib/harald/transport.ex b/lib/harald/transport.ex index fc46787..879c715 100644 --- a/lib/harald/transport.ex +++ b/lib/harald/transport.ex @@ -4,7 +4,7 @@ defmodule Harald.Transport do """ use GenServer - alias Harald.HCI + alias Harald.{HCI, LE} @type adapter_state :: map @type command :: binary @@ -18,26 +18,33 @@ defmodule Harald.Transport do @doc """ Start the transport. + + ## Options + + `:handlers` - additional processes to send Bluetooth events to + `:namespace` - a prefix to what the transport will register its name as + + Note: `opts` is passed through to the `init/1` call. """ @spec start_link(keyword) :: GenServer.server() - def start_link(passed_args) do - args = Keyword.put_new(passed_args, :handlers, default_handlers()) - - GenServer.start_link(__MODULE__, args, name: name(args[:namespace])) + def start_link(opts) do + GenServer.start_link(__MODULE__, opts, name: name(opts[:namespace])) end @impl GenServer - def init(args) do - {adapter, adapter_args} = args[:adapter] - {:ok, adapter_state} = apply(adapter, :setup, [self(), adapter_args]) + def init(opts) do + {adapter, adapter_opts} = opts[:adapter] + {:ok, adapter_state} = apply(adapter, :setup, [self(), adapter_opts]) + + handlers = [LE | Keyword.get(opts, :handlers, [])] - handlers = - for h <- args[:handlers] do - {:ok, pid} = apply(h, :setup, [Keyword.take(args, [:namespace])]) + handler_pids = + for h <- handlers do + {:ok, pid} = apply(h, :setup, [Keyword.take(opts, [:namespace])]) pid end - {:ok, %State{adapter: adapter, adapter_state: adapter_state, handlers: handlers}} + {:ok, %State{adapter: adapter, adapter_state: adapter_state, handlers: handler_pids}} end @doc """ @@ -50,21 +57,10 @@ defmodule Harald.Transport do |> GenServer.call({:send_command, command}) end - def name(namespace), do: String.to_atom("#{namespace}.#{__MODULE__}") - - @doc """ - The default handlers that Transport will start. - """ - @spec default_handlers() :: [Harald.LE, ...] - def default_handlers, do: [Harald.LE] - @impl GenServer def handle_info({:transport_adapter, msg}, %{handlers: handlers} = state) do - _ = - msg - |> HCI.deserialize() - |> send_to_handlers(handlers) - + {_, data} = HCI.deserialize(msg) + send_to_handlers(data, handlers) {:noreply, state} end @@ -78,17 +74,11 @@ defmodule Harald.Transport do {:reply, :ok, %State{state | adapter_state: adapter_state}} end - defp send_to_handlers({:ok, events}, handlers) when is_list(events) do - for e <- events do - for h <- handlers do - send(h, {:bluetooth_event, e}) - end - end - end - - defp send_to_handlers({:ok, event}, handlers), do: send_to_handlers({:ok, [event]}, handlers) + defp name(namespace), do: String.to_atom("#{namespace}.#{__MODULE__}") - defp send_to_handlers({:error, _} = error, handlers) do - send_to_handlers({:ok, [error]}, handlers) + defp send_to_handlers(data, handlers) do + for h <- handlers do + send(h, {:bluetooth_event, data}) + end end end diff --git a/mix.lock b/mix.lock index 0d18831..3901ce6 100644 --- a/mix.lock +++ b/mix.lock @@ -1,26 +1,26 @@ %{ "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"}, - "certifi": {:hex, :certifi, "2.4.2", "75424ff0f3baaccfd34b1214184b6ef616d89e420b258bb0a5ea7d7bc628f7f0", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, - "circuits_uart": {:hex, :circuits_uart, "1.3.0", "a489e648f358fb3c59e8dd146ead4295cec09d8abae3a0024be2fe3a0b6ddf8b", [:mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"}, - "credo": {:hex, :credo, "1.0.0", "aaa40fdd0543a0cf8080e8c5949d8c25f0a24e4fc8c1d83d06c388f5e5e0ea42", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"}, + "certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, + "circuits_uart": {:hex, :circuits_uart, "1.3.1", "8c0a56f06828133a0b08363ecb994350a9529d388a912357d31829ae617cd801", [:mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"}, + "credo": {:hex, :credo, "1.0.5", "fdea745579f8845315fe6a3b43e2f9f8866839cfbc8562bb72778e9fdaa94214", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"}, "dialyxir": {:hex, :dialyxir, "0.5.1", "b331b091720fd93e878137add264bac4f644e1ddae07a70bf7062c7862c4b952", [:mix], [], "hexpm"}, "earmark": {:hex, :earmark, "1.3.2", "b840562ea3d67795ffbb5bd88940b1bed0ed9fa32834915125ea7d02e35888a5", [:mix], [], "hexpm"}, - "elixir_make": {:hex, :elixir_make, "0.4.2", "332c649d08c18bc1ecc73b1befc68c647136de4f340b548844efc796405743bf", [:mix], [], "hexpm"}, + "elixir_make": {:hex, :elixir_make, "0.5.2", "96a28c79f5b8d34879cd95ebc04d2a0d678cfbbd3e74c43cb63a76adf0ee8054", [:mix], [], "hexpm"}, "erlex": {:hex, :erlex, "0.1.6", "c01c889363168d3fdd23f4211647d8a34c0f9a21ec726762312e08e083f3d47e", [:mix], [], "hexpm"}, - "ex_doc": {:hex, :ex_doc, "0.20.1", "88eaa16e67c505664fd6a66f42ddb962d424ad68df586b214b71443c69887123", [:mix], [{:earmark, "~> 1.3", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, - "excoveralls": {:hex, :excoveralls, "0.10.5", "7c912c4ec0715a6013647d835c87cde8154855b9b84e256bc7a63858d5f284e3", [:mix], [{:hackney, "~> 1.13", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"}, - "file_system": {:hex, :file_system, "0.2.6", "fd4dc3af89b9ab1dc8ccbcc214a0e60c41f34be251d9307920748a14bf41f1d3", [:mix], [], "hexpm"}, - "hackney": {:hex, :hackney, "1.15.0", "287a5d2304d516f63e56c469511c42b016423bcb167e61b611f6bad47e3ca60e", [:rebar3], [{:certifi, "2.4.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, + "ex_doc": {:hex, :ex_doc, "0.20.2", "1bd0dfb0304bade58beb77f20f21ee3558cc3c753743ae0ddbb0fd7ba2912331", [:mix], [{:earmark, "~> 1.3", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, + "excoveralls": {:hex, :excoveralls, "0.11.1", "dd677fbdd49114fdbdbf445540ec735808250d56b011077798316505064edb2c", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"}, + "file_system": {:hex, :file_system, "0.2.7", "e6f7f155970975789f26e77b8b8d8ab084c59844d8ecfaf58cbda31c494d14aa", [:mix], [], "hexpm"}, + "hackney": {:hex, :hackney, "1.15.1", "9f8f471c844b8ce395f7b6d8398139e26ddca9ebc171a8b91342ee15a19963f4", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"}, "makeup": {:hex, :makeup, "0.8.0", "9cf32aea71c7fe0a4b2e9246c2c4978f9070257e5c9ce6d4a28ec450a839b55f", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, "makeup_elixir": {:hex, :makeup_elixir, "0.13.0", "be7a477997dcac2e48a9d695ec730b2d22418292675c75aa2d34ba0909dcdeda", [:mix], [{:makeup, "~> 0.8", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"}, - "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"}, + "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"}, "mix_test_watch": {:hex, :mix_test_watch, "0.9.0", "c72132a6071261893518fa08e121e911c9358713f62794a90c95db59042af375", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm"}, "nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm"}, "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"}, - "stream_data": {:hex, :stream_data, "0.4.2", "fa86b78c88ec4eaa482c0891350fcc23f19a79059a687760ddcf8680aac2799b", [:mix], [], "hexpm"}, + "stream_data": {:hex, :stream_data, "0.4.3", "62aafd870caff0849a5057a7ec270fad0eb86889f4d433b937d996de99e3db25", [:mix], [], "hexpm"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"}, } diff --git a/test/harald/hci/event/le_meta/advertising_report_test.exs b/test/harald/hci/event/le_meta/advertising_report_test.exs index 7783704..b7b98fd 100644 --- a/test/harald/hci/event/le_meta/advertising_report_test.exs +++ b/test/harald/hci/event/le_meta/advertising_report_test.exs @@ -9,8 +9,12 @@ defmodule Harald.HCI.Event.LEMeta.AdvertisingReportTest do property "symmetric (de)serialization" do check all parameters <- AdvertisingReportGen.parameters() do case AdvertisingReport.deserialize(parameters) do - {:ok, data} -> assert {:ok, parameters} == AdvertisingReport.serialize(data) - {:error, _} -> :ok + {:ok, data} -> + assert {:ok, bin} = AdvertisingReport.serialize(data) + assert :binary.bin_to_list(parameters) == :binary.bin_to_list(bin) + + {:error, _} -> + :ok end end diff --git a/test/harald/hci_test.exs b/test/harald/hci_test.exs index a9bb725..ebd9fd1 100644 --- a/test/harald/hci_test.exs +++ b/test/harald/hci_test.exs @@ -52,8 +52,12 @@ defmodule Harald.HCITest do property "symmetric (de)serialization" do check all parameters <- HCIGen.packet() do case HCI.deserialize(parameters) do - {:ok, data} -> assert {:ok, parameters} == HCI.serialize(data) - {:error, _} -> :ok + {:ok, data} -> + assert {:ok, bin} = HCI.serialize(data) + assert :binary.bin_to_list(parameters) == :binary.bin_to_list(bin) + + {:error, _} -> + :ok end end diff --git a/test/support/generators/hci/event.ex b/test/support/generators/hci/event.ex index d87004a..c2d26b4 100644 --- a/test/support/generators/hci/event.ex +++ b/test/support/generators/hci/event.ex @@ -7,7 +7,7 @@ defmodule Harald.Generators.HCI.Event do use ExUnitProperties alias Harald.Generators - alias Harald.HCI.{Event, Event.LEMeta} + alias Harald.HCI.Event @doc """ Returns a partial HCI Event packet binary for a random event, everything besides the HCI packet diff --git a/test/support/generators/hci/event/le_meta/advertising_report.ex b/test/support/generators/hci/event/le_meta/advertising_report.ex index 530c9ed..90fa057 100644 --- a/test/support/generators/hci/event/le_meta/advertising_report.ex +++ b/test/support/generators/hci/event/le_meta/advertising_report.ex @@ -11,31 +11,16 @@ defmodule Harald.Generators.HCI.Event.LEMeta.AdvertisingReport do defp calc_max_data_size(num_reports), do: 253 - 10 * num_reports @spec parameters :: no_return() + # credo:disable-for-next-line def parameters do - array_range = fn range, num_reports -> - bind( - list_of(map(integer(range), &<<&1>>), length: num_reports), - fn list -> constant(Enum.join(list)) end - ) - end - - gen_data = fn len, max -> - gen all raw_list <- list_of(integer(0x00..0x1F), length: len), - list = dampen_list(raw_list, max), - length_datas = Enum.into(list, <<>>, &<<&1>>), - datas = Enum.into(list, <<>>, &Enum.at(binary(length: &1), 0)) do - {length_datas, datas} - end - end - gen all num_reports <- integer(0x01..0x19), max_data_size = calc_max_data_size(num_reports), - {length_datas, datas} <- gen_data.(num_reports, max_data_size), - event_types <- array_range.(0x00..0x04, num_reports), - address_types <- array_range.(0x00..0x03, num_reports), + {length_datas, datas} <- gen_data(num_reports, max_data_size), + event_types <- array_range(0x00..0x04, num_reports), + address_types <- array_range(0x00..0x03, num_reports), address_list <- list_of(binary(length: 6), length: num_reports), addresses = Enum.join(address_list), - rss <- array_range.(-127..126, num_reports) do + rss <- array_range(-127..126, num_reports) do << AdvertisingReport.subevent_code(), num_reports, @@ -49,6 +34,21 @@ defmodule Harald.Generators.HCI.Event.LEMeta.AdvertisingReport do end end + defp array_range(range, num_reports) do + bind(list_of(map(integer(range), &<<&1>>), length: num_reports), fn list -> + constant(Enum.join(list)) + end) + end + + defp gen_data(len, max) do + gen all raw_list <- list_of(integer(0x00..0x1F), length: len), + list = dampen_list(raw_list, max), + length_datas = Enum.into(list, <<>>, &<<&1>>), + datas = Enum.into(list, <<>>, &Enum.at(binary(length: &1), 0)) do + {length_datas, datas} + end + end + defp dampen_list(list, max) do if Enum.sum(list) <= max do list