%% 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). -export([init/0, deinit/1, process_pdu/2, process_pdu_safe/2, handle_exit/2, encode_pdu/1, decode_pdu/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() :: #'ProtocolIE-Field'{}. -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() }). -type proxy_state() :: #proxy_state{}. -type proxy_action() :: forward | reply. -export_type([proxy_action/0]). %% ------------------------------------------------------------------ %% public API %% ------------------------------------------------------------------ %% Initialize per-connection data -spec init() -> proxy_state(). init() -> #proxy_state{erabs = dict:new()}. %% De-initialize per-connection data -spec deinit(proxy_state()) -> ok. deinit(_S) -> ok. %% Process an S1AP PDU -type process_pdu_result() :: {{proxy_action(), binary()}, proxy_state()}. -spec process_pdu(binary(), proxy_state()) -> process_pdu_result(). process_pdu(OrigData, S0) -> s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_ALL), case 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? end. %% A safe wrapper for proc/2 -spec process_pdu_safe(binary(), proxy_state()) -> process_pdu_result(). process_pdu_safe(OrigData, S) -> try process_pdu(OrigData, S) of Result -> Result 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}, S} %% XXX: proxy as-is or drop? end. %% Handle an exit event -spec handle_exit(pid(), proxy_state()) -> proxy_state(). handle_exit(Pid, #proxy_state{erabs = ERABs} = S) -> Fun = fun(_Key, Val) -> Val =/= Pid end, S#proxy_state{erabs = dict:filter(Fun, ERABs)}. %% ------------------------------------------------------------------ %% 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 -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(C0#'E-RABSetupRequest'.protocolIEs, ?'id-E-RABToBeSetupListBearerSUReq', 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(C0#'E-RABSetupResponse'.protocolIEs, ?'id-E-RABSetupListBearerSURes', 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(C0#'E-RABReleaseCommand'.protocolIEs, ?'id-E-RABToBeReleasedList', 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(C0#'E-RABReleaseResponse'.protocolIEs, ?'id-E-RABReleaseListBearerRelComp', 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(C0#'E-RABReleaseIndication'.protocolIEs, ?'id-E-RABReleasedList', 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(IEs0, ?'id-E-RABToBeModifiedListBearerModInd', S0), %% E-RAB not to be Modified List %% TODO: handle {error, Reason} {{ok, IEs2}, S2} = handle_ies(IEs1, ?'id-E-RABNotToBeModifiedListBearerModInd', 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(C0#'InitialContextSetupRequest'.protocolIEs, ?'id-E-RABToBeSetupListCtxtSUReq', 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), case handle_ies(C0#'InitialContextSetupResponse'.protocolIEs, ?'id-E-RABSetupListCtxtSURes', 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(s1ap_ie(), proxy_state()) -> {handle_ie_result(), proxy_state()}. %% E-RAB SETUP REQUEST related IEs handle_ie(#'ProtocolIE-Field'{id = ?'id-E-RABToBeSetupListBearerSUReq', value = Content}, S) -> %% This IE contains a list of BearerSUReq, so patch inner IEs handle_ies(Content, ?'id-E-RABToBeSetupItemBearerSUReq', S); handle_ie(#'ProtocolIE-Field'{id = ?'id-E-RABToBeSetupItemBearerSUReq', value = C0}, S0) -> %% start and register an E-RAB FSM #'E-RABToBeSetupItemBearerSUReq'{'e-RAB-ID' = ERABId, 'transportLayerAddress' = TLA_In, 'gTP-TEID' = << TEID_In:32/big >>} = C0, {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(#'ProtocolIE-Field'{id = ?'id-E-RABSetupListBearerSURes', value = Content}, S) -> %% This IE contains a list of BearerSURes, so patch inner IEs handle_ies(Content, ?'id-E-RABSetupItemBearerSURes', S); handle_ie(#'ProtocolIE-Field'{id = ?'id-E-RABSetupItemBearerSURes', value = C0}, S) -> %% poke E-RAB FSM #'E-RABSetupItemBearerSURes'{'e-RAB-ID' = ERABId, 'transportLayerAddress' = TLA_In, 'gTP-TEID' = << TEID_In:32/big >>} = C0, 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", [ERABId]), {{error, erab_not_registered}, S} end; %% 9.1.3.5 E-RAB RELEASE COMMAND related IEs handle_ie(#'ProtocolIE-Field'{id = ?'id-E-RABToBeReleasedList', value = Content}, S) -> %% This IE contains a list of E-RABItem handle_ies(Content, ?'id-E-RABItem', S); handle_ie(#'ProtocolIE-Field'{id = ?'id-E-RABItem', value = C}, S) -> %% poke E-RAB FSM #'E-RABItem'{'e-RAB-ID' = ERABId} = C, case erab_fsm_find(ERABId, S) of {ok, Pid} -> ok = erab_fsm:erab_release_req(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.6 E-RAB RELEASE RESPONSE related IEs handle_ie(#'ProtocolIE-Field'{id = ?'id-E-RABReleaseListBearerRelComp', value = Content}, S) -> %% This IE contains a list of E-RABReleaseItemBearerRelComp handle_ies(Content, ?'id-E-RABReleaseItemBearerRelComp', S); handle_ie(#'ProtocolIE-Field'{id = ?'id-E-RABReleaseItemBearerRelComp', value = C}, S) -> %% poke E-RAB FSM #'E-RABReleaseItemBearerRelComp'{'e-RAB-ID' = ERABId} = C, 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(#'ProtocolIE-Field'{id = ?'id-E-RABReleasedList', value = Content}, S) -> %% This IE contains a list of E-RABItem handle_ies(Content, ?'id-E-RABItem', S); %% E-RAB MODIFICATION INDICATION related IEs handle_ie(#'ProtocolIE-Field'{id = ?'id-E-RABToBeModifiedListBearerModInd', value = Content}, S) -> %% This IE contains a list of BearerModInd, so patch inner IEs handle_ies(Content, ?'id-E-RABToBeModifiedItemBearerModInd', S); handle_ie(#'ProtocolIE-Field'{id = ?'id-E-RABToBeModifiedItemBearerModInd', value = C}, S) -> %% TODO: find and poke an E-RAB FSM associated with this E-RAB {{ok, C}, S}; handle_ie(#'ProtocolIE-Field'{id = ?'id-E-RABNotToBeModifiedListBearerModInd', value = Content}, S) -> %% This IE contains a list of BearerModInd, so patch inner IEs handle_ies(Content, ?'id-E-RABNotToBeModifiedItemBearerModInd', S); handle_ie(#'ProtocolIE-Field'{id = ?'id-E-RABNotToBeModifiedItemBearerModInd', value = 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(#'ProtocolIE-Field'{id = ?'id-E-RABToBeSetupListCtxtSUReq', value = Content}, S) -> %% This IE contains a list of CtxtSUReq, so patch inner IEs handle_ies(Content, ?'id-E-RABToBeSetupItemCtxtSUReq', S); handle_ie(#'ProtocolIE-Field'{id = ?'id-E-RABToBeSetupItemCtxtSUReq', value = C0}, S0) -> %% start and register an E-RAB FSM #'E-RABToBeSetupItemCtxtSUReq'{'e-RAB-ID' = ERABId, 'transportLayerAddress' = TLA_In, 'gTP-TEID' = << TEID_In:32/big >>} = C0, {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(#'ProtocolIE-Field'{id = ?'id-E-RABSetupListCtxtSURes', value = Content}, S) -> %% This IE contains a list of CtxtSURes, so patch inner IEs handle_ies(Content, ?'id-E-RABSetupItemCtxtSURes', S); handle_ie(#'ProtocolIE-Field'{id = ?'id-E-RABSetupItemCtxtSURes', value = C0}, S) -> %% poke E-RAB FSM #'E-RABSetupItemCtxtSURes'{'e-RAB-ID' = ERABId, 'transportLayerAddress' = TLA_In, 'gTP-TEID' = << TEID_In:32/big >>} = C0, 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(#'ProtocolIE-Field'{} = IE, S) -> ?LOG_ERROR("[BUG] Unhandled S1AP IE: ~p", [IE]), {{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(list(), integer(), proxy_state()) -> {handle_ies_result(), proxy_state()}. handle_ies(IEs, IEI, S) -> handle_ies([], IEs, IEI, S). handle_ies(Acc, [IE | IEs], IEI, S0) -> case IE of #'ProtocolIE-Field'{id = IEI} -> case handle_ie(IE, S0) of {{ok, C}, S1} -> NewIE = IE#'ProtocolIE-Field'{value = C}, handle_ies([NewIE | Acc], IEs, IEI, 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], IEs, IEI, S1); #'ProtocolIE-Field'{id = ?'id-eNB-UE-S1AP-ID', value = Id} -> S1 = S0#proxy_state{enb_ue_id = Id}, handle_ies([IE | Acc], IEs, IEI, S1); _ -> handle_ies([IE | Acc], IEs, IEI, S0) end; handle_ies(Acc, [], _IEI, S) -> IEs = lists:reverse(Acc), {{ok, IEs}, S}. 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: