Packages

Bindings for the D-Bus IPC protocol.

Current section

Files

Jump to
dbux lib type.ex
Raw

lib/type.ex

defmodule DBux.Type do
@type simple_type_name :: :byte | :boolean | :int16 | :uint16 | :int32 | :uint32 | :int64 | :uint64 | :double | :string | :object_path | :signature | :unix_fd
@type container_type_name :: :array | :struct | :variant | :dict_entry
@type simple_type :: simple_type_name
@type container_type :: {container_type_name, [simple_type | container_type]}
@type t :: simple_type | container_type
@type list_of_types :: [] | [t]
@doc """
Returns bitstring that contains 1-byte D-Bus signature of given type.
Reverse function is `type/1`.
"""
@spec signature(simple_type | :variant) :: String.t
def signature(:byte), do: "y"
def signature(:boolean), do: "b"
def signature(:int16), do: "n"
def signature(:uint16), do: "q"
def signature(:int32), do: "i"
def signature(:uint32), do: "u"
def signature(:int64), do: "x"
def signature(:uint64), do: "t"
def signature(:double), do: "d"
def signature(:string), do: "s"
def signature(:object_path), do: "o"
def signature(:signature), do: "g"
def signature(:unix_fd), do: "h"
def signature(:variant), do: "v"
def signature(%DBux.Value{type: :array, subtype: [subtype]}) do
"a" <> signature(subtype)
end
def signature(%DBux.Value{type: :struct, subtype: subtypes}) when is_list(subtypes) do
"(" <> Enum.map(subtypes, fn(subtype) -> signature(subtype) end) <> ")"
end
def signature(%DBux.Value{type: :dict_entry, subtype: subtypes}) when is_list(subtypes) do
"{" <> Enum.map(subtypes, fn(subtype) -> signature(subtype) end) <> "}"
end
def signature(%DBux.Value{type: type}), do: signature(type)
@doc """
Returns atom that contains atom identifying type.
Reverse function is `signature/1`.
"""
@spec type(String.t) :: simple_type | :variant
def type("y"), do: :byte
def type("b"), do: :boolean
def type("n"), do: :int16
def type("q"), do: :uint16
def type("i"), do: :int32
def type("u"), do: :uint32
def type("x"), do: :int64
def type("t"), do: :uint64
def type("d"), do: :double
def type("s"), do: :string
def type("o"), do: :object_path
def type("g"), do: :signature
def type("h"), do: :unix_fd
def type("v"), do: :variant
@doc """
Parses signature in D-Bus format and returns it as a nested list in which
simple types are represented as atoms and container types as tuples.
For example, "yba{s(ui)}" will become `[:byte, :boolean, {:array, [{:dict, [:string, {:struct, [:uint32, :int32]}]}]}]`.
First of all, it is much more convenient to have such structure if you want
to recursively parse signature in Elixir, so it is used internally while
demarshalling messages. It can also serve as validator for signatures.
It returns `{:ok, list}` in case of success, `{:error, reason}` otherwise.
It does most of the checks from the specification, but it does not check
for dicts constraints at the moment.
"""
@spec type_from_signature(String.t) :: list_of_types
def type_from_signature(""), do: {:ok, []}
def type_from_signature(signature) when is_binary(signature) do
parse(signature, [])
end
@doc """
Returns alignment size for given D-Bus type.
"""
@spec align_size(simple_type_name | container_type_name) :: number
def align_size(:byte), do: 1
def align_size(:boolean), do: 4
def align_size(:int16), do: 2
def align_size(:uint16), do: 2
def align_size(:int32), do: 4
def align_size(:uint32), do: 4
def align_size(:int64), do: 8
def align_size(:uint64), do: 8
def align_size(:double), do: 8
def align_size(:string), do: 4
def align_size(:object_path), do: 4
def align_size(:signature), do: 1
def align_size(:array), do: 4
def align_size(:struct), do: 8
def align_size(:variant), do: 1
def align_size(:dict_entry), do: 8
def align_size(:unix_fd), do: 4
def align_size({subtype_major, _subtype_minor}), do: align_size(subtype_major)
# Computes padding size for container types.
# It just takes container type, and ignores inner type.
@doc false
def compute_padding_size(length, type) when is_tuple(type) do
{subtype_major, _} = type
compute_padding_size(length, subtype_major)
end
# Computes padding size for a type, given data length and type name.
@doc false
def compute_padding_size(length, type) when is_atom(type) do
compute_padding_size(length, DBux.Type.align_size(type))
end
# Computes padding size for a type, given data length and target padding.
@doc false
def compute_padding_size(length, align) when is_number(align) do
padding = rem(length, align)
case padding do
0 -> 0
_ -> align - padding
end
end
# ------ TOP LEVEL ------
# Top level: End of signature, return
defp parse(<< >>, acc) do
{:ok, acc}
end
# Top level: Enter inner recurrence for struct
defp parse(<< "(", rest :: binary >>, acc) do
case parse_struct(rest, []) do
{:ok, value_parsed, rest_after_parse} ->
parse(rest_after_parse, acc ++ [value_parsed])
{:error, reason} ->
{:error, reason}
end
end
# Top level: Got struct closing token but it wasn't opened
defp parse(<< ")", _rest :: binary >>, _acc) do
{:error, {:badsignature, :unmatchedstruct}}
end
# Top level: Attempt to enter inner recurrence for dict without enclosing array
defp parse(<< "{", _rest :: binary >>, _acc) do
{:error, {:badsignature, :unwrappeddict}}
end
# Top level: Got dict closing token but it wasn't opened
defp parse(<< "}", _rest :: binary >>, _acc) do
{:error, {:badsignature, :unmatcheddict}}
end
# Top level: Enter inner recurrence for array
defp parse(<< "a", rest :: binary >>, acc) do
case parse_array(rest, []) do
{:ok, value_parsed, rest_after_parse} ->
parse(rest_after_parse, acc ++ [value_parsed])
{:error, reason} ->
{:error, reason}
end
end
# Top level: Simple types
defp parse(<< head :: binary-size(1), rest :: binary >>, acc) do
parse(rest, acc ++ [type(head)])
end
# ------ STRUCT ------
# Within struct: Enter inner recurrence for another struct
defp parse_struct(<< "(", rest :: binary >>, acc) do
case parse_struct(rest, []) do
{:ok, value_parsed, rest_after_parse} ->
parse_struct(rest_after_parse, acc ++ [value_parsed])
{:error, reason} ->
{:error, reason}
end
end
# Within struct: Closing empty struct
defp parse_struct(<< ")", _rest :: binary >>, []) do
{:error, {:badsignature, :emptystruct}}
end
# Within struct: Closing non-empty struct, return
defp parse_struct(<< ")", rest :: binary >>, acc) do
{:ok, {:struct, acc}, rest}
end
# Within struct: Attempt to enter inner recurrence for dict without enclosing array
defp parse_struct(<< "{", _rest :: binary >>, _acc) do
{:error, {:badsignature, :unwrappeddict}}
end
# Within struct: Got dict closing token but it wasn't opened
defp parse_struct(<< "}", _rest :: binary >>, _acc) do
{:error, {:badsignature, :unmatcheddict}}
end
# Within struct: Enter inner recurrence for array
defp parse_struct(<< "a", rest :: binary >>, acc) do
case parse_array(rest, []) do
{:ok, value_parsed, rest_after_parse} ->
parse_struct(rest_after_parse, acc ++ [value_parsed])
{:error, reason} ->
{:error, reason}
end
end
# Within struct: Struct has no contents
defp parse_struct(<< >>, _acc) do
{:error, {:badsignature, :unmatchedstruct}}
end
# Within struct: Simple types
defp parse_struct(<< head :: binary-size(1), rest :: binary >>, acc) do
parse_struct(rest, acc ++ [type(head)])
end
# ------ DICT ------
# Within dict: Attempt to enter inner recurrence for dict without enclosing array
defp parse_dict(<< "{", _rest :: binary >>, _acc) do
{:error, {:badsignature, :unwrappeddict}}
end
# Within dict: Closing empty dict
defp parse_dict(<< "}", _rest :: binary >>, []) do
{:error, {:badsignature, :emptydict}}
end
# Within dict: Closing non-empty dict, return
defp parse_dict(<< "}", rest :: binary >>, acc) do
{:ok, {:dict, acc}, rest}
end
# Within dict: Enter inner recurrence for struct
defp parse_dict(<< "(", rest :: binary >>, acc) do
case parse_struct(rest, []) do
{:ok, value_parsed, rest_after_parse} ->
parse_dict(rest_after_parse, acc ++ [value_parsed])
{:error, reason} ->
{:error, reason}
end
end
# Within dict: Got struct closing token but it wasn't opened
defp parse_dict(<< ")", _rest :: binary >>, _acc) do
{:error, {:badsignature, :unmatchedstruct}}
end
# Within dict: Enter inner recurrence for array
defp parse_dict(<< "a", rest :: binary >>, acc) do
case parse_array(rest, []) do
{:ok, value_parsed, rest_after_parse} ->
parse_dict(rest_after_parse, acc ++ [value_parsed])
{:error, reason} ->
{:error, reason}
end
end
# Within dict: Dict has no contents
defp parse_dict(<< >>, _acc) do
{:error, {:badsignature, :unmatcheddict}}
end
# Within dict: Simple types
defp parse_dict(<< head :: binary-size(1), rest :: binary >>, acc) do
parse_dict(rest, acc ++ [type(head)])
end
# ------ ARRAY ------
# Within array: Enter inner recurrence for struct
defp parse_array(<< "(", rest :: binary >>, acc) do
case parse_struct(rest, []) do
{:ok, value_parsed, rest_after_parse} ->
{:ok, {:array, acc ++ [value_parsed]}, rest_after_parse}
{:error, reason} ->
{:error, reason}
end
end
# Within array: Enter inner recurrence for dict
defp parse_array(<< "{", rest :: binary >>, acc) do
case parse_dict(rest, []) do
{:ok, value_parsed, rest_after_parse} ->
{:ok, {:array, acc ++ [value_parsed]}, rest_after_parse}
{:error, reason} ->
{:error, reason}
end
end
# Within array: Got struct closing token but it wasn't opened
defp parse_array(<< ")", _rest :: binary >>, _acc) do
{:error, {:badsignature, :unmatchedstruct}}
end
# Within array: Got dict closing token but it wasn't opened
defp parse_array(<< "}", _rest :: binary >>, _acc) do
{:error, {:badsignature, :unmatcheddict}}
end
# Within array: Enter inner recurrence for another array
defp parse_array(<< "a", rest :: binary >>, acc) do
case parse_array(rest, []) do
{:ok, value_parsed, rest_after_parse} ->
parse_array(rest_after_parse, acc ++ [value_parsed])
{:error, reason} ->
{:error, reason}
end
end
# Within array: Empty array
defp parse_array(<< >>, []) do
{:error, {:badsignature, :emptyarray}}
end
# Within array: Container types, return
defp parse_array(<< >>, acc) do
{:ok, {:array, acc}, << >>}
end
# Within array: Simple types, return
defp parse_array(<< head :: binary-size(1), rest :: binary >>, acc) do
{:ok, {:array, acc ++ [type(head)]}, rest}
end
end