%% Copyright (C) 2025 by sysmocom - s.f.m.c. GmbH %% Author: Vadim Yanitskiy %% %% All Rights Reserved %% %% SPDX-License-Identifier: AGPL-3.0-or-later %% %% This program is free software; you can redistribute it and/or modify %% it under the terms of the GNU Affero General Public License as %% published by the Free Software Foundation; either version 3 of the %% License, or (at your option) any later version. %% %% This program is distributed in the hope that it will be useful, %% but WITHOUT ANY WARRANTY; without even the implied warranty of %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %% GNU General Public License for more details. %% %% You should have received a copy of the GNU Affero General Public License %% along with this program. If not, see . %% %% Additional Permission under GNU AGPL version 3 section 7: %% %% If you modify this Program, or any covered work, by linking or %% combining it with runtime libraries of Erlang/OTP as released by %% Ericsson on https://www.erlang.org (or a modified version of these %% libraries), containing parts covered by the terms of the Erlang Public %% License (https://www.erlang.org/EPLICENSE), the licensors of this %% Program grant you additional permission to convey the resulting work %% without the need to license the runtime libraries of Erlang/OTP under %% the GNU Affero General Public License. Corresponding Source for a %% non-source form of such a combination shall include the source code %% for the parts of the runtime libraries of Erlang/OTP used as well as %% that of the covered work. -module(rest_server). -export([metrics_list/1, pfcp_assoc_state/1, pfcp_heartbeat/1, enb_list/1, enb_info/1, enb_erab_list/1, erab_list/1, erab_info/1 ]). -include_lib("kernel/include/logger.hrl"). -include("s1gw_metrics.hrl"). %% ------------------------------------------------------------------ %% public API %% ------------------------------------------------------------------ %% MetricsList :: Get a list of metrics metrics_list(#{query_parameters := QP}) -> L0 = case proplists:get_value(<< "type" >>, QP, << "all" >>) of << "counter" >> -> metric_list([ctr]); << "gauge" >> -> metric_list([gauge]); << "all" >> -> metric_list([]) end, L1 = case proplists:get_value(<< "path" >>, QP) of undefined -> L0; Path -> lists:filter(fun(M) -> metric_filter_path(M, Path) end, L0) end, {200, [], L1}. %% PfcpAssocState :: Get the PFCP association state pfcp_assoc_state(#{}) -> Info0 = pfcp_peer:fetch_info(), Info1 = maps:update_with(laddr, fun inet:ntoa/1, Info0), Info2 = maps:update_with(raddr, fun inet:ntoa/1, Info1), {200, [], rsp_map(Info2)}. %% TODO: PfcpAssocSetup :: Initiate the PFCP Association Setup procedure %% TODO: PfcpAssocRelease :: Initiate the PFCP Association Release procedure %% PfcpHeartbeat :: Send a PFCP Heartbeat Request to the peer pfcp_heartbeat(#{}) -> case pfcp_peer:heartbeat_req() of ok -> {200, [], rsp_map(#{success => true})}; {error, Error} -> {200, [], rsp_map(#{success => false, message => Error})} end. %% EnbList :: Get a list of eNB connections enb_list(#{}) -> EnbList = enb_registry:fetch_enb_list(), {200, [], lists:map(fun enb_item/1, EnbList)}. %% EnbInfo :: Get information about a specific eNB enb_info(#{path_parameters := PP}) -> [{<< "EnbId" >>, << ID/bytes >>}] = PP, case fetch_enb_info(ID) of [EnbInfo | _] -> {200, [], enb_item(EnbInfo)}; [] -> {404, [], undefined}; error -> {500, [], undefined} end. %% EnbErabList :: Get E-RAB list for a specific eNB enb_erab_list(#{path_parameters := PP}) -> [{<< "EnbId" >>, << ID/bytes >>}] = PP, case fetch_enb_info(ID) of [EnbInfo | _] -> Rsp = fetch_erab_list(EnbInfo), {200, [], Rsp}; [] -> {404, [], undefined}; error -> {500, [], undefined} end. %% ErabList :: Get E-RAB list for all eNBs erab_list(#{}) -> EnbList = enb_registry:fetch_enb_list(), ErabList = lists:map(fun fetch_erab_list/1, EnbList), {200, [], lists:flatten(ErabList)}. %% ErabInfo :: Get information about a specific E-RAB erab_info(#{path_parameters := PP}) -> [{<< "ErabId" >>, << ID/bytes >>}] = PP, case fetch_erab_info(ID) of {ok, ErabInfo} -> {200, [], ErabInfo}; error -> {404, [], undefined} end. %% ------------------------------------------------------------------ %% private API %% ------------------------------------------------------------------ -spec metric_list(list()) -> [map()]. metric_list(Path) -> L = exometer:get_values(Path), lists:map(fun metric_item/1, L). -spec metric_item({Name, Props}) -> map() when Name :: s1gw_metrics:metric(), Props :: proplists:proplist(). metric_item({Name, Props}) -> Value = proplists:get_value(value, Props), #{<< "type" >> => metric_type(Name), << "name" >> => metric_name(Name), << "value" >> => Value}. -spec metric_type(s1gw_metrics:metric()) -> binary(). metric_type([ctr | _]) -> << "counter" >>; metric_type([gauge | _]) -> << "gauge" >>. -spec metric_filter_path(map(), binary()) -> boolean(). metric_filter_path(#{<< "name" >> := Name}, Path) -> Len = erlang:min(byte_size(Name), byte_size(Path)), binary:part(Name, 0, Len) =:= Path. -spec metric_name(s1gw_metrics:metric()) -> binary(). metric_name([_Type | P0]) -> %% turn each list member into a string P1 = lists:map(fun thing_to_list/1, P0), %% put the dot-separator in between P2 = string:join(P1, "."), list_to_binary(P2). %% stolen from exometer_report_statsd.git thing_to_list(X) when is_atom(X) -> atom_to_list(X); thing_to_list(X) when is_integer(X) -> integer_to_list(X); thing_to_list(X) when is_binary(X) -> X; thing_to_list(X) when is_list(X) -> X. -spec enb_item(enb_registry:enb_info()) -> map(). enb_item(EnbInfo) -> M0 = #{handle => maps:get(handle, EnbInfo), pid => pid_to_list(maps:get(pid, EnbInfo)), state => maps:get(state, EnbInfo), uptime => maps:get(uptime, EnbInfo), erab_count => 0}, M1 = enb_item_add_enb_info(M0, EnbInfo), M2 = enb_item_add_enb_conn_info(M1, EnbInfo), M3 = enb_item_add_mme_conn_info(M2, EnbInfo), rsp_map(M3). -spec enb_item_add_enb_info(map(), enb_registry:enb_info()) -> map(). enb_item_add_enb_info(M0, #{genb_id_str := GlobalENBId}) -> %% TODO: add enb_id and plmn_id M0#{genb_id => GlobalENBId}; enb_item_add_enb_info(M0, _) -> M0. -spec enb_item_add_enb_conn_info(map(), enb_registry:enb_info()) -> map(). enb_item_add_enb_conn_info(M0, #{enb_conn_info := ConnInfo}) -> M0#{enb_saddr => inet:ntoa(maps:get(addr, ConnInfo)), enb_sport => maps:get(port, ConnInfo), enb_sctp_aid => maps:get(aid, ConnInfo) }; enb_item_add_enb_conn_info(M0, _) -> M0. -spec enb_item_add_mme_conn_info(map(), enb_registry:enb_info()) -> map(). enb_item_add_mme_conn_info(M0, #{mme_conn_info := ConnInfo}) -> Pid = maps:get(handler, ConnInfo), ERABs = s1ap_proxy:fetch_erab_list(Pid), M0#{mme_daddr => maps:get(mme_addr, ConnInfo), %% XXX inet:ntoa mme_dport => maps:get(mme_port, ConnInfo), %% TODO: mme_sport mme_sctp_aid => maps:get(mme_aid, ConnInfo), erab_count => length(ERABs) }; enb_item_add_mme_conn_info(M0, _) -> M0. -spec fetch_enb_info(binary()) -> [enb_registry:enb_info()] | error. fetch_enb_info(<< "handle:", Val/bytes >>) -> Handle = binary_to_integer(Val), case enb_registry:fetch_enb_info(Handle) of {ok, EnbInfo} -> [EnbInfo]; error -> [] end; fetch_enb_info(<< "pid:", Val/bytes >>) -> Pid = parse_pid(Val), case enb_registry:fetch_enb_info(Pid) of {ok, EnbInfo} -> [EnbInfo]; error -> [] end; fetch_enb_info(<< "genbid:", Val/bytes >>) -> GlobalENBId = binary_to_list(Val), enb_registry:fetch_enb_list({genb_id_str, GlobalENBId}); fetch_enb_info(<< "enb-sctp-aid:", Val/bytes >>) -> Aid = binary_to_integer(Val), enb_registry:fetch_enb_list({enb_sctp_aid, Aid}); fetch_enb_info(<< "mme-sctp-aid:", Val/bytes >>) -> Aid = binary_to_integer(Val), enb_registry:fetch_enb_list({mme_sctp_aid, Aid}); %% TODO: '^enb-conn:[0-9:.]+-[0-9]+$' fetch_enb_info(ID) -> ?LOG_ERROR("Unhandled eNB ID ~p", [ID]), error. -spec fetch_erab_info(binary()) -> {ok, erab_fsm:erab_info()} | error. fetch_erab_info(<< "pid:", Val/bytes >>) -> Pid = parse_pid(Val), %% guard against non-existent process IDs %% TODO: check if the given Pid is actually an erab_fsm try erab_list_item({pid, Pid}) of ErabInfo -> {ok, ErabInfo} catch exit:{noproc, _} -> error end; fetch_erab_info(ID) -> ?LOG_ERROR("Unhandled E-RAB ID ~p", [ID]), error. -spec fetch_erab_list(enb_registry:enb_info()) -> [map()]. fetch_erab_list(#{mme_conn_info := ConnInfo}) -> Pid = maps:get(handler, ConnInfo), %% s1ap_proxy process pid ERABs = s1ap_proxy:fetch_erab_list(Pid), lists:map(fun erab_list_item/1, ERABs); fetch_erab_list(_) -> []. -spec erab_list_item({term(), pid()}) -> map(). erab_list_item({_, Pid}) -> %% XXX: E-RAB FSM process might be dead here Info = erab_fsm:fetch_info(Pid), {MmeUeId, ErabId} = maps:get(uid, Info), M0 = #{mme_ue_id => MmeUeId, erab_id => ErabId, state => maps:get(state, Info), pid => pid_to_list(Pid)}, M1 = erab_list_item_add_seid(Info, M0), M2 = erab_list_item_add_f_teid(f_teid_u2c, Info, M1), M3 = erab_list_item_add_f_teid(f_teid_c2u, Info, M2), M4 = erab_list_item_add_f_teid(f_teid_a2u, Info, M3), M5 = erab_list_item_add_f_teid(f_teid_u2a, Info, M4), rsp_map(M5). -spec erab_list_item_add_seid(erab_fsm:erab_info(), map()) -> map(). erab_list_item_add_seid(Info, M0) -> %% local SEID is always known/present M1 = M0#{pfcp_lseid => maps:get(seid_loc, Info)}, %% remote SEID may or may not be known/present case maps:find(seid_rem, Info) of {ok, SEID} -> M1#{pfcp_rseid => SEID}; error -> M1 end. -spec erab_list_item_add_f_teid(atom(), erab_fsm:erab_info(), map()) -> map(). erab_list_item_add_f_teid(Key, Info, M0) -> case maps:find(Key, Info) of {ok, {TEID, AddrBin}} -> Addr = list_to_tuple(binary_to_list(AddrBin)), M0#{Key => #{teid => TEID, tla => inet:ntoa(Addr)}}; error -> M0 end. -spec parse_pid(binary() | list()) -> pid(). parse_pid(Data) when is_binary(Data) -> parse_pid(binary_to_list(Data)); parse_pid(Data) when is_list(Data) -> list_to_pid("<" ++ Data ++ ">"). %% Convert the given response map to a format acceptable by the erf -spec rsp_map(map()) -> map(). rsp_map(M) -> Fun = fun(K, V) -> {bval(K), bval(V)} end, maps:from_list([Fun(K, V) || {K, V} <- maps:to_list(M)]). bval(V) when is_boolean(V) -> V; bval(V) when is_atom(V) -> atom_to_binary(V); bval(V) when is_list(V) -> list_to_binary(V); bval(V) when is_map(V) -> rsp_map(V); bval(V) -> V. %% vim:set ts=4 sw=4 et: