pespin has submitted this change. ( 
https://gerrit.osmocom.org/c/erlang/osmo-epdg/+/35659?usp=email )

Change subject: Introduce ue_fsm
......................................................................

Introduce ue_fsm

Decouple gsup_server from other protocols.

Change-Id: I0c960c4c250458384ed706a99582ec52083019f6
---
M src/gsup_server.erl
A src/ue_fsm.erl
2 files changed, 269 insertions(+), 70 deletions(-)

Approvals:
  laforge: Looks good to me, but someone else must approve
  Jenkins Builder: Verified
  dexter: Looks good to me, but someone else must approve
  pespin: Looks good to me, approved




diff --git a/src/gsup_server.erl b/src/gsup_server.erl
index b8ed637..5fe227c 100644
--- a/src/gsup_server.erl
+++ b/src/gsup_server.erl
@@ -41,19 +41,28 @@
 -include_lib("osmo_gsup/include/gsup_protocol.hrl").
 -include_lib("gtplib/include/gtp_packet.hrl").
 
+-define(SERVER, ?MODULE).
+
 -define(IPAC_PROTO_EXT_GSUP,   {osmo, 5}).

 -record(gsups_state, {
        lsocket, % listening socket
        lport, % local port. only interesting if we bind with port 0
        socket, % current active socket. we only support a single tcp connection
-       ccm_options % ipa ccm options
+       ccm_options, % ipa ccm options
+       ues = sets:new()
+       }).
+
+-record(gsups_ue, {
+       imsi                   :: binary(),
+       pid                    :: pid()
        }).

 -export([start_link/3]).

 -export([init/1, handle_call/3, handle_cast/2, handle_info/2]).
 -export([code_change/3, terminate/2]).
+-export([auth_response/2, lu_response/2, tunnel_response/2]).

 % TODO: -spec dia_sip2gsup('SIP-Auth-Data-Item'()) -> #'GSUPAuthTuple'{}.
 dia_sip2gsup(#'SIP-Auth-Data-Item'{'SIP-Authenticate' = [Authenticate], 
'SIP-Authorization' = [Authorization],
@@ -71,7 +80,7 @@
 %% ------------------------------------------------------------------

 start_link(ServerAddr, ServerPort, Options) ->
-       gen_server:start_link(?MODULE, [ServerAddr, ServerPort, Options], 
[{debug, [trace]}]).
+       gen_server:start_link({local, ?SERVER}, ?MODULE, [ServerAddr, 
ServerPort, Options], [{debug, [trace]}]).

 %% ------------------------------------------------------------------
 %% gen_server Function Definitions
@@ -105,20 +114,76 @@
        end.

 % send a given GSUP message and synchronously wait for message type ExpRes or 
ExpErr
-handle_call({transceive_gsup, GsupMsgTx, ExpRes, ExpErr}, _From, State) ->
-       Socket = State#gsups_state.socket,
-       {ok, Imsi} = maps:find(imsi, GsupMsgTx),
-       ipa_proto:send(Socket, ?IPAC_PROTO_EXT_GSUP, GsupMsgTx),
-       % selective receive for only those GSUP responses we expect
-       receive
-               {ipa, Socket, ?IPAC_PROTO_EXT_GSUP, GsupMsgRx = #{message_type 
:= ExpRes, imsi := Imsi}} ->
-                       {reply, GsupMsgRx, State};
+handle_call(Info, _From, State) ->
+       error_logger:error_report(["unknown handle_call", {module, ?MODULE}, 
{info, Info}, {state, State}]),
+       {reply, error, not_implemented}.

-               {ipa, Socket, ?IPAC_PROTO_EXT_GSUP, GsupMsgRx = #{message_type 
:= ExpErr, imsi := Imsi}} ->
-                       {reply, GsupMsgRx, State}
-       after 5000 ->
-               {reply, timeout, State}
-       end.
+handle_cast({auth_response, {Imsi, Auth}}, State) ->
+       lager:info("auth_response for ~p: ~p~n", [Imsi, Auth]),
+       Socket = State#gsups_state.socket,
+       case Auth of
+               {ok, Mar} ->    SipAuthTuples = Mar#'MAA'.'SIP-Auth-Data-Item',
+                               % AuthTuples = dia_sip2gsup(SipAuthTuples),
+                               Resp = #{message_type => send_auth_info_res,
+                                       message_class => 5,
+                                       imsi => 
list_to_binary(Mar#'MAA'.'User-Name'),
+                                       auth_tuples => lists:map(fun 
dia_sip2gsup/1, SipAuthTuples)
+                                       };
+               {error, _} ->   Resp = #{message_type => send_auth_info_err, 
imsi => Imsi, message_class => 5, cause => 16#11}
+       end,
+       lager:info("GSUP: Tx ~p~n", [Resp]),
+       ipa_proto:send(Socket, ?IPAC_PROTO_EXT_GSUP, Resp),
+       {noreply, State};
+
+handle_cast({lu_response, {Imsi, Result}}, State) ->
+       lager:info("lu_response for ~p: ~p~n", [Imsi, Result]),
+       Socket = State#gsups_state.socket,
+       case Result of
+               {ok, _Sar} ->   Resp = #{message_type => location_upd_res,
+                                        imsi => Imsi,
+                                        message_class => 5
+                                        };
+               {error, _} ->   Resp = #{message_type => location_upd_err,
+                                        imsi => Imsi,
+                                        message_class => 5,
+                                        cause => 16#11 % FIXME: Use proper 
defines as cause code and use Network failure
+                                        }
+       end,
+       lager:info("GSUP: Tx ~p~n", [Resp]),
+       ipa_proto:send(Socket, ?IPAC_PROTO_EXT_GSUP, Resp),
+       {noreply, State};
+
+handle_cast({tunnel_response, {Imsi, Result}}, State) ->
+       lager:info("tunnel_response for ~p: ~p~n", [Imsi, Result]),
+       Socket = State#gsups_state.socket,
+       case Result of
+               {ok, #gtp{version = v2, type = create_session_response}} ->
+                       {ok, CreateSessResp} = Result,
+                       IEs = CreateSessResp#gtp.ie,
+                       %%#{{v2_bearer_context,0} := BearerMap} = IEs,
+                       #{{v2_pdn_address_allocation,0} := Paa} = IEs,
+                       PdpAddress = #{pdp_type_org => 1, pdp_type_nr => 16#21, 
address => #{ ipv4 => Paa#v2_pdn_address_allocation.address}},
+                       PdpInfo = #{pdp_context_id => 0,
+                               pdp_address => PdpAddress,
+                               access_point_name => "foobar.apn",
+                               quality_of_service => <<0, 0, 0>>,
+                               pdp_charging => 0},
+                       Resp = #{message_type => epdg_tunnel_result,
+                               imsi => Imsi,
+                               message_class => 5,
+                               pdp_info_complete => true,
+                               pdp_info_list => [PdpInfo]
+                               };
+               {error, _} ->
+                       Resp = #{message_type => epdg_tunnel_error,
+                               imsi => Imsi,
+                               message_class => 5,
+                               cause => 16#11 % FIXME: Use proper defines as 
cause code and use Network failure
+                               }
+       end,
+       lager:info("GSUP: Tx ~p~n", [Resp]),
+       ipa_proto:send(Socket, ?IPAC_PROTO_EXT_GSUP, Resp),
+       {noreply, State};

 handle_cast(Info, S) ->
        error_logger:error_report(["unknown handle_cast", {module, ?MODULE}, 
{info, Info}, {state, S}]),
@@ -140,74 +205,47 @@
        {noreply, S#gsups_state{socket=Socket}};

 % send auth info / requesting authentication tuples
-handle_info({ipa, Socket, ?IPAC_PROTO_EXT_GSUP, GsupMsgRx = #{message_type := 
send_auth_info_req, imsi := Imsi}}, S) ->
-       Auth = auth_handler:auth_request(Imsi),
-       case Auth of
-               {ok, Mar} ->    SipAuthTuples = Mar#'MAA'.'SIP-Auth-Data-Item',
-                               % AuthTuples = dia_sip2gsup(SipAuthTuples),
-                               Resp = #{message_type => send_auth_info_res,
-                                        message_class => 5,
-                                        imsi => 
list_to_binary(Mar#'MAA'.'User-Name'),
-                                        auth_tuples => lists:map(fun 
dia_sip2gsup/1, SipAuthTuples)
-                                       };
-               {error, _} ->   Resp = #{message_type => send_auth_info_err, 
imsi => Imsi, message_class => 5, cause => 16#11}
-       end,
-       lager:info("auth tuples: ~p ~n", [Resp]),
-       ipa_proto:send(Socket, ?IPAC_PROTO_EXT_GSUP, Resp),
-       {noreply, S};
+handle_info({ipa, _Socket, ?IPAC_PROTO_EXT_GSUP, _GsupMsgRx = #{message_type 
:= send_auth_info_req, imsi := Imsi}}, State0) ->
+       {UE, State1} = find_or_new_gsups_ue(Imsi, State0),
+       ue_fsm:auth_request(UE#gsups_ue.pid),
+       {noreply, State1};

 % location update request / when a UE wants to connect to a specific APN. This 
will trigger a AAA->HLR Request Server Assignment Request
 % FIXME: add APN instead of hardcoded internet
-handle_info({ipa, Socket, ?IPAC_PROTO_EXT_GSUP, GsupMsgRx = #{message_type := 
location_upd_req, imsi := Imsi}}, S) ->
-       % FIXME: use enum for Server-Assignment-Type => REGISTERING
-       Result = epdg_diameter_swx:server_assignment_request(Imsi, 1, 
"internet"),
-       case Result of
-               {ok, Sar} ->    Resp = #{message_type => location_upd_res,
-                                        imsi => Imsi,
-                                        message_class => 5
-                                        };
-               {error, _} ->   Resp = #{message_type => location_upd_err,
-                                        imsi => Imsi,
-                                        message_class => 5,
-                                        cause => 16#11 % FIXME: Use proper 
defines as cause code and use Network failure
-                                        }
+handle_info({ipa, Socket, ?IPAC_PROTO_EXT_GSUP, _GsupMsgRx = #{message_type := 
location_upd_req, imsi := Imsi}}, State) ->
+       UE = find_gsups_ue_by_imsi(Imsi, State),
+       case UE of
+               #gsups_ue{imsi = Imsi} ->
+                       ue_fsm:lu_request(UE#gsups_ue.pid);
+               undefined ->
+                       Resp = #{message_type => location_upd_err,
+                                imsi => Imsi,
+                                message_class => 5,
+                                cause => 16#11 % FIXME: Use proper defines as 
cause code and use Network failure
+                       },
+                       lager:info("GSUP: Tx ~p~n", [Resp]),
+                       ipa_proto:send(Socket, ?IPAC_PROTO_EXT_GSUP, Resp)
        end,
