Skip to content

Commit

Permalink
Increase data structures handled by scans
Browse files Browse the repository at this point in the history
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
  • Loading branch information
danielspofford committed May 26, 2019
1 parent e591240 commit ee4b4b4
Show file tree
Hide file tree
Showing 10 changed files with 154 additions and 204 deletions.
14 changes: 9 additions & 5 deletions lib/harald/hci/arrayed_data.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -190,9 +189,14 @@ defmodule Harald.HCI.ArrayedData do
{field_size, rest}
end)

<<parameter::binary-size(field_size), bin::binary>> = 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
<<parameter::binary-size(field_size), bin::binary>> ->
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
Expand Down
14 changes: 2 additions & 12 deletions lib/harald/hci/event.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 ->
Expand All @@ -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 ->
Expand All @@ -71,9 +65,5 @@ defmodule Harald.HCI.Event do
end
end)

def deserialize(<<code, _::binary>> = 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
5 changes: 2 additions & 3 deletions lib/harald/hci/event/le_meta/advertising_report.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
185 changes: 72 additions & 113 deletions lib/harald/hci/event/le_meta/advertising_report/device.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand All @@ -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: [
Expand All @@ -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 ->
Expand Down Expand Up @@ -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, <<bin::bits, length, data::bits>>)
def serialize_advertising_data(ads_bin, bin) when is_binary(ads_bin) do
serialize_advertising_data([], <<bin::bits, ads_bin::binary>>)
end

def serialize_advertising_data([{:error, {:unknown_type, {ad_type, ad_data}}} | ads], bin) do
data = <<ad_type, ad_data::bits>>
length = byte_size(data)
serialize_advertising_data(ads, <<bin::bits, length, data::bits>>)
end

def serialize_advertising_data([{:early_termination, 0} | ads], bin) do
serialize_advertising_data(ads, <<bin::bits, 0>>)
def serialize_advertising_data([ads_bin | ads], bin) when is_binary(ads_bin) do
serialize_advertising_data(ads, <<bin::bits, ads_bin::binary>>)
end

def serialize_advertising_data([{"Manufacturer Specific Data", data} | ads], bin) do
Expand Down Expand Up @@ -201,28 +193,22 @@ defmodule Harald.HCI.Event.LEMeta.AdvertisingReport.Device do
serialize_advertising_data(ads, <<bin::bits, length, service_data_32::binary>>)
end

def serialize_advertising_data([{ad_type, ad_data} | ads], bin) do
data = <<ad_type, ad_data::bits>>
length = byte_size(data)
serialize_advertising_data(ads, <<bin::bits, length, data::bits>>)
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,
Expand All @@ -232,16 +218,14 @@ defmodule Harald.HCI.Event.LEMeta.AdvertisingReport.Device do
]}
"""
@impl Serializable
def deserialize(<<num_reports, arrayed_parameters::binary>> = bin) do
@arrayed_data_schema
|> ArrayedData.deserialize(num_reports, Device, arrayed_parameters)
|> case do
{:ok, devices} -> deserialize_advertising_datas(devices)
def deserialize(<<num_reports, tail::binary>> = 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.
Expand All @@ -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(<<length>>, {_, 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(<<length>> <> 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(
<<length, data::binary-size(length), rest::binary>>,
{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 """
Expand All @@ -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(<<GenericAccessProfile.id("Manufacturer Specific Data"), bin::binary>>) 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(
<<GenericAccessProfile.id("Service Data - 32-bit UUID"), tail::binary>> = bin
) do
case tail do
<<
uuid::little-size(32),
data::binary
>> ->
service_data_32 = %{
uuid: uuid,
data: data
}
def deserialize_ads(<<GenericAccessProfile.id("Service Data - 32-bit UUID"), bin::binary>>) do
{status, data} =
case bin do
<<uuid::little-size(32), data::binary>> ->
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(<<type, bin::bits>>) when type in GenericAccessProfile.ids() do
{:ok, {type, bin}}
{status, {"Service Data - 32-bit UUID", data}}
end

def deserialize_ads(<<type, bin::bits>>), 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
Loading

0 comments on commit ee4b4b4

Please sign in to comment.