%% -*-: erlang -*-

%%--------------------------------------------------------------------
%% Services

{mapping, "exproto.server.http.port", "emqx_exproto.servers", [
  {datatype, integer}
]}.

{mapping, "exproto.server.https.port", "emqx_exproto.servers", [
  {datatype, integer}
]}.

{mapping, "exproto.server.https.cacertfile", "emqx_exproto.servers", [
  {datatype, string}
]}.

{mapping, "exproto.server.https.certfile", "emqx_exproto.servers", [
  {datatype, string}
]}.

{mapping, "exproto.server.https.keyfile", "emqx_exproto.servers", [
  {datatype, string}
]}.

{translation, "emqx_exproto.servers", fun(Conf) ->
    Filter = fun(Opts) -> [{K, V} || {K, V} <- Opts, V =/= undefined] end,
    Http = case cuttlefish:conf_get("exproto.server.http.port", Conf, undefined) of
               undefined -> [];
               P1 -> [{http, P1, []}]
           end,
    Https = case cuttlefish:conf_get("exproto.server.https.port", Conf, undefined) of
                undefined -> [];
                P2 ->
                    [{https, P2,
                        Filter([{ssl, true},
                                {certfile, cuttlefish:conf_get("exproto.server.https.certfile", Conf)},
                                {keyfile, cuttlefish:conf_get("exproto.server.https.keyfile", Conf)},
                                {cacertfile, cuttlefish:conf_get("exproto.server.https.cacertfile", Conf)}])}]
            end,
    Http ++ Https
end}.

%%--------------------------------------------------------------------
%% Listeners

{mapping, "exproto.listener.$proto", "emqx_exproto.listeners", [
  {datatype, string}
]}.

{mapping, "exproto.listener.$proto.connection_handler_url", "emqx_exproto.listeners", [
  {datatype, string}
]}.

{mapping, "exproto.listener.$proto.connection_handler_certfile", "emqx_exproto.listeners", [
  {datatype, string}
]}.

{mapping, "exproto.listener.$proto.connection_handler_cacertfile", "emqx_exproto.listeners", [
  {datatype, string}
]}.

{mapping, "exproto.listener.$proto.connection_handler_keyfile", "emqx_exproto.listeners", [
  {datatype, string}
]}.

{mapping, "exproto.listener.$proto.acceptors", "emqx_exproto.listeners", [
  {default, 8},
  {datatype, integer}
]}.

{mapping, "exproto.listener.$proto.max_connections", "emqx_exproto.listeners", [
  {default, 1024},
  {datatype, integer}
]}.

{mapping, "exproto.listener.$proto.max_conn_rate", "emqx_exproto.listeners", [
  {datatype, integer}
]}.

{mapping, "exproto.listener.$proto.active_n", "emqx_exproto.listeners", [
  {default, 100},
  {datatype, integer}
]}.

{mapping, "exproto.listener.$proto.idle_timeout", "emqx_exproto.listeners", [
  {default, "30s"},
  {datatype, {duration, ms}}
]}.

{mapping, "exproto.listener.$proto.access.$id", "emqx_exproto.listeners", [
  {datatype, string}
]}.

{mapping, "exproto.listener.$proto.proxy_protocol", "emqx_exproto.listeners", [
  {datatype, flag}
]}.

{mapping, "exproto.listener.$proto.proxy_protocol_timeout", "emqx_exproto.listeners", [
  {datatype, {duration, ms}}
]}.

{mapping, "exproto.listener.$proto.backlog", "emqx_exproto.listeners", [
  {datatype, integer},
  {default, 1024}
]}.

{mapping, "exproto.listener.$proto.send_timeout", "emqx_exproto.listeners", [
  {datatype, {duration, ms}},
  {default, "15s"}
]}.

{mapping, "exproto.listener.$proto.send_timeout_close", "emqx_exproto.listeners", [
  {datatype, flag},
  {default, on}
]}.

{mapping, "exproto.listener.$proto.recbuf", "emqx_exproto.listeners", [
  {datatype, bytesize},
  hidden
]}.

{mapping, "exproto.listener.$proto.sndbuf", "emqx_exproto.listeners", [
  {datatype, bytesize},
  hidden
]}.