-       ipa_proto:send(Socket, ?IPAC_PROTO_EXT_GSUP, Resp),
-       {noreply, S};
+       {noreply, State};

 % epdg tunnel request / trigger the establishment to the PGW and prepares 
everything for the user traffic to flow
 % When sending a epdg_tunnel_response everything must be ready for the UE 
traffic
-handle_info({ipa, Socket, ?IPAC_PROTO_EXT_GSUP, GsupMsgRx = #{message_type := 
epdg_tunnel_request, imsi := Imsi}}, S) ->
+handle_info({ipa, Socket, ?IPAC_PROTO_EXT_GSUP, GsupMsgRx = #{message_type := 
epdg_tunnel_request, imsi := Imsi}}, State) ->
        lager:info("GSUP: Rx ~p~n", [GsupMsgRx]),
-       Result = epdg_gtpc_s2b:create_session_req(Imsi),
-       case Result of
-               {ok, #gtp{version = v2, type = create_session_response}} ->
-                       {ok, CreateSessResp} = Result,
-                       IEs = CreateSessResp#gtp.ie,
-                       %%#{{v2_bearer_context,0} := BearerMap} = IEs,
-                       #{{v2_pdn_address_allocation,0} := Paa} = IEs,
-                       PdpAddress = #{pdp_type_org => 1, pdp_type_nr => 16#21, 
address => #{ ipv4 => Paa#v2_pdn_address_allocation.address}},
-                       PdpInfo = #{pdp_context_id => 0,
-                                   pdp_address => PdpAddress,
-                                   access_point_name => "foobar.apn",
-                                   quality_of_service => <<0, 0, 0>>,
-                                   pdp_charging => 0},
-                       Resp = #{message_type => epdg_tunnel_result,
-                                imsi => Imsi,
-                                message_class => 5,
-                                pdp_info_complete => true,
-                                pdp_info_list => [PdpInfo]
-                               };
-               {error, _} ->
+       UE = find_gsups_ue_by_imsi(Imsi, State),
+       case UE of
+               #gsups_ue{imsi = Imsi} ->
+                       ue_fsm:tunnel_request(UE#gsups_ue.pid);
+               undefined ->
                        Resp = #{message_type => epdg_tunnel_error,
                                 imsi => Imsi,
                                 message_class => 5,
                                 cause => 16#11 % FIXME: Use proper defines as 
cause code and use Network failure
-                               }
+                       },
+                       lager:info("GSUP: Tx ~p~n", [Resp]),
+                       ipa_proto:send(Socket, ?IPAC_PROTO_EXT_GSUP, Resp)
        end,
