Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Increase data structures handled by scans #39

Merged
merged 1 commit into from
May 27, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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