Packages

Bindings for the D-Bus IPC protocol.

Current section

Files

Jump to
dbux lib protocol.ex
Raw

lib/protocol.ex

defmodule DBux.Protocol do
require Logger
@type endianness :: :little_endian | :big_endian
@debug !is_nil(System.get_env("DBUX_DEBUG"))
@doc """
Unmarshalls given bitstring while interpreting data using given endianness
and signature (where signature comes in D-Bus format).
It returns `{:ok, {list_of_values, rest}}` on success.
If `unwrap_values` is `false`, `list_of_values` will contain a nested list of
`DBux.Value` structs, otherwise it will contain plain values.
For example, signature "i(ss)" will map to the following result:
`[%DBux.Value{type: :int32, value: 123},
%DBux.Value{type: :struct, value: [
%DBux.Value{type: :string, value: "sample1"},
%DBux.Value{type: :string, value: "sample2"}]]`
if unwrap_values is set to `false`, but it will map to:
`[123, {"sample1", "sample2"}]` if it is set to `true`.
`rest` will contain remaining part of the bitstring.
It returns `{:error, reason}` in case of failure.
Specifically, it returns `{:error, :bitstring_too_short}` if given bitstring
was not long enough.
"""
@spec unmarshall_bitstring(Bitstring, endianness, String.t, boolean) :: {:ok, DBux.Value.list_of_values, Bitstring} | {:error, any}
def unmarshall_bitstring(bitstring, endianness, signature, unwrap_values) when is_binary(bitstring) and is_atom(endianness) and is_binary(signature) do
case DBux.Type.type_from_signature(signature) do
{:ok, signature_as_list} ->
unmarshall_bitstring_step(bitstring, endianness, signature_as_list, unwrap_values, [], 0)
{:error, reason} ->
{:error, reason}
end
end
@doc """
Unmarshalls given bitstring while interpreting data using given endianness
and signature (where signature comes in as nested list of types, such as
one generated by `DBux.Type.type_from_signature/1`).
It handles padding between values if they require some alignment.
It returns `{:ok, {list_of_values, rest}}` on success.
If `unwrap_values` is `false`, `list_of_values` will contain a nested list of
`DBux.Value` structs, otherwise it will contain plain values.
For example, signature "i(ss)" will map to the following result:
`[%DBux.Value{type: :int32, value: 123},
%DBux.Value{type: :struct, value: [
%DBux.Value{type: :string, value: "sample1"},
%DBux.Value{type: :string, value: "sample2"}]]`
if unwrap_values is set to `false`, but it will map to:
`[123, {"sample1", "sample2"}]` if it is set to `true`.
`rest` will contain remaining part of the bitstring.
It returns `{:error, reason}` in case of failure.
Specifically, it returns `{:error, :bitstring_too_short}` if given bitstring
was not long enough.
"""
@spec unmarshall_bitstring(Bitstring, endianness, DBux.Type.list_of_types, boolean) :: {:ok, DBux.Value.list_of_values, Bitstring} | {:error, any}
def unmarshall_bitstring(bitstring, endianness, signature, unwrap_values) when is_binary(bitstring) and is_atom(endianness) and is_list(signature) do
unmarshall_bitstring_step(bitstring, endianness, signature, unwrap_values, [], 0)
end
@doc """
Marshalls given list of values using given endianness.
It returns `{:ok, {bitstring, signature}}`.
It applies padding between values if they require some alignment.
"""
@spec marshall_bitstring(DBux.Value.list_of_values, endianness) :: {:ok, {Bitstring, String.t}}
def marshall_bitstring(values, endianness) when is_list(values) and is_atom(endianness) do
marshall_bitstring_step(values, endianness, << >>, << >>)
end
defp marshall_bitstring_step([], _endianness, bitstring_acc, signature_acc) do
{:ok, {bitstring_acc, signature_acc}}
end
defp marshall_bitstring_step([head|tail], endianness, bitstring_acc, signature_acc) do
{:ok, {bitstring_with_padding, _padding}} = bitstring_acc |> DBux.Value.align(head.type)
case DBux.Value.marshall(head, endianness) do
{:ok, {bitstring_value, _padding}} ->
marshall_bitstring_step(tail, endianness, bitstring_with_padding <> bitstring_value, signature_acc <> DBux.Type.signature(head))
end
end
defp unmarshall_bitstring_step(bitstring, _endianness, [], _unwrap_values, values_acc, _position_acc) do
{:ok, {values_acc, bitstring}}
end
defp unmarshall_bitstring_step(<< >>, _endianness, [_signature_head|_signature_rest], _unwrap_values, _values_acc, _position_acc) do
{:error, :bitstring_too_short}
end
defp unmarshall_bitstring_step(bitstring, endianness, [signature_head|signature_rest], unwrap_values, values_acc, position_acc) do
if @debug, do: Logger.debug("unmarshall_bitstring_step: bitstring = #{inspect(bitstring)}, signature_head = #{inspect(signature_head)}, values_acc = #{inspect(values_acc)}, position_acc = #{inspect(position_acc)}")
case signature_head do
{subtype_major, subtype_minor} ->
padding_size = DBux.Type.compute_padding_size(position_acc, subtype_major)
<< padding :: binary-size(padding_size), rest_after_padding :: binary >> = bitstring
if @debug, do: Logger.debug("unmarshall_bitstring_step: padding = #{inspect(padding)}, rest_after_padding = #{inspect(rest_after_padding)}")
case DBux.Value.unmarshall(rest_after_padding, endianness, subtype_major, subtype_minor, unwrap_values, 0) do
{:ok, {value, rest_after_parse}} ->
unmarshall_bitstring_step(rest_after_parse, endianness, signature_rest, unwrap_values, values_acc ++ [value], position_acc + (byte_size(bitstring) - byte_size(rest_after_parse)))
{:error, reason} ->
{:error, reason}
end
_ ->
padding_size = DBux.Type.compute_padding_size(position_acc, signature_head)
<< padding :: binary-size(padding_size), rest_after_padding :: binary >> = bitstring
if @debug, do: Logger.debug("unmarshall_bitstring_step: padding = #{inspect(padding)}, rest_after_padding = #{inspect(rest_after_padding)}")
case DBux.Value.unmarshall(rest_after_padding, endianness, signature_head, nil, unwrap_values, 0) do
{:ok, {value, rest_after_parse}} ->
unmarshall_bitstring_step(rest_after_parse, endianness, signature_rest, unwrap_values, values_acc ++ [value], position_acc + (byte_size(bitstring) - byte_size(rest_after_parse)))
{:error, reason} ->
{:error, reason}
end
end
end
end