-       lager:info("GSUP: Tx ~p~n", [Resp]),
-       ipa_proto:send(Socket, ?IPAC_PROTO_EXT_GSUP, Resp),
-       {noreply, S};
+       {noreply, State};

 handle_info(Info, S) ->
        error_logger:error_report(["unknown handle_info", {module, ?MODULE}, 
{info, Info}, {state, S}]),
@@ -218,3 +256,43 @@

 code_change(_OldVsn, State, _Extra) ->
        {ok, State}.
+
+auth_response(Imsi, Auth) ->
+       lager:info("auth_response(~p): ~p~n", [Imsi, Auth]),
+       gen_server:cast(?SERVER, {auth_response, {Imsi, Auth}}).
+
+lu_response(Imsi, Result) ->
+       lager:info("lu_response(~p): ~p~n", [Imsi, Result]),
+       gen_server:cast(?SERVER, {lu_response, {Imsi, Result}}).
+
+tunnel_response(Imsi, Result) ->
+       lager:info("tunnel_response(~p): ~p~n", [Imsi, Result]),
+       gen_server:cast(?SERVER, {tunnel_response, {Imsi, Result}}).
+
+%% ------------------------------------------------------------------
+%% Internal Function Definitions
+%% ------------------------------------------------------------------
+
+new_gsups_ue(Imsi, State) ->
+       {ok, Pid} = ue_fsm:start_link(Imsi),
+       UE = #gsups_ue{imsi = Imsi, pid = Pid},
+       NewSt = State#gsups_state{ues = sets:add_element(UE, 
State#gsups_state.ues)},
+       {UE, NewSt}.
+
+% returns gsups_ue if found, undefined it not
+find_gsups_ue_by_imsi(Imsi, State) ->
+       sets:fold(
+           fun(UEsIt = #gsups_ue{imsi = Imsi}, _AccIn) -> UEsIt;
+              (_, AccIn) -> AccIn
+           end,
+           undefined,
+           State#gsups_state.ues).
+
+find_or_new_gsups_ue(Imsi, State) ->
+       UE = find_gsups_ue_by_imsi(Imsi, State),
+       case UE of
+           #gsups_ue{imsi = Imsi} ->
+               {UE, State};
+           undefined ->
+               new_gsups_ue(Imsi, State)
+       end.
\ No newline at end of file
diff --git a/src/ue_fsm.erl b/src/ue_fsm.erl
new file mode 100644
index 0000000..0fe6992
--- /dev/null
+++ b/src/ue_fsm.erl
@@ -0,0 +1,110 @@
+% UE FSM
+% (C) 2023 by sysmocom
+%
+% All Rights Reserved
+%
+% 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 <http://www.gnu.org/licenses/>.
+%
+% 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 http://www.erlang.org (or a modified version of these
+% libraries), containing parts covered by the terms of the Erlang Public
+% License (http://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(ue_fsm).
+-behaviour(gen_statem).
+-define(NAME, ue_fsm).
+
+-export([start_link/1]).
+-export([init/1,callback_mode/0,terminate/3]).
+-export([auth_request/1, lu_request/1, tunnel_request/1]).
+-export([state_new/3,state_authenticated/3]).
+
+-record(ue_fsm_data, {
+        imsi
+        }).
+
+start_link(Imsi) ->
+        ServerName = lists:concat([?NAME, "_", binary_to_list(Imsi)]),
+        lager:info("ue_fsm start_link(~p)~n", [ServerName]),
+        gen_statem:start_link({local, list_to_atom(ServerName)}, ?MODULE, 
Imsi, [{debug, [trace]}]).
+
+auth_request(Pid) ->
+        lager:info("ue_fsm auth_request~n", []),
+        gen_statem:cast(Pid, auth_request).
+
+lu_request(Pid) ->
+        lager:info("ue_fsm lu_request~n", []),
+        gen_statem:cast(Pid, lu_request).
+
+tunnel_request(Pid) ->
+        lager:info("ue_fsm tunnel_request~n", []),
+        gen_statem:cast(Pid, tunnel_request).
+
+init(Imsi) ->
+        lager:info("ue_fsm init(~p)~n", [Imsi]),
+        Data = #ue_fsm_data{imsi = Imsi},
+        {ok, state_new, Data}.
+
+callback_mode() ->
+        state_functions.
+
+terminate(Reason, State, Data) ->
+        lager:info("terminating ~p with reason ~p state=~p, ~p~n", [?MODULE, 
Reason, State, Data]),
+        ok.
+
+state_new(cast, auth_request, Data) ->
+        lager:info("ue_fsm state_new event=auth_request, ~p~n", [Data]),
+        Auth = auth_handler:auth_request(Data#ue_fsm_data.imsi),
+        gsup_server:auth_response(Data#ue_fsm_data.imsi, Auth),
+        case Auth of
+                {ok, _} ->
+                        {next_state, state_authenticated, Data};
+               {error, Err} ->
+                        {stop, Err, Data}
+       end.
+
+state_authenticated(cast, lu_request, Data) ->
+        lager:info("ue_fsm state_authenticated event=lu_request, ~p~n", 
[Data]),
+        Result = 
epdg_diameter_swx:server_assignment_request(Data#ue_fsm_data.imsi, 1, 
"internet"),
+        gsup_server:lu_response(Data#ue_fsm_data.imsi, Result),
+        case Result of
+                {ok, _} ->
+                        {keep_state, Data};
+                {error, Err} ->
+                        {stop, Err, Data}
+        end;
+
+state_authenticated(cast, tunnel_request, Data) ->
+        lager:info("ue_fsm state_authenticated event=tunnel_request, ~p~n", 
[Data]),
+        Result = epdg_gtpc_s2b:create_session_req(Data#ue_fsm_data.imsi),
+        gsup_server:tunnel_response(Data#ue_fsm_data.imsi, Result),
+        case Result of
+                {ok, _} ->
+                        {keep_state, Data};
+                {error, Err} ->
+                        {stop, Err, Data}
+        end;
+
+state_authenticated(cast, _Whatever, Data) ->
+        lager:info("ue_fsm state_authenticated event=auth_request, ~p~n", 
[Data]),
+        {keep_state, Data}.
\ No newline at end of file

--
To view, visit https://gerrit.osmocom.org/c/erlang/osmo-epdg/+/35659?usp=email
To unsubscribe, or for help writing mail filters, visit 
https://gerrit.osmocom.org/settings

Gerrit-Project: erlang/osmo-epdg
Gerrit-Branch: master
Gerrit-Change-Id: I0c960c4c250458384ed706a99582ec52083019f6
Gerrit-Change-Number: 35659
Gerrit-PatchSet: 1
Gerrit-Owner: pespin <pes...@sysmocom.de>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: dexter <pma...@sysmocom.de>
Gerrit-Reviewer: laforge <lafo...@osmocom.org>
Gerrit-Reviewer: pespin <pes...@sysmocom.de>
Gerrit-MessageType: merged

Reply via email to