fixeria submitted this change.
s1ap_utils: add API for S1AP PDU parsing
This API will be used in a follow-up patch, in which handling of
the S1 SETUP procedure will be fully migrated to `sctp_proxy` -
a prerequisite for proper MME pooling support.
Change-Id: I0aa3847d0f0ae65f13fa6bbfdbcd6ed188dcb04f
Related: SYS#7052
---
M src/enb_registry.erl
M src/s1ap_proxy.erl
M src/s1ap_utils.erl
A test/s1ap_utils_test.erl
4 files changed, 188 insertions(+), 52 deletions(-)
diff --git a/src/enb_registry.erl b/src/enb_registry.erl
index 0217836..90c3376 100644
--- a/src/enb_registry.erl
+++ b/src/enb_registry.erl
@@ -80,8 +80,8 @@
reg_time := integer(), %% registration time (monotonic)
uptime := non_neg_integer(), %% seconds since reg_time
genb_id_str => string(), %% Global-eNB-ID
- enb_id => s1ap_proxy:enb_id(), %% eNB-ID
- plmn_id => s1ap_proxy:plmn_id(), %% PLMN-ID
+ enb_id => s1ap_utils:enb_id(), %% eNB-ID
+ plmn_id => s1ap_utils:plmn_id(), %% PLMN-ID
enb_conn_info => sctp_server:conn_info(), %% eNB -> S1GW connection info
mme_conn_info => sctp_proxy:conn_info() %% S1GW -> MME connection info
}.
diff --git a/src/s1ap_proxy.erl b/src/s1ap_proxy.erl
index cbd9b10..43624d2 100644
--- a/src/s1ap_proxy.erl
+++ b/src/s1ap_proxy.erl
@@ -60,19 +60,16 @@
-type s1ap_ie_id() :: non_neg_integer().
-type s1ap_ie_val() :: tuple().
--type enb_id() :: 0..16#fffffff.
-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(), erab_id()}.
--type plmn_id() :: {MCC :: nonempty_string(),
- MNC :: nonempty_string()}.
-record(proxy_state, {owner :: pid(),
erabs :: dict:dict(K :: erab_uid(),
V :: pid()),
- enb_id :: undefined | non_neg_integer(),
- plmn_id :: undefined | plmn_id(),
+ enb_id :: undefined | s1ap_utils:enb_id(),
+ plmn_id :: undefined | s1ap_utils:plmn_id(),
genb_id_str :: undefined | string(),
mme_ue_id :: undefined | mme_ue_id(),
enb_ue_id :: undefined | enb_ue_id(),
@@ -83,15 +80,13 @@
-type proxy_state() :: #proxy_state{}.
-type proxy_action() :: forward | reply | drop.
--type enb_info() :: #{enb_id => enb_id(),
- plmn_id => plmn_id(),
+-type enb_info() :: #{enb_id => s1ap_utils:enb_id(),
+ plmn_id => s1ap_utils:plmn_id(),
genb_id_str => string()
}.
-export_type([proxy_action/0,
- enb_info/0,
- enb_id/0,
- plmn_id/0]).
+ enb_info/0]).
%% ------------------------------------------------------------------
@@ -203,41 +198,6 @@
ok.
-%% Parse PLMN-ID as per 3GPP TS 24.008, Figure 10.5.13
-%% | MCC digit 2 | MCC digit 1 | octet 1
-%% | MNC digit 3 | MCC digit 3 | octet 2
-%% | MNC digit 2 | MNC digit 1 | octet 3
--spec parse_plmn_id(<< _:24 >>) -> plmn_id().
-parse_plmn_id(<< MCC2:4, MCC1:4,
- MNC3:4, MCC3:4,
- MNC2:4, MNC1:4 >>) ->
- MCC = parse_mcc_mnc(MCC1, MCC2, MCC3),
- MNC = parse_mcc_mnc(MNC1, MNC2, MNC3),
- {MCC, MNC}.
-
-
--define(UNHEX(H), H + 48).
-
-parse_mcc_mnc(D1, D2, 16#f) ->
- [?UNHEX(D1), ?UNHEX(D2)];
-
-parse_mcc_mnc(D1, D2, D3) ->
- [?UNHEX(D1), ?UNHEX(D2), ?UNHEX(D3)].
-
-
--spec parse_enb_id(tuple()) -> enb_id().
-parse_enb_id({'macroENB-ID', << ID:20 >>}) -> ID;
-parse_enb_id({'homeENB-ID', << ID:28 >>}) -> ID;
-parse_enb_id({'short-macroENB-ID', << ID:18 >>}) -> ID;
-parse_enb_id({'long-macroENB-ID', << ID:21 >>}) -> ID.
-
-
--spec genb_id_str(proxy_state()) -> string().
-genb_id_str(#proxy_state{plmn_id = {MCC, MNC},
- enb_id = ENBId}) ->
- MCC ++ "-" ++ MNC ++ "-" ++ integer_to_list(ENBId).
-
-
-spec enb_info(proxy_state()) -> enb_info().
enb_info(S) ->
Info = #{enb_id => S#proxy_state.enb_id,
@@ -686,13 +646,14 @@
#'Global-ENB-ID'{'pLMNidentity' = PLMNId,
'eNB-ID' = ENBId} = C, S0) ->
%% store PLMNId/ENBId
- S1 = S0#proxy_state{plmn_id = parse_plmn_id(PLMNId),
- enb_id = parse_enb_id(ENBId)},
+ S1 = S0#proxy_state{plmn_id = s1ap_utils:parse_plmn_id(PLMNId),
+ enb_id = s1ap_utils:parse_enb_id(ENBId)},
?LOG_INFO("Global-ENB-ID: PLMN-ID=~p, eNB-ID=~p",
[S1#proxy_state.plmn_id,
S1#proxy_state.enb_id]),
%% use that as a context for logging
- GlobalENBId = genb_id_str(S1),
+ GlobalENBId = s1ap_utils:genb_id_str(#{plmn_id => S1#proxy_state.plmn_id,
+ enb_id => S1#proxy_state.enb_id}),
osmo_s1gw:set_log_prefix("eNB " ++ GlobalENBId),
%% register per-eNB metrics
ctr_reg_all(GlobalENBId),
diff --git a/src/s1ap_utils.erl b/src/s1ap_utils.erl
index ab7180c..6f7e6b6 100644
--- a/src/s1ap_utils.erl
+++ b/src/s1ap_utils.erl
@@ -35,11 +35,19 @@
-module(s1ap_utils).
-export([encode_pdu/1,
- decode_pdu/1]).
+ decode_pdu/1,
+ parse_pdu/1,
+ parse_plmn_id/1,
+ parse_enb_id/1,
+ genb_id_str/1]).
-include_lib("kernel/include/logger.hrl").
-include("S1AP-PDU-Descriptions.hrl").
+-include("S1AP-PDU-Contents.hrl").
+-include("S1AP-Containers.hrl").
+-include("S1AP-Constants.hrl").
+-include("S1AP-IEs.hrl").
%% S1AP PDU (decoded)
@@ -47,7 +55,28 @@
{successfulOutcome, #'SuccessfulOutcome'{}} |
{unsuccessfulOutcome, #'UnsuccessfulOutcome'{}}.
--export_type([s1ap_pdu/0]).
+%% 9.2.1.1 Message Type
+-type s1ap_msg_type() :: {Proc :: non_neg_integer(),
+ Type :: initiatingMessage | successfulOutcome | unsuccessfulOutcome}.
+
+%% S1AP PDU (decoded, unrolled)
+-type s1ap_pdu_info() :: {MsgType :: s1ap_msg_type(),
+ Content :: proplists:proplist()}.
+
+-export_type([s1ap_pdu/0,
+ s1ap_msg_type/0,
+ s1ap_pdu_info/0]).
+
+
+-type enb_id() :: 0..16#fffffff.
+-type plmn_id() :: {MCC :: nonempty_string(),
+ MNC :: nonempty_string()}.
+-type genb_id() :: #{enb_id => enb_id(),
+ plmn_id => plmn_id()}.
+
+-export_type([enb_id/0,
+ plmn_id/0,
+ genb_id/0]).
%% ------------------------------------------------------------------
@@ -68,4 +97,114 @@
'S1AP-PDU-Descriptions':decode('S1AP-PDU', Data).
+%% Parse an S1AP PDU
+-spec parse_pdu(binary()) -> s1ap_pdu_info() | {error, term()}.
+parse_pdu(Data) ->
+ try decode_pdu(Data) of
+ {ok, PDU} ->
+ {MsgType, IEs} = unroll_pdu(PDU),
+ {MsgType, parse_ies(IEs)};
+ {error, Error} ->
+ ?LOG_ERROR("S1AP PDU decoding failed: ~p", [Error]),
+ {error, {decode_pdu, Error}}
+ catch
+ Exception:Reason:StackTrace ->
+ ?LOG_ERROR("An exception occurred: ~p, ~p, ~p", [Exception, Reason, StackTrace]),
+ {error, decode_pdu}
+ end.
+
+
+-spec genb_id_str(genb_id()) -> string().
+genb_id_str(#{plmn_id := {MCC, MNC},
+ enb_id := ENBId}) ->
+ MCC ++ "-" ++ MNC ++ "-" ++ integer_to_list(ENBId).
+
+
+%% ------------------------------------------------------------------
+%% private API
+%% ------------------------------------------------------------------
+
+%% Unroll a decoded S1AP PDU (procedure code, type, IEs)
+-spec unroll_pdu(s1ap_pdu()) -> s1ap_pdu_info().
+unroll_pdu({Type = initiatingMessage,
+ #'InitiatingMessage'{procedureCode = PC,
+ value = {_, IEs}}}) -> {{PC, Type}, IEs};
+unroll_pdu({Type = successfulOutcome,
+ #'SuccessfulOutcome'{procedureCode = PC,
+ value = {_, IEs}}}) -> {{PC, Type}, IEs};
+unroll_pdu({Type = unsuccessfulOutcome,
+ #'UnsuccessfulOutcome'{procedureCode = PC,
+ value = {_, IEs}}}) -> {{PC, Type}, IEs}.
+
+
+%% Parse PLMN-ID as per 3GPP TS 24.008, Figure 10.5.13
+%% | MCC digit 2 | MCC digit 1 | octet 1
+%% | MNC digit 3 | MCC digit 3 | octet 2
+%% | MNC digit 2 | MNC digit 1 | octet 3
+-spec parse_plmn_id(<< _:24 >>) -> plmn_id().
+parse_plmn_id(<< MCC2:4, MCC1:4,
+ MNC3:4, MCC3:4,
+ MNC2:4, MNC1:4 >>) ->
+ MCC = parse_mcc_mnc(MCC1, MCC2, MCC3),
+ MNC = parse_mcc_mnc(MNC1, MNC2, MNC3),
+ {MCC, MNC}.
+
+
+-define(UNHEX(H), H + 48).
+
+parse_mcc_mnc(D1, D2, 16#f) ->
+ [?UNHEX(D1), ?UNHEX(D2)];
+
+parse_mcc_mnc(D1, D2, D3) ->
+ [?UNHEX(D1), ?UNHEX(D2), ?UNHEX(D3)].
+
+
+-spec parse_enb_id(tuple()) -> enb_id().
+parse_enb_id({'macroENB-ID', << ID:20 >>}) -> ID;
+parse_enb_id({'homeENB-ID', << ID:28 >>}) -> ID;
+parse_enb_id({'short-macroENB-ID', << ID:18 >>}) -> ID;
+parse_enb_id({'long-macroENB-ID', << ID:21 >>}) -> ID.
+
+
+-type s1ap_ie_id() :: non_neg_integer().
+-type s1ap_ie_val() :: tuple().
+
+-spec parse_ie(tuple()) -> proplists:property().
+-spec parse_ie(s1ap_ie_id(), s1ap_ie_val()) -> term().
+
+parse_ie(#'ProtocolIE-Field'{id = IEI, value = C}) ->
+ {IEI, parse_ie(IEI, C)};
+
+parse_ie(#'ProtocolExtensionField'{id = IEI, extensionValue = C}) ->
+ {IEI, parse_ie(IEI, C)};
+
+parse_ie(IE) ->
+ ?LOG_ERROR("Unknown IE format: ~p", [IE]),
+ {unknown, IE}.
+
+
+%% 9.2.1.37 Global eNB ID
+parse_ie(?'id-Global-ENB-ID',
+ #'Global-ENB-ID'{'pLMNidentity' = PLMNId,
+ 'eNB-ID' = EnbId}) ->
+ #{plmn_id => parse_plmn_id(PLMNId),
+ enb_id => parse_enb_id(EnbId)};
+
+%% 9.1.8.4 Supported TAs
+%% 9.2.3.7 Broadcast TAC
+parse_ie(?'id-SupportedTAs', TAs) ->
+ %% TODO: Broadcast PLMNs
+ [TAC || #'SupportedTAs-Item'{tAC = << TAC:16 >>} <- TAs];
+
+%% For all other IEIs return the contents as-is.
+parse_ie(_IEI, C) -> C.
+
+
+%% Iterate over the given list of S1AP IEs, calling parse_ie/2 for each.
+%% The result is a proplist containing parsed IEs.
+-spec parse_ies(list()) -> proplists:proplist().
+parse_ies(IEs) ->
+ lists:map(fun parse_ie/1, IEs).
+
+
%% vim:set ts=4 sw=4 et:
diff --git a/test/s1ap_utils_test.erl b/test/s1ap_utils_test.erl
new file mode 100644
index 0000000..1f3279b
--- /dev/null
+++ b/test/s1ap_utils_test.erl
@@ -0,0 +1,36 @@
+-module(s1ap_utils_test).
+
+-include_lib("eunit/include/eunit.hrl").
+-include("S1AP-Constants.hrl").
+-include("S1AP-IEs.hrl").
+
+
+-define(_assertEqualIE(IEI, Values),
+ ?_assertEqual(Values, proplists:get_value(IEI, IEs))).
+
+-define(_assertMatchIE(IEI, Values),
+ ?_assertMatch(Values, proplists:get_value(IEI, IEs))).
+
+
+%% ------------------------------------------------------------------
+%% actual testcases
+%% ------------------------------------------------------------------
+
+s1ap_setup_req_test_() ->
+ {MsgType, IEs} = s1ap_utils:parse_pdu(s1ap_samples:s1_setup_req_pdu()),
+ [?_assertEqual(MsgType, {?'id-S1Setup', initiatingMessage}),
+ ?_assertEqualIE(?'id-Global-ENB-ID', #{enb_id => 0,
+ plmn_id => {"001", "01"}}),
+ ?_assertEqualIE(?'id-eNBname', undefined), %% optional, not present
+ ?_assertMatchIE(?'id-SupportedTAs', [12345])]. %% we only parse the TACs
+
+
+s1ap_setup_rsp_test_() ->
+ {MsgType, IEs} = s1ap_utils:parse_pdu(s1ap_samples:s1_setup_rsp_pdu()),
+ [?_assertEqual(MsgType, {?'id-S1Setup', successfulOutcome}),
+ ?_assertEqualIE(?'id-MMEname', "open5gs-mme0"),
+ ?_assertMatchIE(?'id-ServedGUMMEIs', [#'ServedGUMMEIsItem'{}]),
+ ?_assertEqualIE(?'id-RelativeMMECapacity', 16#ff)].
+
+
+%% vim:set ts=4 sw=4 et:
To view, visit change 41621. To unsubscribe, or for help writing mail filters, visit settings.