fixeria has submitted this change. ( 
https://gerrit.osmocom.org/c/erlang/osmo-s1gw/+/41621?usp=email )

Change subject: s1ap_utils: add API for S1AP PDU parsing
......................................................................

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(-)

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




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 https://gerrit.osmocom.org/c/erlang/osmo-s1gw/+/41621?usp=email
To unsubscribe, or for help writing mail filters, visit 
https://gerrit.osmocom.org/settings?usp=email

Gerrit-MessageType: merged
Gerrit-Project: erlang/osmo-s1gw
Gerrit-Branch: master
Gerrit-Change-Id: I0aa3847d0f0ae65f13fa6bbfdbcd6ed188dcb04f
Gerrit-Change-Number: 41621
Gerrit-PatchSet: 2
Gerrit-Owner: fixeria <[email protected]>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: fixeria <[email protected]>
Gerrit-Reviewer: osmith <[email protected]>
Gerrit-Reviewer: pespin <[email protected]>

Reply via email to