Packages

Chatter is extracted from the ScaleSmall project as a standalone piece. This may be used independently to broadcast messages to a set of nodes. It uses a mixture of UDP multicast and TCP to deliver messages and tries to minimize network traffic while doing so.

Current section

Files

Jump to
chatter lib net_id.ex
Raw

lib/net_id.ex

defmodule Chatter.NetID do
require Record
alias Chatter.Serializer
Record.defrecord :net_id,
ip: nil,
port: 0
@type t :: record( :net_id,
ip: tuple,
port: integer )
defmacro is_valid_port(data) do
case Macro.Env.in_guard?(__CALLER__) do
true ->
quote do
is_integer(unquote(data)) and
unquote(data) >= 0 and
unquote(data) <= 0xffff
end
false ->
quote bind_quoted: binding() do
is_integer(data) and
data >= 0 and
data <= 0xffff
end
end
end
defmacro is_valid_ip(ip) do
case Macro.Env.in_guard?(__CALLER__) do
true ->
quote do
is_tuple(unquote(ip)) and
tuple_size(unquote(ip)) == 4 and
is_integer(:erlang.element(1,unquote(ip))) and
is_integer(:erlang.element(2,unquote(ip))) and
is_integer(:erlang.element(3,unquote(ip))) and
is_integer(:erlang.element(4,unquote(ip))) and
:erlang.element(1,unquote(ip)) >= 0 and
:erlang.element(2,unquote(ip)) >= 0 and
:erlang.element(3,unquote(ip)) >= 0 and
:erlang.element(4,unquote(ip)) >= 0 and
:erlang.element(1,unquote(ip)) < 256 and
:erlang.element(2,unquote(ip)) < 256 and
:erlang.element(3,unquote(ip)) < 256 and
:erlang.element(4,unquote(ip)) < 256
end
false ->
quote bind_quoted: binding() do
is_tuple(ip) and
tuple_size(ip) == 4 and
is_integer(:erlang.element(1,ip)) and
is_integer(:erlang.element(2,ip)) and
is_integer(:erlang.element(3,ip)) and
is_integer(:erlang.element(4,ip)) and
:erlang.element(1,ip) >= 0 and
:erlang.element(2,ip) >= 0 and
:erlang.element(3,ip) >= 0 and
:erlang.element(4,ip) >= 0 and
:erlang.element(1,ip) < 256 and
:erlang.element(2,ip) < 256 and
:erlang.element(3,ip) < 256 and
:erlang.element(4,ip) < 256
end
end
end
@spec new(tuple, integer) :: t
def new(ip, port)
# TODO : IPV6
when is_valid_ip(ip) and
is_valid_port(port)
do
net_id(ip: ip) |> net_id(port: port)
end
defmacro is_valid(data) do
case Macro.Env.in_guard?(__CALLER__) do
true ->
quote do
is_tuple(unquote(data)) and tuple_size(unquote(data)) == 3 and
:erlang.element(1, unquote(data)) == :net_id and
# ip
# TODO : IPV6
is_tuple(:erlang.element(2, unquote(data))) and
tuple_size(:erlang.element(2, unquote(data))) == 4 and
# port
is_integer(:erlang.element(3, unquote(data))) and
:erlang.element(3, unquote(data)) >= 0 and
:erlang.element(3, unquote(data)) <= 0xffff
end
false ->
quote bind_quoted: binding() do
is_tuple(data) and tuple_size(data) == 3 and
:erlang.element(1, data) == :net_id and
# ip
# TODO : IPV6
is_tuple(:erlang.element(2, data)) and
tuple_size(:erlang.element(2, data)) == 4 and
# port
is_integer(:erlang.element(3, data)) and
:erlang.element(3, data) >= 0 and
:erlang.element(3, data) <= 0xffff
end
end
end
@spec valid?(t) :: boolean
def valid?(data)
when is_valid(data)
do
true
end
def valid?(_), do: false
@spec validate_list(list(t)) :: :ok | :error
def validate_list([]), do: :ok
def validate_list([head|rest])
do
case valid?(head) do
true -> validate_list(rest)
false -> :error
end
end
@spec validate_list!(list(t)) :: :ok
def validate_list!([]), do: :ok
def validate_list!([head|rest])
when is_valid(head)
do
validate_list!(rest)
end
@spec ip(t) :: tuple
def ip(id)
when is_valid(id)
do
net_id(id, :ip)
end
@spec port(t) :: integer
def port(id)
when is_valid(id)
do
net_id(id, :port)
end
@spec encode(t) :: binary
def encode(id)
when is_valid(id)
do
{ip1, ip2, ip3, ip4} = net_id(id, :ip)
<< ip1 :: size(8), ip2 :: size(8), ip3 :: size(8), ip4 :: size(8), net_id(id, :port) :: big-size(16) >>
end
@spec encode_list(list(t)) :: binary
def encode_list(ids)
when is_list(ids)
do
List.foldl(ids, Serializer.encode_uint(length(ids)), fn(id, acc) ->
acc <> encode(id)
end)
end
@spec encode_list_with(list(t), map) :: binary
def encode_list_with(ids, id_map)
when is_list(ids) and
is_map(id_map)
do
:ok = validate_list!(ids)
bin_size = ids |> length |> Serializer.encode_uint
bin_list = ids |> Enum.reduce(<<>>, fn(x,acc) ->
id = Map.fetch!(id_map, x)
acc <> Serializer.encode_uint(id)
end)
<< bin_size :: binary,
bin_list :: binary >>
end
@spec decode(binary) :: {t, binary}
def decode(<< ip1 :: size(8), ip2 :: size(8), ip3 :: size(8), ip4 :: size(8), port :: big-size(16) >>)
when is_valid_port(port) and
is_valid_ip({ip1, ip2, ip3, ip4})
do
{net_id(ip: {ip1, ip2, ip3, ip4}) |> net_id(port: port), <<>>}
end
def decode(<< ip1 :: size(8), ip2 :: size(8), ip3 :: size(8), ip4 :: size(8), port :: big-size(16), remaining :: binary >>)
when is_valid_port(port) and
is_valid_ip({ip1, ip2, ip3, ip4})
do
{net_id(ip: {ip1, ip2, ip3, ip4}) |> net_id(port: port), remaining}
end
@spec decode_list(binary) :: {list(t), binary}
def decode_list(bin)
do
{count, remaining} = Serializer.decode_uint(bin)
{list, remaining} = decode_list_(remaining, count, [])
{Enum.reverse(list), remaining}
end
defp decode_list_(<<>>, _count, acc), do: {acc, <<>>}
defp decode_list_(binary, 0, acc), do: {acc, binary}
defp decode_list_(msg, count, acc)
when is_binary(msg) and
is_integer(count) and
count > 0 and
is_list(acc)
do
{id, remaining} = decode(msg)
decode_list_(remaining, count-1, [id | acc])
end
@spec decode_list_with(binary, map) :: {list(t), binary}
def decode_list_with(bin, id_map)
do
{count, remaining} = Serializer.decode_uint(bin)
{list, remaining} = decode_list_with_(remaining, count, [], id_map)
{Enum.reverse(list), remaining}
end
defp decode_list_with_(<<>>, _count, acc, _map), do: {acc, <<>>}
defp decode_list_with_(binary, 0, acc, _map), do: {acc, binary}
defp decode_list_with_(msg, count, acc, map)
when is_binary(msg) and
is_integer(count) and
count > 0 and
is_list(acc) and
is_map(map)
do
{id, remaining} = Serializer.decode_uint(msg)
netid = Map.fetch!(map, id)
decode_list_with_(remaining, count-1, [netid | acc], map)
end
end