1%%% @doc
2%%% Codec for `inet', `cidr'
3%%% https://www.postgresql.org/docs/10/static/datatype-net-types.html
4%%% $PG$/src/backend/utils/adt/network.c
5%%%
6%%% TIP: use `inet:ntoa/1' to convert `ip()' to string.
7%%% @end
8%%% Created : 12 Oct 2017 by Sergey Prokhorov <me@seriyps.ru>
9-module(epgsql_codec_net).
10-behaviour(epgsql_codec).
11
12-export([init/2, names/0, encode/3, decode/3, decode_text/3]).
13
14-export_type([data/0]).
15
16-type data() :: ip() | ip_mask() | macaddr() | macaddr8().
17
18-type ip() :: inet:ip_address().
19-type mask() :: 0..32.
20-type ip_mask() :: {ip(), mask()}.
21-type macaddr() :: {byte(), byte(), byte(), byte(), byte(), byte()}.
22-type macaddr8() :: {byte(), byte(), byte(), byte(), byte(), byte(), byte(), byte()}.
23
24-define(INET, 2).
25-define(INET6, 3).
26-define(IP_SIZE, 4).
27-define(IP6_SIZE, 16).
28-define(MAX_IP_MASK, 32).
29-define(MAX_IP6_MASK, 128).
30
31init(_, _) -> [].
32
33names() ->
34    [inet, cidr, macaddr, macaddr8].
35
36encode({B1, B2, B3, B4, B5, B6}, macaddr, _) ->
37    <<B1, B2, B3, B4, B5, B6>>;
38encode({B1, B2, B3, B4, B5, B6, B7, B8}, macaddr8, _) ->
39    <<B1, B2, B3, B4, B5, B6, B7, B8>>;
40encode(IpMask, _, _) ->
41    encode_net(IpMask).
42
43decode(<<B1, B2, B3, B4, B5, B6>>, macaddr, _) ->
44    {B1, B2, B3, B4, B5, B6};
45decode(<<B1, B2, B3, B4, B5, B6, B7, B8>>, macaddr8, _) ->
46    {B1, B2, B3, B4, B5, B6, B7, B8};
47decode(Bin, _, _) ->
48    decode_net(Bin).
49
50-spec encode_net(ip() | ip_mask()) -> binary().
51encode_net({{_, _, _, _} = IP, Mask}) ->
52    Bin = list_to_binary(tuple_to_list(IP)),
53    <<?INET, Mask:8, 1, ?IP_SIZE, Bin/binary>>;
54encode_net({{_, _, _, _, _, _, _, _} = IP, Mask}) ->
55    Bin = << <<X:16>> || X <- tuple_to_list(IP) >>,
56    <<?INET6, Mask:8, 1, ?IP6_SIZE, Bin/binary>>;
57encode_net({_, _, _, _} = IP) ->
58    Bin = list_to_binary(tuple_to_list(IP)),
59    <<?INET, ?MAX_IP_MASK, 0, ?IP_SIZE, Bin/binary>>;
60encode_net({_, _, _, _, _, _, _, _} = IP) ->
61    Bin = << <<X:16>> || X <- tuple_to_list(IP) >>,
62    <<?INET6, ?MAX_IP6_MASK, 0, ?IP6_SIZE, Bin/binary>>.
63
64-spec decode_net(binary()) -> ip() | ip_mask().
65decode_net(<<?INET, Mask:8, 1, ?IP_SIZE, Bin/binary>>) ->
66    {list_to_tuple(binary_to_list(Bin)), Mask};
67decode_net(<<?INET6, Mask:8, 1, ?IP6_SIZE, Bin/binary>>) ->
68    {list_to_tuple([X || <<X:16>> <= Bin]), Mask};
69decode_net(<<?INET, ?MAX_IP_MASK, 0, ?IP_SIZE, Bin/binary>>) ->
70    list_to_tuple(binary_to_list(Bin));
71decode_net(<<?INET6, ?MAX_IP6_MASK, 0, ?IP6_SIZE, Bin/binary>>) ->
72    list_to_tuple([X || <<X:16>> <= Bin]).
73
74decode_text(V, _, _) -> V.
75