{mapping, "exproto.listener.$proto.buffer", "emqx_exproto.listeners", [
  {datatype, bytesize},
  hidden
]}.

{mapping, "exproto.listener.$proto.tune_buffer", "emqx_exproto.listeners", [
  {datatype, flag},
  hidden
]}.

{mapping, "exproto.listener.$proto.nodelay", "emqx_exproto.listeners", [
  {datatype, {enum, [true, false]}},
  hidden
]}.

{mapping, "exproto.listener.$proto.reuseaddr", "emqx_exproto.listeners", [
  {datatype, {enum, [true, false]}},
  hidden
]}.

%%--------------------------------------------------------------------
%% TLS Options

{mapping, "exproto.listener.$proto.tls_versions", "emqx_exproto.listeners", [
  {datatype, string}
]}.

{mapping, "exproto.listener.$proto.ciphers", "emqx_exproto.listeners", [
  {datatype, string}
]}.

{mapping, "exproto.listener.$proto.psk_ciphers", "emqx_exproto.listeners", [
  {datatype, string}
]}.

{mapping, "exproto.listener.$proto.dhfile", "emqx_exproto.listeners", [
  {datatype, string}
]}.

{mapping, "exproto.listener.$proto.keyfile", "emqx_exproto.listeners", [
  {datatype, string}
]}.

{mapping, "exproto.listener.$proto.certfile", "emqx_exproto.listeners", [
  {datatype, string}
]}.

{mapping, "exproto.listener.$proto.cacertfile", "emqx_exproto.listeners", [
  {datatype, string}
]}.

{mapping, "exproto.listener.$proto.verify", "emqx_exproto.listeners", [
  {datatype, atom}
]}.

{mapping, "exproto.listener.$proto.fail_if_no_peer_cert", "emqx_exproto.listeners", [
  {datatype, {enum, [true, false]}}
]}.

{mapping, "exproto.listener.$proto.secure_renegotiate", "emqx_exproto.listeners", [
  {datatype, flag}
]}.

{mapping, "exproto.listener.$proto.reuse_sessions", "emqx_exproto.listeners", [
  {default, on},
  {datatype, flag}
]}.

{mapping, "exproto.listener.$proto.honor_cipher_order", "emqx_exproto.listeners", [
  {datatype, flag}
]}.

{translation, "emqx_exproto.listeners", fun(Conf) ->

    Filter = fun(Opts) -> [{K, V} || {K, V} <- Opts, V =/= undefined] end,

    Atom = fun(undefined) -> undefined; (S) -> list_to_atom(S) end,

    Access = fun(S) ->
                 [A, CIDR] = string:tokens(S, " "),
                 {list_to_atom(A), case CIDR of "all" -> all; _ -> CIDR end}
             end,

    AccOpts = fun(Prefix) ->
                  case cuttlefish_variable:filter_by_prefix(Prefix ++ ".access", Conf) of
                      [] -> [];
                      Rules -> [{access_rules, [Access(Rule) || {_, Rule} <- Rules]}]
                  end
              end,

    RateLimit = fun(undefined) ->
                        undefined;
                   (Val) ->
                        [L, D] = string:tokens(Val, ", "),
                        Limit = case cuttlefish_bytesize:parse(L) of
                                    Sz when is_integer(Sz) -> Sz;
                                    {error, Reason} -> error(Reason)
                                end,
                        Duration = case cuttlefish_duration:parse(D, s) of
                                       Secs when is_integer(Secs) -> Secs;
                                       {error, Reason1} -> error(Reason1)
                                   end,
                        Rate = Limit / Duration,
                        {Rate, Limit}
                end,

    HandlerOpts = fun(Prefix) ->
                      Opts =
                      case http_uri:parse(cuttlefish:conf_get(Prefix ++ ".connection_handler_url", Conf)) of
                          {ok, {http, _, Host, Port, _, _}} ->
                              [{scheme, http}, {host, Host}, {port, Port}];
                          {ok, {https, _, Host, Port, _, _}} ->
                              [{scheme, https}, {host, Host}, {port, Port},
                               {ssl_options,
                                 Filter([{certfile, cuttlefish:conf_get(Prefix ++ ".connection_handler_certfile", Conf)},
                                         {keyfile, cuttlefish:conf_get(Prefix ++ ".connection_handler_keyfile", Conf)},
                                         {cacertfile, cuttlefish:conf_get(Prefix ++ ".connection_handler_cacertfile", Conf)}
                                        ])}];
                          _ ->
                              error(invaild_connection_handler_url)
                      end,
                      [{handler, Opts}]
                  end,

    ConnOpts = fun(Prefix) ->
                   Filter([{active_n, cuttlefish:conf_get(Prefix ++ ".active_n", Conf, undefined)},
                           {idle_timeout, cuttlefish:conf_get(Prefix ++ ".idle_timeout", Conf, undefined)}])
               end,

    LisOpts = fun(Prefix) ->
                  Filter([{acceptors, cuttlefish:conf_get(Prefix ++ ".acceptors", Conf)},
                          {max_connections, cuttlefish:conf_get(Prefix ++ ".max_connections", Conf)},
                          {max_conn_rate, cuttlefish:conf_get(Prefix ++ ".max_conn_rate", Conf, undefined)},
                          {tune_buffer, cuttlefish:conf_get(Prefix ++ ".tune_buffer", Conf, undefined)},
                          {proxy_protocol, cuttlefish:conf_get(Prefix ++ ".proxy_protocol", Conf, undefined)},
                          {proxy_protocol_timeout, cuttlefish:conf_get(Prefix ++ ".proxy_protocol_timeout", Conf, undefined)} | AccOpts(Prefix)])
              end,

    TcpOpts = fun(Prefix) ->
                  Filter([{backlog, cuttlefish:conf_get(Prefix ++ ".backlog", Conf, undefined)},
                          {send_timeout, cuttlefish:conf_get(Prefix ++ ".send_timeout", Conf, undefined)},
                          {send_timeout_close, cuttlefish:conf_get(Prefix ++ ".send_timeout_close", Conf, undefined)},
                          {recbuf,  cuttlefish:conf_get(Prefix ++ ".recbuf", Conf, undefined)},
                          {sndbuf,  cuttlefish:conf_get(Prefix ++ ".sndbuf", Conf, undefined)},
                          {buffer,  cuttlefish:conf_get(Prefix ++ ".buffer", Conf, undefined)},
                          {nodelay, cuttlefish:conf_get(Prefix ++ ".nodelay", Conf, true)},
                          {reuseaddr, cuttlefish:conf_get(Prefix ++ ".reuseaddr", Conf, undefined)}])
              end,
    SplitFun = fun(undefined) -> undefined; (S) -> string:tokens(S, ",") end,
    MapPSKCiphers = fun(PSKCiphers) ->
                      lists:map(
                          fun("PSK-AES128-CBC-SHA") -> {psk, aes_128_cbc, sha};
                             ("PSK-AES256-CBC-SHA") -> {psk, aes_256_cbc, sha};
                             ("PSK-3DES-EDE-CBC-SHA") -> {psk, '3des_ede_cbc', sha};
                             ("PSK-RC4-SHA") -> {psk, rc4_128, sha}
                          end, PSKCiphers)
                    end,
    SslOpts = fun(Prefix) ->
                  Versions = case SplitFun(cuttlefish:conf_get(Prefix ++ ".tls_versions", Conf, undefined)) of
                                undefined -> undefined;
                                L -> [list_to_atom(V) || V <- L]
                            end,
                  TLSCiphers = cuttlefish:conf_get(Prefix++".ciphers", Conf, undefined),
                  PSKCiphers = cuttlefish:conf_get(Prefix++".psk_ciphers", Conf, undefined),
                  Ciphers =
                    case {TLSCiphers, PSKCiphers} of
                      {undefined, undefined} ->
                        cuttlefish:invalid(Prefix++".ciphers or "++Prefix++".psk_ciphers is absent");
                      {TLSCiphers, undefined} ->
                        SplitFun(TLSCiphers);
                      {undefined, PSKCiphers} ->
                        MapPSKCiphers(SplitFun(PSKCiphers));
                      {_TLSCiphers, _PSKCiphers} ->
                        cuttlefish:invalid(Prefix++".ciphers and "++Prefix++".psk_ciphers cannot be configured at the same time")
                    end,
                  UserLookupFun =
                    case PSKCiphers of
                      undefined -> undefined;
                      _ -> {fun emqx_psk:lookup/3, <<>>}
                    end,
                  Filter([{versions, Versions},
                          {ciphers, Ciphers},
                          {user_lookup_fun, UserLookupFun},
                          %{handshake_timeout, cuttlefish:conf_get(Prefix ++ ".handshake_timeout", Conf, undefined)},
                          {dhfile, cuttlefish:conf_get(Prefix ++ ".dhfile", Conf, undefined)},
                          {keyfile,    cuttlefish:conf_get(Prefix ++ ".keyfile", Conf, undefined)},
                          {certfile,   cuttlefish:conf_get(Prefix ++ ".certfile", Conf, undefined)},
                          {cacertfile, cuttlefish:conf_get(Prefix ++ ".cacertfile", Conf, undefined)},
                          {verify,     cuttlefish:conf_get(Prefix ++ ".verify", Conf, undefined)},
                          {fail_if_no_peer_cert, cuttlefish:conf_get(Prefix ++ ".fail_if_no_peer_cert", Conf, undefined)},
                          {secure_renegotiate, cuttlefish:conf_get(Prefix ++ ".secure_renegotiate", Conf, undefined)},
                          {reuse_sessions, cuttlefish:conf_get(Prefix ++ ".reuse_sessions", Conf, undefined)},
                          {honor_cipher_order, cuttlefish:conf_get(Prefix ++ ".honor_cipher_order", Conf, undefined)}])
              end,

    UdpOpts = fun(Prefix) ->
                  Filter([{recbuf,  cuttlefish:conf_get(Prefix ++ ".recbuf", Conf, undefined)},
                          {sndbuf,  cuttlefish:conf_get(Prefix ++ ".sndbuf", Conf, undefined)},
                          {buffer,  cuttlefish:conf_get(Prefix ++ ".buffer", Conf, undefined)},
                          {reuseaddr, cuttlefish:conf_get(Prefix ++ ".reuseaddr", Conf, undefined)}])
              end,

    ParseListenOn = fun(ListenOn) ->
                        case string:tokens(ListenOn, "://") of
                            [Port] -> {tcp, list_to_integer(Port)};
                            [T, Ip, Port]
                              when T =:= "tcp"; T =:= "ssl";
                                   T =:= "udp"; T =:= "dtls" ->
                                {Atom(T), {Ip, list_to_integer(Port)}}
                        end
                    end,

    Listeners = fun(Proto) ->
                    Prefix = string:join(["exproto","listener", Proto], "."),
                    Opts = HandlerOpts(Prefix) ++ ConnOpts(Prefix) ++ LisOpts(Prefix),
                    case cuttlefish:conf_get(Prefix, Conf, undefined) of
                        undefined -> [];
                        ListenOn0 ->
                            case ParseListenOn(ListenOn0) of
                                {tcp, ListenOn} ->
                                    [{Proto, tcp, ListenOn, [{tcp_options, TcpOpts(Prefix)} | Opts]}];
                                {ssl, ListenOn} ->
                                    [{Proto, ssl, ListenOn, [{tcp_options, TcpOpts(Prefix)},
                                                             {ssl_options, SslOpts(Prefix)} | Opts]}];
                                {udp, ListenOn} ->
                                    [{Proto, udp, ListenOn, [{udp_options, UdpOpts(Prefix)} | Opts]}];
                                {dtls, ListenOn} ->
                                    [{Proto, dtls, ListenOn, [{udp_options, UdpOpts(Prefix)},
                                                              {dtls_options, SslOpts(Prefix)} | Opts]}];
                                {_, _} ->
                                    cuttlefish:invalid("Not supported listener type")
                            end
                    end
                end,
    lists:flatten([Listeners(Proto) || {[_, "listener", Proto], ListenOn}
                                   <- cuttlefish_variable:filter_by_prefix("exproto.listener", Conf)])
end}.
