Current section

Files

Jump to
eradius src eradius_dict.erl
Raw

src/eradius_dict.erl

%% @private
%% @doc Dictionary server
-module(eradius_dict).
-export([start_link/0, lookup/2, load_tables/1, load_tables/2]).
-export_type([attribute/0, attr_value/0, table_name/0, attribute_id/0, attribute_type/0,
attribute_prim_type/0, attribute_encryption/0, vendor_id/0, value_id/0]).
-behaviour(gen_server).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-include_lib("kernel/include/logger.hrl").
-include("eradius_dict.hrl").
-define(SERVER, ?MODULE).
-define(TABLENAME, ?MODULE).
-type table_name() :: atom() | string().
-type attribute_id() :: pos_integer() | {vendor_id(), pos_integer()}.
-type attribute_encryption() :: 'no' | 'scramble' | 'salt_crypt' | 'ascend'.
-type attribute_type() :: attribute_prim_type() | {tagged, attribute_prim_type()}.
-type attribute_prim_type() :: 'string' | 'integer' | 'integer64' | 'ipaddr' | 'ipv6addr'
| 'ipv6prefix' | 'date' | 'abinary' | 'binary' | 'octets'.
-type value_id() :: {attribute_id(), pos_integer()}.
-type vendor_id() :: pos_integer().
-type attribute() :: #attribute{} | attribute_id().
-type attr_value() :: term().
-record(state, {}).
%% ------------------------------------------------------------------------------------------
%% -- API
-spec start_link() -> {ok, pid()} | {error, term()}.
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
-spec lookup(attribute | vendor | value, attribute_id() | value_id() | vendor_id()) -> false | #attribute{} | #value{} | #vendor{}.
lookup(Type, Id) ->
dict_lookup(Type, Id).
-spec load_tables(list(table_name())) -> ok | {error, {consult, table_name()}}.
load_tables(Tables) when is_list(Tables) ->
load_tables(code:priv_dir(eradius), Tables).
-spec load_tables(file:filename(), list(table_name())) -> ok | {error, {consult, table_name()}}.
load_tables(Dir, Tables) when is_list(Tables) ->
gen_server:call(?SERVER, {load_tables, Dir, Tables}, infinity).
%% ------------------------------------------------------------------------------------------
%% -- gen_server callbacks
init([]) ->
dict_init(),
{ok, InitialLoadTables} = application:get_env(eradius, tables),
do_load_tables(code:priv_dir(eradius), InitialLoadTables),
{ok, #state{}}.
handle_call({load_tables, Dir, Tables}, _From, State) ->
{reply, do_load_tables(Dir, Tables), State}.
%% unused callbacks
handle_cast(_Msg, State) -> {noreply, State}.
handle_info(_Info, State) -> {noreply, State}.
terminate(_Reason, _State) -> ok.
code_change(_OldVsn, _NewVsn, _State) -> {ok, state}.
%% ------------------------------------------------------------------------------------------
%% -- gen_server callbacks
mapfile(A) when is_atom(A) -> mapfile(atom_to_list(A));
mapfile(A) when is_list(A) -> A ++ ".map".
-spec do_load_tables(file:filename(), [table_name()]) -> ok | {error, {consult, file:filename()}}.
do_load_tables(_Dir, []) ->
ok;
do_load_tables(Dir, Tables) ->
try
All = lists:flatmap(fun (Tab) ->
TabFile = filename:join(Dir, mapfile(Tab)),
case file:consult(TabFile) of
{ok, Res} -> Res;
{error, _Error} -> throw({consult, TabFile})
end
end, Tables),
{MoreIncludes, Defs} = lists:partition(fun({include, _}) -> true; (_) -> false end, All),
dict_insert(Defs),
?LOG(info, "Loaded RADIUS tables: ~p", [Tables]),
do_load_tables(Dir, [T || {include, T} <- MoreIncludes])
catch
throw:{consult, FailedTable} ->
?LOG(error, "Failed to load RADIUS table: ~s (wanted: ~p)", [FailedTable, Tables]),
{error, {consult, FailedTable}}
end.
%% check if we can use persistent_term for config
%% persistent term was added in OTP 21.2 but we can't
%% check minor versions with macros so we're stuck waiting
%% for OTP 22
-ifdef(HAVE_PERSISTENT_TERM).
dict_init() ->
ok.
dict_insert(Value) when is_list(Value) ->
[dict_insert(V) || V <- Value];
dict_insert(Value) when is_tuple(Value) ->
Key = {?TABLENAME, element(1, Value), element(2, Value)},
persistent_term:put(Key, Value).
dict_lookup(Type, Id) ->
try
persistent_term:get({?TABLENAME, Type, Id})
catch
error:badarg ->
false
end.
-else.
dict_init() ->
ets:new(?TABLENAME, [set, named_table, {keypos, 1}, protected]).
dict_insert(Value) when is_list(Value) ->
[dict_insert(V) || V <- Value];
dict_insert(Value) when is_tuple(Value) ->
Key = {element(1, Value), element(2, Value)},
ets:insert(?TABLENAME, {Key, Value}).
dict_lookup(Type, Id) ->
try
ets:lookup_element(?TABLENAME, {Type, Id}, 2)
catch
error:badarg ->
false
end.
-endif.