%% Copyright (C) 2024 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(s1ap_proxy). -behaviour(gen_server). -export([init/1, handle_info/2, handle_call/3, handle_cast/2, terminate/2]). -export([start_link/0, process_pdu/2, fetch_erab/2, fetch_erab_list/1, shutdown/1]). -include_lib("kernel/include/logger.hrl"). -include("s1gw_metrics.hrl"). -include("S1AP-PDU-Descriptions.hrl"). -include("S1AP-PDU-Contents.hrl"). -include("S1AP-Containers.hrl"). -include("S1AP-Constants.hrl"). -include("S1AP-IEs.hrl"). -type s1ap_pdu() :: {initiatingMessage, #'InitiatingMessage'{}} | {successfulOutcome, #'SuccessfulOutcome'{}} | {unsuccessfulOutcome, #'UnsuccessfulOutcome'{}}. -type s1ap_ie_id() :: non_neg_integer(). -type s1ap_ie_val() :: tuple(). -type mme_ue_id() :: 0..16#ffffffff. -type enb_ue_id() :: 0..16#ffffff. -type erab_id() :: 0..16#ff. -type erab_uid() :: {mme_ue_id(), enb_ue_id(), erab_id()}. -record(proxy_state, {erabs :: dict:dict(), mme_ue_id :: undefined | mme_ue_id(), enb_ue_id :: undefined | enb_ue_id(), path :: [s1ap_ie_id()] }). -type proxy_state() :: #proxy_state{}. -type proxy_action() :: forward | reply. -export_type([proxy_action/0]). %% ------------------------------------------------------------------ %% public API %% ------------------------------------------------------------------ -spec start_link() -> gen_server:start_ret(). start_link() -> gen_server:start_link(?MODULE, [], []). -type process_pdu_result() :: {proxy_action(), binary()}. -spec process_pdu(pid(), binary()) -> process_pdu_result(). process_pdu(Pid, PDU) -> gen_server:call(Pid, {?FUNCTION_NAME, PDU}). %% Fetch a single E-RAB from the registry (by UID) -spec fetch_erab(pid(), erab_uid()) -> {ok, pid()} | error. fetch_erab(Pid, UID) -> gen_server:call(Pid, {?FUNCTION_NAME, UID}). %% Fetch all E-RABs from the registry in form of a list -spec fetch_erab_list(pid()) -> [{erab_uid(), pid()}]. fetch_erab_list(Pid) -> gen_server:call(Pid, ?FUNCTION_NAME). -spec shutdown(pid()) -> ok. shutdown(Pid) -> gen_server:stop(Pid). %% ------------------------------------------------------------------ %% gen_server API %% ------------------------------------------------------------------ init([]) -> process_flag(trap_exit, true), {ok, #proxy_state{erabs = dict:new(), path = []}}. handle_call({process_pdu, OrigData}, _From, #proxy_state{} = S0) -> {Reply, S1} = handle_pdu_bin(OrigData, S0), {reply, Reply, S1}; handle_call({fetch_erab, UID}, _From, #proxy_state{erabs = ERABs} = S) -> {reply, dict:find(UID, ERABs), S}; handle_call(fetch_erab_list, _From, #proxy_state{erabs = ERABs} = S) -> {reply, dict:to_list(ERABs), S}; handle_call(Info, From, #proxy_state{} = S) -> ?LOG_ERROR("unknown ~p() from ~p: ~p", [?FUNCTION_NAME, From, Info]), {reply, {error, not_implemented}, S}. handle_cast(Info, #proxy_state{} = S) -> ?LOG_ERROR("unknown ~p(): ~p", [?FUNCTION_NAME, Info]), {noreply, S}. handle_info({'EXIT', Pid, Reason}, #proxy_state{erabs = ERABs} = S) -> ?LOG_DEBUG("Child process ~p terminated with reason ~p", [Pid, Reason]), Fun = fun(_Key, Val) -> Val =/= Pid end, {noreply, S#proxy_state{erabs = dict:filter(Fun, ERABs)}}; handle_info(Info, #proxy_state{} = S) -> ?LOG_ERROR("unknown ~p(): ~p", [?FUNCTION_NAME, Info]), {noreply, S}. terminate(Reason, #proxy_state{}) -> ?LOG_NOTICE("Terminating, reason ~p", [Reason]), ok. %% ------------------------------------------------------------------ %% private API %% ------------------------------------------------------------------ -spec erab_uid(erab_id(), proxy_state()) -> erab_uid(). erab_uid(ERABId, #proxy_state{mme_ue_id = MmeUeId, enb_ue_id = EnbUeId}) -> {MmeUeId, EnbUeId, ERABId}. %% Encode an S1AP PDU -spec encode_pdu(s1ap_pdu()) -> {ok, binary()} | {error, {asn1, tuple()}}. encode_pdu(Pdu) -> 'S1AP-PDU-Descriptions':encode('S1AP-PDU', Pdu). %% Decode an S1AP PDU -spec decode_pdu(binary()) -> {ok, s1ap_pdu()} | {error, {asn1, tuple()}}. decode_pdu(Data) -> 'S1AP-PDU-Descriptions':decode('S1AP-PDU', Data). %% Process an S1AP PDU (binary) -spec handle_pdu_bin(binary(), proxy_state()) -> {process_pdu_result(), proxy_state()}. handle_pdu_bin(OrigData, S0) -> s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_ALL), try decode_pdu(OrigData) of {ok, PDU} -> ?LOG_DEBUG("Rx S1AP PDU: ~p", [PDU]), case handle_pdu(PDU, S0) of {{Action, NewPDU}, S1} -> {ok, NewData} = encode_pdu(NewPDU), ?LOG_DEBUG("Tx (~p) S1AP PDU: ~p", [Action, NewPDU]), case Action of forward -> s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_OUT_PKT_FWD_ALL), s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_OUT_PKT_FWD_PROC); reply -> s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_OUT_PKT_REPLY_ALL) end, {{Action, NewData}, S1}; {forward, S1} -> ?LOG_DEBUG("Tx (forward) S1AP PDU unmodified"), s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_OUT_PKT_FWD_ALL), s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_OUT_PKT_FWD_UNMODIFIED), {{forward, OrigData}, S1} end; {error, {asn1, Error}} -> ?LOG_ERROR("S1AP PDU decoding failed: ~p", [Error]), s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_DECODE_ERROR), s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_OUT_PKT_FWD_UNMODIFIED), {{forward, OrigData}, S0} %% XXX: forward as-is or drop? catch Exception:Reason:StackTrace -> ?LOG_ERROR("An exception occurred: ~p, ~p, ~p", [Exception, Reason, StackTrace]), s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_EXCEPTION), s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_OUT_PKT_FWD_UNMODIFIED), {{forward, OrigData}, S0} %% XXX: proxy as-is or drop? end. %% Process an S1AP PDU (decoded) -spec handle_pdu(s1ap_pdu(), proxy_state()) -> {{proxy_action(), s1ap_pdu()}, proxy_state()} | {forward, proxy_state()}. %% 9.1.3.1 E-RAB SETUP REQUEST handle_pdu({Outcome = initiatingMessage, #'InitiatingMessage'{procedureCode = ?'id-E-RABSetup', value = C0} = Msg}, S0) -> ?LOG_DEBUG("Processing E-RAB SETUP REQUEST"), s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_ERAB_SETUP_REQ), case handle_ies(?'id-E-RABToBeSetupListBearerSUReq', C0#'E-RABSetupRequest'.protocolIEs, S0) of {{ok, IEs}, S1} -> C1 = C0#'E-RABSetupRequest'{protocolIEs = IEs}, PDU = {Outcome, Msg#'InitiatingMessage'{value = C1}}, {{forward, PDU}, S1}; %% forward patched PDU {{error, Reason}, S1} -> ?LOG_NOTICE("Failed to process E-RAB SETUP REQUEST: ~p", [Reason]), s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_PROC_ERROR), PDU = build_erab_setup_response_failure(S1), s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_OUT_PKT_REPLY_ERAB_SETUP_RSP), {{reply, PDU}, S1} %% reply PDU back to sender end; %% 9.1.3.2 E-RAB SETUP RESPONSE handle_pdu({Outcome = successfulOutcome, #'SuccessfulOutcome'{procedureCode = ?'id-E-RABSetup', value = C0} = Msg}, S0) -> ?LOG_DEBUG("Processing E-RAB SETUP RESPONSE"), s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_ERAB_SETUP_RSP), case handle_ies(?'id-E-RABSetupListBearerSURes', C0#'E-RABSetupResponse'.protocolIEs, S0) of {{ok, IEs}, S1} -> C1 = C0#'E-RABSetupResponse'{protocolIEs = IEs}, PDU = {Outcome, Msg#'SuccessfulOutcome'{value = C1}}, {{forward, PDU}, S1}; %% forward patched PDU {{error, Reason}, S1} -> ?LOG_NOTICE("Failed to process E-RAB SETUP RESPONSE: ~p", [Reason]), s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_PROC_ERROR), {forward, S1} %% XXX: forward as-is or drop? end; %% TODO: 9.1.3.3 E-RAB MODIFY REQUEST / (Optional) Transport Information %% 9.1.3.5 E-RAB RELEASE COMMAND handle_pdu({Outcome = initiatingMessage, #'InitiatingMessage'{procedureCode = ?'id-E-RABRelease', value = C0} = Msg}, S0) -> ?LOG_DEBUG("Processing E-RAB RELEASE COMMAND"), s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_ERAB_RELEASE_CMD), case handle_ies(?'id-E-RABToBeReleasedList', C0#'E-RABReleaseCommand'.protocolIEs, S0) of {{ok, IEs}, S1} -> C1 = C0#'E-RABReleaseCommand'{protocolIEs = IEs}, PDU = {Outcome, Msg#'InitiatingMessage'{value = C1}}, {{forward, PDU}, S1}; %% forward patched PDU {{error, Reason}, S1} -> ?LOG_NOTICE("Failed to process E-RAB RELEASE COMMAND: ~p", [Reason]), s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_PROC_ERROR), {forward, S1} %% XXX: forward as-is or drop? end; %% 9.1.3.6 E-RAB RELEASE RESPONSE handle_pdu({Outcome = successfulOutcome, #'SuccessfulOutcome'{procedureCode = ?'id-E-RABRelease', value = C0} = Msg}, S0) -> ?LOG_DEBUG("Processing E-RAB RELEASE RESPONSE"), s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_ERAB_RELEASE_RSP), case handle_ies(?'id-E-RABReleaseListBearerRelComp', C0#'E-RABReleaseResponse'.protocolIEs, S0) of {{ok, IEs}, S1} -> C1 = C0#'E-RABReleaseResponse'{protocolIEs = IEs}, PDU = {Outcome, Msg#'SuccessfulOutcome'{value = C1}}, {{forward, PDU}, S1}; %% forward patched PDU {{error, Reason}, S1} -> ?LOG_NOTICE("Failed to process E-RAB RELEASE RESPONSE: ~p", [Reason]), s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_PROC_ERROR), {forward, S1} %% XXX: forward as-is or drop? end; %% 9.1.3.7 E-RAB RELEASE INDICATION handle_pdu({Outcome = initiatingMessage, #'InitiatingMessage'{procedureCode = ?'id-E-RABReleaseIndication', value = C0} = Msg}, S0) -> ?LOG_DEBUG("Processing E-RAB RELEASE INDICATION"), s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_ERAB_RELEASE_IND), case handle_ies(?'id-E-RABReleasedList', C0#'E-RABReleaseIndication'.protocolIEs, S0) of {{ok, IEs}, S1} -> C1 = C0#'E-RABReleaseIndication'{protocolIEs = IEs}, PDU = {Outcome, Msg#'InitiatingMessage'{value = C1}}, {{forward, PDU}, S1}; %% forward patched PDU {{error, Reason}, S1} -> ?LOG_NOTICE("Failed to process E-RAB RELEASE INDICATION: ~p", [Reason]), s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_PROC_ERROR), {forward, S1} %% XXX: forward as-is or drop? end; %% 9.1.3.8 E-RAB MODIFICATION INDICATION handle_pdu({Outcome = initiatingMessage, #'InitiatingMessage'{procedureCode = ?'id-E-RABModificationIndication', value = C0} = Msg}, S0) -> ?LOG_DEBUG("Processing E-RAB MODIFICATION INDICATION"), s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_ERAB_MOD_IND), IEs0 = C0#'E-RABModificationIndication'.protocolIEs, %% E-RAB to be Modified List %% TODO: handle {error, Reason} {{ok, IEs1}, S1} = handle_ies(?'id-E-RABToBeModifiedListBearerModInd', IEs0, S0), %% E-RAB not to be Modified List %% TODO: handle {error, Reason} {{ok, IEs2}, S2} = handle_ies(?'id-E-RABNotToBeModifiedListBearerModInd', IEs1, S1), C1 = C0#'E-RABModificationIndication'{protocolIEs = IEs2}, PDU = {Outcome, Msg#'InitiatingMessage'{value = C1}}, {{forward, PDU}, S2}; %% 9.1.4.1 INITIAL CONTEXT SETUP REQUEST handle_pdu({Outcome = initiatingMessage, #'InitiatingMessage'{procedureCode = ?'id-InitialContextSetup', value = C0} = Msg}, S0) -> ?LOG_DEBUG("Processing INITIAL CONTEXT SETUP REQUEST"), s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_INIT_CTX_REQ), case handle_ies(?'id-E-RABToBeSetupListCtxtSUReq', C0#'InitialContextSetupRequest'.protocolIEs, S0) of {{ok, IEs}, S1} -> C1 = C0#'InitialContextSetupRequest'{protocolIEs = IEs}, PDU = {Outcome, Msg#'InitiatingMessage'{value = C1}}, {{forward, PDU}, S1}; %% forward patched PDU {{error, Reason}, S1} -> ?LOG_NOTICE("Failed to process INITIAL CONTEXT SETUP REQUEST: ~p", [Reason]), s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_PROC_ERROR), {forward, S1} %% XXX: forward as-is or drop? end; %% 9.1.4.3 INITIAL CONTEXT SETUP RESPONSE handle_pdu({Outcome = successfulOutcome, #'SuccessfulOutcome'{procedureCode = ?'id-InitialContextSetup', value = C0} = Msg}, S0) -> ?LOG_DEBUG("Processing INITIAL CONTEXT SETUP RESPONSE"), s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_INIT_CTX_RSP), %% TODO: handle optional E-RAB Failed to Setup List IE case handle_ies(?'id-E-RABSetupListCtxtSURes', C0#'InitialContextSetupResponse'.protocolIEs, S0) of {{ok, IEs}, S1} -> C1 = C0#'InitialContextSetupResponse'{protocolIEs = IEs}, PDU = {Outcome, Msg#'SuccessfulOutcome'{value = C1}}, {{forward, PDU}, S1}; %% forward patched PDU {{error, Reason}, S1} -> ?LOG_NOTICE("Failed to process INITIAL CONTEXT SETUP RESPONSE: ~p", [Reason]), s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_PROC_ERROR), {forward, S1} %% XXX: forward as-is or drop? end; %% TODO: 9.1.5.2 HANDOVER COMMAND :: (O) UL/DL Transport Layer Address %% TODO: 9.1.5.4 HANDOVER REQUEST :: (M) Transport Layer Address %% TODO: 9.1.5.5 HANDOVER REQUEST ACKNOWLEDGE :: (M) Transport Layer Address, %% (O) UL/DL Transport Layer Address %% TODO: 9.1.5.8 PATH SWITCH REQUEST :: (M) Transport Layer Address %% TODO: 9.1.5.9 PATH SWITCH REQUEST ACKNOWLEDGE :: (M) Transport Layer Address %% Proxy all other messages unmodified handle_pdu(_PDU, S) -> {forward, S}. %% Handle a single IE (Information Element) -type handle_ie_result() :: {ok, s1ap_ie_val()} | {error, term()}. -spec handle_ie(Path, Content, State) -> Result when Path :: [s1ap_ie_id()], Content :: s1ap_ie_val(), State :: proxy_state(), Result :: {handle_ie_result(), proxy_state()}. %% E-RAB SETUP REQUEST related IEs handle_ie([?'id-E-RABToBeSetupListBearerSUReq'], C, S) -> %% This IE contains a list of BearerSUReq, so patch inner IEs handle_ies(?'id-E-RABToBeSetupItemBearerSUReq', C, S); handle_ie([?'id-E-RABToBeSetupItemBearerSUReq', ?'id-E-RABToBeSetupListBearerSUReq'], #'E-RABToBeSetupItemBearerSUReq'{'e-RAB-ID' = ERABId, 'transportLayerAddress' = TLA_In, 'gTP-TEID' = << TEID_In:32/big >>} = C0, S0) -> %% start and register an E-RAB FSM {Pid, S1} = erab_fsm_start_reg(ERABId, S0), case erab_fsm:erab_setup_req(Pid, {TEID_In, TLA_In}) of {ok, {TEID_Out, TLA_Out}} -> C1 = C0#'E-RABToBeSetupItemBearerSUReq'{'transportLayerAddress' = TLA_Out, 'gTP-TEID' = << TEID_Out:32/big >>}, {{ok, C1}, S1}; {error, Reason} -> {{error, Reason}, S1} end; %% E-RAB SETUP RESPONSE related IEs handle_ie([?'id-E-RABSetupListBearerSURes'], C, S) -> %% This IE contains a list of BearerSURes, so patch inner IEs handle_ies(?'id-E-RABSetupItemBearerSURes', C, S); handle_ie([?'id-E-RABSetupItemBearerSURes', ?'id-E-RABSetupListBearerSURes'], #'E-RABSetupItemBearerSURes'{'e-RAB-ID' = ERABId, 'transportLayerAddress' = TLA_In, 'gTP-TEID' = << TEID_In:32/big >>} = C0, S) -> %% poke E-RAB FSM case erab_fsm_find(ERABId, S) of {ok, Pid} -> case erab_fsm:erab_setup_rsp(Pid, {TEID_In, TLA_In}) of {ok, {TEID_Out, TLA_Out}} -> C1 = C0#'E-RABSetupItemBearerSURes'{'transportLayerAddress' = TLA_Out, 'gTP-TEID' = << TEID_Out:32/big >>}, {{ok, C1}, S}; {error, Reason} -> {{error, Reason}, S} end; error -> ?LOG_ERROR("E-RAB-ID ~p is not registered", [erab_uid(ERABId, S)]), {{error, erab_not_registered}, S} end; %% 9.1.3.5 E-RAB RELEASE COMMAND related IEs handle_ie([?'id-E-RABToBeReleasedList'], C, S) -> %% This IE contains a list of E-RABItem handle_ies(?'id-E-RABItem', C, S); handle_ie([?'id-E-RABItem', ?'id-E-RABToBeReleasedList'], #'E-RABItem'{'e-RAB-ID' = ERABId} = C, S) -> %% poke E-RAB FSM case erab_fsm_find(ERABId, S) of {ok, Pid} -> ok = erab_fsm:erab_release(Pid, cmd), {{ok, C}, S}; error -> ?LOG_ERROR("E-RAB ~p is not registered", [erab_uid(ERABId, S)]), {{error, erab_not_registered}, S} end; %% 9.1.3.6 E-RAB RELEASE RESPONSE related IEs handle_ie([?'id-E-RABReleaseListBearerRelComp'], C, S) -> %% This IE contains a list of E-RABReleaseItemBearerRelComp handle_ies(?'id-E-RABReleaseItemBearerRelComp', C, S); handle_ie([?'id-E-RABReleaseItemBearerRelComp', ?'id-E-RABReleaseListBearerRelComp'], #'E-RABReleaseItemBearerRelComp'{'e-RAB-ID' = ERABId} = C, S) -> %% poke E-RAB FSM case erab_fsm_find(ERABId, S) of {ok, Pid} -> ok = erab_fsm:erab_release_rsp(Pid), {{ok, C}, S}; error -> ?LOG_ERROR("E-RAB ~p is not registered", [erab_uid(ERABId, S)]), {{error, erab_not_registered}, S} end; %% 9.1.3.7 E-RAB RELEASE INDICATION related IEs handle_ie([?'id-E-RABReleasedList'], C, S) -> %% This IE contains a list of E-RABItem handle_ies(?'id-E-RABItem', C, S); handle_ie([?'id-E-RABItem', ?'id-E-RABReleasedList'], #'E-RABItem'{'e-RAB-ID' = ERABId} = C, S) -> %% poke E-RAB FSM case erab_fsm_find(ERABId, S) of {ok, Pid} -> ok = erab_fsm:erab_release(Pid, ind), {{ok, C}, S}; error -> ?LOG_ERROR("E-RAB ~p is not registered", [erab_uid(ERABId, S)]), {{error, erab_not_registered}, S} end; %% E-RAB MODIFICATION INDICATION related IEs handle_ie([?'id-E-RABToBeModifiedListBearerModInd'], C, S) -> %% This IE contains a list of BearerModInd, so patch inner IEs handle_ies(?'id-E-RABToBeModifiedItemBearerModInd', C, S); handle_ie([?'id-E-RABToBeModifiedItemBearerModInd', ?'id-E-RABToBeModifiedListBearerModInd'], #'E-RABToBeModifiedItemBearerModInd'{} = C, S) -> %% TODO: find and poke an E-RAB FSM associated with this E-RAB {{ok, C}, S}; handle_ie([?'id-E-RABNotToBeModifiedListBearerModInd'], C, S) -> %% This IE contains a list of BearerModInd, so patch inner IEs handle_ies(?'id-E-RABNotToBeModifiedItemBearerModInd', C, S); handle_ie([?'id-E-RABNotToBeModifiedItemBearerModInd', ?'id-E-RABNotToBeModifiedListBearerModInd'], #'E-RABNotToBeModifiedItemBearerModInd'{} = C, S) -> %% TODO: find and poke an E-RAB FSM associated with this E-RAB {{ok, C}, S}; %% INITIAL CONTEXT SETUP REQUEST related IEs handle_ie([?'id-E-RABToBeSetupListCtxtSUReq'], C, S) -> %% This IE contains a list of CtxtSUReq, so patch inner IEs handle_ies(?'id-E-RABToBeSetupItemCtxtSUReq', C, S); handle_ie([?'id-E-RABToBeSetupItemCtxtSUReq', ?'id-E-RABToBeSetupListCtxtSUReq'], #'E-RABToBeSetupItemCtxtSUReq'{'e-RAB-ID' = ERABId, 'transportLayerAddress' = TLA_In, 'gTP-TEID' = << TEID_In:32/big >>} = C0, S0) -> %% start and register an E-RAB FSM {Pid, S1} = erab_fsm_start_reg(ERABId, S0), case erab_fsm:erab_setup_req(Pid, {TEID_In, TLA_In}) of {ok, {TEID_Out, TLA_Out}} -> C1 = C0#'E-RABToBeSetupItemCtxtSUReq'{'transportLayerAddress' = TLA_Out, 'gTP-TEID' = << TEID_Out:32/big >>}, {{ok, C1}, S1}; {error, Reason} -> {{error, Reason}, S1} end; %% INITIAL CONTEXT SETUP RESPONSE related IEs handle_ie([?'id-E-RABSetupListCtxtSURes'], C, S) -> %% This IE contains a list of CtxtSURes, so patch inner IEs handle_ies(?'id-E-RABSetupItemCtxtSURes', C, S); handle_ie([?'id-E-RABSetupItemCtxtSURes', ?'id-E-RABSetupListCtxtSURes'], #'E-RABSetupItemCtxtSURes'{'e-RAB-ID' = ERABId, 'transportLayerAddress' = TLA_In, 'gTP-TEID' = << TEID_In:32/big >>} = C0, S) -> %% poke E-RAB FSM case erab_fsm_find(ERABId, S) of {ok, Pid} -> case erab_fsm:erab_setup_rsp(Pid, {TEID_In, TLA_In}) of {ok, {TEID_Out, TLA_Out}} -> C1 = C0#'E-RABSetupItemCtxtSURes'{'transportLayerAddress' = TLA_Out, 'gTP-TEID' = << TEID_Out:32/big >>}, {{ok, C1}, S}; {error, Reason} -> {{error, Reason}, S} end; error -> ?LOG_ERROR("E-RAB ~p is not registered", [erab_uid(ERABId, S)]), {{error, erab_not_registered}, S} end; %% Catch-all variant, which should not be called normally handle_ie(P, C, S) -> ?LOG_ERROR("[BUG] Unhandled S1AP IE: ~p, ~p", [P, C]), {{error, unhandled_ie}, S}. %% Iterate over the given list of 'ProtocolIE-Field' IEs, %% calling function handle_ie/1 for IEs matching the given IEI. %% Additionally look for {MME,eNB}-UE-S1AP-ID IEs and store their values. -type handle_ies_result() :: {ok, list()} | {error, term()}. -spec handle_ies(s1ap_ie_id(), list(), proxy_state()) -> {handle_ies_result(), proxy_state()}. handle_ies(IEI, IEs, #proxy_state{path = P} = S) -> handle_ies([], IEI, IEs, S#proxy_state{path = [IEI | P]}). handle_ies(Acc, IEI, [IE | IEs], #proxy_state{path = P} = S0) -> case IE of #'ProtocolIE-Field'{id = IEI, value = C0} -> case handle_ie(P, C0, S0) of {{ok, C1}, S1} -> NewIE = IE#'ProtocolIE-Field'{value = C1}, handle_ies([NewIE | Acc], IEI, IEs, S1); {{error, Reason}, S1} -> {{error, Reason}, S1} end; #'ProtocolExtensionField'{id = IEI, extensionValue = C0} -> case handle_ie(P, C0, S0) of {{ok, C1}, S1} -> NewIE = IE#'ProtocolExtensionField'{extensionValue = C1}, handle_ies([NewIE | Acc], IEI, IEs, S1); {{error, Reason}, S1} -> {{error, Reason}, S1} end; #'ProtocolIE-Field'{id = ?'id-MME-UE-S1AP-ID', value = Id} -> S1 = S0#proxy_state{mme_ue_id = Id}, handle_ies([IE | Acc], IEI, IEs, S1); #'ProtocolIE-Field'{id = ?'id-eNB-UE-S1AP-ID', value = Id} -> S1 = S0#proxy_state{enb_ue_id = Id}, handle_ies([IE | Acc], IEI, IEs, S1); _ -> handle_ies([IE | Acc], IEI, IEs, S0) end; handle_ies(Acc, IEI, [], #proxy_state{path = [IEI | P]} = S) -> IEs = lists:reverse(Acc), {{ok, IEs}, S#proxy_state{path = P}}. build_erab_setup_response_failure(#proxy_state{erabs = ERABs, mme_ue_id = MmeUeId, enb_ue_id = EnbUeId}) -> %% FIXME: Currently we respond with E-RAB-ID of the first E-RAB in the registry. %% Instead, we need to iterate over E-RABs in the REQUEST and reject them all. [{_, _, FirstERABid}|_] = dict:fetch_keys(ERABs), Cause = {transport, 'transport-resource-unavailable'}, ERABitem = #'E-RABItem'{'e-RAB-ID' = FirstERABid, cause = Cause}, ERABlist = [#'ProtocolIE-Field'{id = ?'id-E-RABItem', criticality = ignore, value = ERABitem}], IEs = [#'ProtocolIE-Field'{id = ?'id-MME-UE-S1AP-ID', criticality = ignore, value = MmeUeId}, #'ProtocolIE-Field'{id = ?'id-eNB-UE-S1AP-ID', criticality = ignore, value = EnbUeId}, #'ProtocolIE-Field'{id = ?'id-E-RABFailedToSetupListBearerSURes', criticality = ignore, value = ERABlist}], {successfulOutcome, #'SuccessfulOutcome'{procedureCode = ?'id-E-RABSetup', criticality = reject, value = #'E-RABSetupResponse'{protocolIEs = IEs}}}. -spec erab_fsm_start_reg(erab_id(), proxy_state()) -> {pid(), proxy_state()}. erab_fsm_start_reg(RABId, #proxy_state{erabs = ERABs} = S) -> UID = erab_uid(RABId, S), {ok, Pid} = erab_fsm:start_link(UID), {Pid, S#proxy_state{erabs = dict:store(UID, Pid, ERABs)}}. -spec erab_fsm_find(erab_id(), proxy_state()) -> {ok, pid()} | error. erab_fsm_find(RABId, #proxy_state{erabs = ERABs} = S) -> UID = erab_uid(RABId, S), dict:find(UID, ERABs). %% vim:set ts=4 sw=4 et: