Add support of the GSUP E Interface messages
Encoding and decoding of the following GSUP messages were added to support inter-MSC handover functionality:
* E Prepare Handover Request/Result/Error
* E Prepare Subsequent Handover Request/Result/Error
* E Send End Signal Request/Result/Error
* E Forward Access Signalling Request
* E Process Access Signalling Request
* E Close, E Abort, E Routing Error.
Tests for GSUP E Interface messages were added from libosmocore library.
diff --git a/include/gsup_protocol.hrl b/include/gsup_protocol.hrl
index 2b5676e..d6f0235 100644
--- a/include/gsup_protocol.hrl
+++ b/include/gsup_protocol.hrl
@@ -40,7 +40,21 @@
| ready_for_sm_res
| check_imei_req
| check_imei_err
- | check_imei_res.
+ | check_imei_res
+ | e_prepare_handover_req
+ | e_prepare_handover_err
+ | e_prepare_handover_res
+ | e_prepare_subseq_handover_req
+ | e_prepare_subseq_handover_err
+ | e_prepare_subseq_handover_res
+ | e_send_end_signal_req
+ | e_send_end_signal_err
+ | e_send_end_signal_res
+ | e_process_access_signalling_req
+ | e_forward_access_signalling_req
+ | e_close
+ | e_abort
+ | e_routing_err.
-type 'GSUPMessage'() :: #{
message_type := 'GSUPMessageType'(),
@@ -84,7 +98,13 @@
sm_alert_reason => integer(),
imei => binary(),
imei_check_result => integer(),
- message_class => integer()
+ message_class => integer(),
+ source_name => binary(),
+ destination_name => binary(),
+ an_apdu => binary(),
+ rr_cause => integer(),
+ session_management_cause => integer(),
+ bssap_cause => integer()
}.
-define(SESSION_STATE_BEGIN, 1).
@@ -127,6 +147,12 @@
-define(SM_ALERT_REASON, 16#46).
-define(IMEI, 16#50).
-define(IMEI_CHECK_RESULT, 16#51).
+-define(SOURCE_NAME, 16#60).
+-define(DESTINATION_NAME, 16#61).
+-define(AN_APDU, 16#62).
+-define(RR_CAUSE, 16#63).
+-define(BSSAP_CAUSE, 16#64).
+-define(SESSION_MANAGEMENT_CAUSE, 16#65).
-define(MANDATORY_DEFAULT, [imsi, message_type]).
@@ -166,7 +192,21 @@
16#2e => #{message_type => ready_for_sm_res, mandatory => []},
16#30 => #{message_type => check_imei_req, mandatory => [imei]},
16#31 => #{message_type => check_imei_err, mandatory => [cause]},
- 16#32 => #{message_type => check_imei_res, mandatory => [imei_check_result]}
+ 16#32 => #{message_type => check_imei_res, mandatory => [imei_check_result]},
+ 16#34 => #{message_type => e_prepare_handover_req, mandatory => [message_class, source_name, destination_name, an_apdu, session_id, session_state]},
+ 16#35 => #{message_type => e_prepare_handover_err, mandatory => [message_class, source_name, destination_name, an_apdu, session_id, session_state, bssap_cause]},
+ 16#36 => #{message_type => e_prepare_handover_res, mandatory => [message_class, source_name, destination_name, an_apdu, session_id, session_state]},
+ 16#38 => #{message_type => e_prepare_subseq_handover_req, mandatory => [message_class, source_name, destination_name, an_apdu, session_id, session_state]},
+ 16#39 => #{message_type => e_prepare_subseq_handover_err, mandatory => [message_class, source_name, destination_name, an_apdu, session_id, session_state, bssap_cause]},
+ 16#3a => #{message_type => e_prepare_subseq_handover_res, mandatory => [message_class, source_name, destination_name, an_apdu, session_id, session_state]},
+ 16#3c => #{message_type => e_send_end_signal_req, mandatory => [message_class, source_name, destination_name, an_apdu, session_id, session_state]},
+ 16#3d => #{message_type => e_send_end_signal_err, mandatory => [message_class, source_name, destination_name, an_apdu, session_id, session_state, bssap_cause]},
+ 16#3e => #{message_type => e_send_end_signal_res, mandatory => [message_class, source_name, destination_name, an_apdu, session_id, session_state]},
+ 16#40 => #{message_type => e_process_access_signalling_req, mandatory => [message_class, source_name, destination_name, an_apdu, session_id, session_state]},
+ 16#44 => #{message_type => e_forward_access_signalling_req, mandatory => [message_class, source_name, destination_name, an_apdu, session_id, session_state]},
+ 16#47 => #{message_type => e_close, mandatory => [message_class, source_name, destination_name, session_id, session_state]},
+ 16#4b => #{message_type => e_abort, mandatory => [message_class, session_id, session_state, bssap_cause]},
+ 16#4e => #{message_type => e_routing_err, mandatory => [message_class, source_name, destination_name, session_id, session_state]}
}).
-define(AUTH_TUPLE_MANDATORY, [rand, sres, kc]).
diff --git a/src/gsup_protocol.erl b/src/gsup_protocol.erl
index a8c1754..fe5f38c 100644
--- a/src/gsup_protocol.erl
+++ b/src/gsup_protocol.erl
@@ -151,6 +151,27 @@
?CHECK_LEN(imei_check_result, Len, 1, 1),
decode_ie(Tail, Map#{imei_check_result => IMEIResult});
+decode_ie(<<?SOURCE_NAME, Len, SourceName:Len/binary, Tail/binary>>, Map) ->
+ decode_ie(Tail, Map#{source_name => SourceName});
+
+decode_ie(<<?DESTINATION_NAME, Len, DestName:Len/binary, Tail/binary>>, Map) ->
+ decode_ie(Tail, Map#{destination_name => DestName});
+
+decode_ie(<<?AN_APDU, Len, AN_APDU:Len/binary, Tail/binary>>, Map) ->
+ decode_ie(Tail, Map#{an_apdu => AN_APDU});
+
+decode_ie(<<?RR_CAUSE, Len, RRCause:Len/unit:8, Tail/binary>>, Map) ->
+ ?CHECK_LEN(rr_cause, Len, 1, 1),
+ decode_ie(Tail, Map#{rr_cause => RRCause});
+
+decode_ie(<<?BSSAP_CAUSE, Len, BSSAPCause:Len/unit:8, Tail/binary>>, Map) ->
+ ?CHECK_LEN(bssap_cause, Len, 1, 1),
+ decode_ie(Tail, Map#{bssap_cause => BSSAPCause});
+
+decode_ie(<<?SESSION_MANAGEMENT_CAUSE, Len, SMCause:Len/unit:8, Tail/binary>>, Map) ->
+ ?CHECK_LEN(session_management_cause, Len, 1, 1),
+ decode_ie(Tail, Map#{session_management_cause => SMCause});
+
decode_ie(<<_, Len, _:Len/binary, Tail/binary>>, Map) -> %% skip unknown IE
decode_ie(Tail, Map);
@@ -415,6 +436,33 @@
?CHECK_SIZE(imei_check_result, Len, Value),
encode_ie(maps:without([imei_check_result], GSUPMessage), <<Head/binary, ?IMEI_CHECK_RESULT, Len, Value:Len/unit:8>>);
+encode_ie(#{source_name := Value} = GSUPMessage, Head) ->
+ Len = size(Value),
+ encode_ie(maps:without([source_name], GSUPMessage), <<Head/binary, ?SOURCE_NAME, Len, Value/binary>>);
+
+encode_ie(#{destination_name := Value} = GSUPMessage, Head) ->
+ Len = size(Value),
+ encode_ie(maps:without([destination_name], GSUPMessage), <<Head/binary, ?DESTINATION_NAME, Len, Value/binary>>);
+
+encode_ie(#{an_apdu := Value} = GSUPMessage, Head) ->
+ Len = size(Value),
+ encode_ie(maps:without([an_apdu], GSUPMessage), <<Head/binary, ?AN_APDU, Len, Value/binary>>);
+
+encode_ie(#{rr_cause := Value} = GSUPMessage, Head) ->
+ Len = 1,
+ ?CHECK_SIZE(rr_cause, Len, Value),
+ encode_ie(maps:without([rr_cause], GSUPMessage), <<Head/binary, ?RR_CAUSE, Len, Value:Len/unit:8>>);
+
+encode_ie(#{bssap_cause := Value} = GSUPMessage, Head) ->
+ Len = 1,
+ ?CHECK_SIZE(bssap_cause, Len, Value),
+ encode_ie(maps:without([bssap_cause], GSUPMessage), <<Head/binary, ?BSSAP_CAUSE, Len, Value:Len/unit:8>>);
+
+encode_ie(#{session_management_cause := Value} = GSUPMessage, Head) ->
+ Len = 1,
+ ?CHECK_SIZE(session_management_cause, Len, Value),
+ encode_ie(maps:without([session_management_cause], GSUPMessage), <<Head/binary, ?SESSION_MANAGEMENT_CAUSE, Len, Value:Len/unit:8>>);
+
encode_ie(_, Head) -> Head.
encode_bcd(BCDNumber) -> encode_bcd(BCDNumber, <<>>).
diff --git a/src/osmo_gsup.app.src b/src/osmo_gsup.app.src
index cf9f18b..e93b1e3 100644
--- a/src/osmo_gsup.app.src
+++ b/src/osmo_gsup.app.src
@@ -1,6 +1,6 @@
{application, osmo_gsup, [
{description, "OSMOCOM GSUP library"},
- {vsn, "0.1.1"},
+ {vsn, "0.2.0"},
{registered, []},
{applications, [
kernel,
diff --git a/test/gsup_encode_decode_test.erl b/test/gsup_encode_decode_test.erl
index f7daa36..9fcba6c 100644
--- a/test/gsup_encode_decode_test.erl
+++ b/test/gsup_encode_decode_test.erl
@@ -11,6 +11,11 @@
-define(TEST_IMSI_IE, 16#01, 16#08, 16#21, 16#43, 16#65, 16#87, 16#09, 16#21, 16#43, 16#f5).
-define(TEST_MSISDN_IE, 16#08, 16#07, 16#91, 16#94, 16#61, 16#46, 16#32, 16#24, 16#43).
-define(TEST_CLASS_SUBSCR_IE, 16#0a, 16#01, 16#01).
+-define(TEST_CLASS_INTER_MSC_IE, 16#0a, 16#01, 16#04).
+-define(TEST_AN_APDU_IE, 16#62, 16#05, 16#01, 16#42, 16#42, 16#42, 16#42).
+-define(TEST_SOURCE_NAME_IE, 16#60, 16#05, "MSC-A").
+-define(TEST_DESTINATION_NAME_IE, 16#61, 16#05, "MSC-B").
+
missing_params_test() ->
?assertError({mandatory_ie_missing,location_cancellation_err,[cause]}, gsup_protocol:decode(<<16#1d, ?TEST_IMSI_IE>>)),
@@ -394,3 +399,284 @@
?assertEqual(Map, gsup_protocol:decode(Bin)),
?assertEqual(Bin, gsup_protocol:encode(Map)).
+e_prepare_handover_req_test() ->
+ Bin = <<16#34, ?TEST_IMSI_IE,
+ %% Session ID and state (begin)
+ 16#30, 16#04, 16#de, 16#ad, 16#be, 16#ef,
+ 16#31, 16#01, 16#01,
+
+ ?TEST_CLASS_INTER_MSC_IE,
+ ?TEST_SOURCE_NAME_IE,
+ ?TEST_DESTINATION_NAME_IE,
+ ?TEST_AN_APDU_IE %% (Handover Request)
+ >>,
+ Map = #{an_apdu => <<1,66,66,66,66>>,
+ destination_name => <<"MSC-B">>,
+ imsi => <<"123456789012345">>,message_class => 4,
+ message_type => e_prepare_handover_req,
+ session_id => 3735928559,session_state => 1,
+ source_name => <<"MSC-A">>},
+ ?assertEqual(Map, gsup_protocol:decode(Bin)),
+ ?assertEqual(Bin, gsup_protocol:encode(Map)).
+
+e_prepare_handover_err_test() ->
+ Bin = <<16#35, ?TEST_IMSI_IE,
+ %% Session ID and state (continue)
+ 16#30, 16#04, 16#de, 16#ad, 16#be, 16#ef,
+ 16#31, 16#01, 16#02,
+
+ ?TEST_CLASS_INTER_MSC_IE,
+ ?TEST_SOURCE_NAME_IE,
+ ?TEST_DESTINATION_NAME_IE,
+ ?TEST_AN_APDU_IE, %% (Handover Request) ??? unknown IE
+ %% cause bssap
+ 16#64, 16#01, 16#51
+ >>,
+ Map = #{an_apdu => <<1,66,66,66,66>>,
+ bssap_cause => 81,destination_name => <<"MSC-B">>,
+ imsi => <<"123456789012345">>,message_class => 4,
+ message_type => e_prepare_handover_err,
+ session_id => 3735928559,session_state => 2,
+ source_name => <<"MSC-A">>},
+ ?assertEqual(Map, gsup_protocol:decode(Bin)),
+ ?assertEqual(Bin, gsup_protocol:encode(Map)).
+
+e_prepare_handover_res_test() ->
+ Bin = <<16#36, ?TEST_IMSI_IE,
+ %% Session ID and state (continue)
+ 16#30, 16#04, 16#de, 16#ad, 16#be, 16#ef,
+ 16#31, 16#01, 16#02,
+
+ ?TEST_CLASS_INTER_MSC_IE,
+ ?TEST_SOURCE_NAME_IE,
+ ?TEST_DESTINATION_NAME_IE,
+ ?TEST_AN_APDU_IE %% (Handover Request)
+ >>,
+ Map = #{an_apdu => <<1,66,66,66,66>>,
+ destination_name => <<"MSC-B">>,
+ imsi => <<"123456789012345">>,message_class => 4,
+ message_type => e_prepare_handover_res,
+ session_id => 3735928559,session_state => 2,
+ source_name => <<"MSC-A">>},
+ ?assertEqual(Map, gsup_protocol:decode(Bin)),
+ ?assertEqual(Bin, gsup_protocol:encode(Map)).
+
+e_prepare_subseq_handover_req_test() ->
+ Bin = <<16#38, ?TEST_IMSI_IE,
+ %% Session ID and state (begin)
+ 16#30, 16#04, 16#de, 16#ad, 16#be, 16#ef,
+ 16#31, 16#01, 16#01,
+
+ ?TEST_CLASS_INTER_MSC_IE,
+ ?TEST_SOURCE_NAME_IE,
+ ?TEST_DESTINATION_NAME_IE,
+ ?TEST_AN_APDU_IE %% (Handover Request)
+ >>,
+ Map = #{an_apdu => <<1,66,66,66,66>>,
+ destination_name => <<"MSC-B">>,
+ imsi => <<"123456789012345">>,message_class => 4,
+ message_type => e_prepare_subseq_handover_req,
+ session_id => 3735928559,session_state => 1,
+ source_name => <<"MSC-A">>},
+ ?assertEqual(Map, gsup_protocol:decode(Bin)),
+ ?assertEqual(Bin, gsup_protocol:encode(Map)).
+
+e_prepare_subseq_handover_err_test() ->
+ Bin = <<16#39, ?TEST_IMSI_IE,
+ %% Session ID and state (continue)
+ 16#30, 16#04, 16#de, 16#ad, 16#be, 16#ef,
+ 16#31, 16#01, 16#02,
+
+ ?TEST_CLASS_INTER_MSC_IE,
+ ?TEST_SOURCE_NAME_IE,
+ ?TEST_DESTINATION_NAME_IE,
+ ?TEST_AN_APDU_IE, %% (Handover Request) ??? unknown IE
+ %% cause bssap
+ 16#64, 16#01, 16#51
+ >>,
+ Map = #{an_apdu => <<1,66,66,66,66>>,
+ bssap_cause => 81,destination_name => <<"MSC-B">>,
+ imsi => <<"123456789012345">>,message_class => 4,
+ message_type => e_prepare_subseq_handover_err,
+ session_id => 3735928559,session_state => 2,
+ source_name => <<"MSC-A">>},
+ ?assertEqual(Map, gsup_protocol:decode(Bin)),
+ ?assertEqual(Bin, gsup_protocol:encode(Map)).
+
+e_prepare_subseq_handover_res_test() ->
+ Bin = <<16#3a, ?TEST_IMSI_IE,
+ %% Session ID and state (continue)
+ 16#30, 16#04, 16#de, 16#ad, 16#be, 16#ef,
+ 16#31, 16#01, 16#02,
+
+ ?TEST_CLASS_INTER_MSC_IE,
+ ?TEST_SOURCE_NAME_IE,
+ ?TEST_DESTINATION_NAME_IE,
+ ?TEST_AN_APDU_IE %% (Handover Request)
+ >>,
+ Map = #{an_apdu => <<1,66,66,66,66>>,
+ destination_name => <<"MSC-B">>,
+ imsi => <<"123456789012345">>,message_class => 4,
+ message_type => e_prepare_subseq_handover_res,
+ session_id => 3735928559,session_state => 2,
+ source_name => <<"MSC-A">>},
+ ?assertEqual(Map, gsup_protocol:decode(Bin)),
+ ?assertEqual(Bin, gsup_protocol:encode(Map)).
+
+e_send_end_signal_req_test() ->
+ Bin = <<16#3c, ?TEST_IMSI_IE,
+ %% Session ID and state (end)
+ 16#30, 16#04, 16#de, 16#ad, 16#be, 16#ef,
+ 16#31, 16#01, 16#03,
+
+ ?TEST_CLASS_INTER_MSC_IE,
+ ?TEST_SOURCE_NAME_IE,
+ ?TEST_DESTINATION_NAME_IE,
+ ?TEST_AN_APDU_IE %% (Handover Request)
+ >>,
+ Map = #{an_apdu => <<1,66,66,66,66>>,
+ destination_name => <<"MSC-B">>,
+ imsi => <<"123456789012345">>,message_class => 4,
+ message_type => e_send_end_signal_req,
+ session_id => 3735928559,session_state => 3,
+ source_name => <<"MSC-A">>},
+ ?assertEqual(Map, gsup_protocol:decode(Bin)),
+ ?assertEqual(Bin, gsup_protocol:encode(Map)).
+
+e_send_end_signal_err_test() ->
+ Bin = <<16#3d, ?TEST_IMSI_IE,
+ %% Session ID and state (end)
+ 16#30, 16#04, 16#de, 16#ad, 16#be, 16#ef,
+ 16#31, 16#01, 16#03,
+
+ ?TEST_CLASS_INTER_MSC_IE,
+ ?TEST_SOURCE_NAME_IE,
+ ?TEST_DESTINATION_NAME_IE,
+ ?TEST_AN_APDU_IE, %% (Handover Request) ??? unknown IE
+ %% cause bssap
+ 16#64, 16#01, 16#51
+ >>,
+ Map = #{an_apdu => <<1,66,66,66,66>>,
+ bssap_cause => 81,destination_name => <<"MSC-B">>,
+ imsi => <<"123456789012345">>,message_class => 4,
+ message_type => e_send_end_signal_err,
+ session_id => 3735928559,session_state => 3,
+ source_name => <<"MSC-A">>},
+ ?assertEqual(Map, gsup_protocol:decode(Bin)),
+ ?assertEqual(Bin, gsup_protocol:encode(Map)).
+
+e_send_end_signal_res_test() ->
+ Bin = <<16#3e, ?TEST_IMSI_IE,
+ %% Session ID and state (end)
+ 16#30, 16#04, 16#de, 16#ad, 16#be, 16#ef,
+ 16#31, 16#01, 16#03,
+
+ ?TEST_CLASS_INTER_MSC_IE,
+ ?TEST_SOURCE_NAME_IE,
+ ?TEST_DESTINATION_NAME_IE,
+ ?TEST_AN_APDU_IE %% (Handover Request)
+ >>,
+ Map = #{an_apdu => <<1,66,66,66,66>>,
+ destination_name => <<"MSC-B">>,
+ imsi => <<"123456789012345">>,message_class => 4,
+ message_type => e_send_end_signal_res,
+ session_id => 3735928559,session_state => 3,
+ source_name => <<"MSC-A">>},
+ ?assertEqual(Map, gsup_protocol:decode(Bin)),
+ ?assertEqual(Bin, gsup_protocol:encode(Map)).
+
+e_process_access_signalling_req_test() ->
+ Bin = <<16#40, ?TEST_IMSI_IE,
+ %% Session ID and state (continue)
+ 16#30, 16#04, 16#de, 16#ad, 16#be, 16#ef,
+ 16#31, 16#01, 16#02,
+
+ ?TEST_CLASS_INTER_MSC_IE,
+ ?TEST_SOURCE_NAME_IE,
+ ?TEST_DESTINATION_NAME_IE,
+ ?TEST_AN_APDU_IE %% (Handover Request)
+ >>,
+ Map = #{an_apdu => <<1,66,66,66,66>>,
+ destination_name => <<"MSC-B">>,
+ imsi => <<"123456789012345">>,message_class => 4,
+ message_type => e_process_access_signalling_req,
+ session_id => 3735928559,session_state => 2,
+ source_name => <<"MSC-A">>},
+ ?assertEqual(Map, gsup_protocol:decode(Bin)),
+ ?assertEqual(Bin, gsup_protocol:encode(Map)).
+
+e_forward_access_signalling_req_test() ->
+ Bin = <<16#44, ?TEST_IMSI_IE,
+ %% Session ID and state (continue)
+ 16#30, 16#04, 16#de, 16#ad, 16#be, 16#ef,
+ 16#31, 16#01, 16#02,
+
+ ?TEST_CLASS_INTER_MSC_IE,
+ ?TEST_SOURCE_NAME_IE,
+ ?TEST_DESTINATION_NAME_IE,
+ ?TEST_AN_APDU_IE %% (Handover Request)
+ >>,
+ Map = #{an_apdu => <<1,66,66,66,66>>,
+ destination_name => <<"MSC-B">>,
+ imsi => <<"123456789012345">>,message_class => 4,
+ message_type => e_forward_access_signalling_req,
+ session_id => 3735928559,session_state => 2,
+ source_name => <<"MSC-A">>},
+ ?assertEqual(Map, gsup_protocol:decode(Bin)),
+ ?assertEqual(Bin, gsup_protocol:encode(Map)).
+
+e_close_test() ->
+ Bin = <<16#47, ?TEST_IMSI_IE,
+ %% Session ID and state (end)
+ 16#30, 16#04, 16#de, 16#ad, 16#be, 16#ef,
+ 16#31, 16#01, 16#03,
+
+ ?TEST_CLASS_INTER_MSC_IE,
+ ?TEST_SOURCE_NAME_IE,
+ ?TEST_DESTINATION_NAME_IE
+ >>,
+ Map = #{
+ destination_name => <<"MSC-B">>,
+ imsi => <<"123456789012345">>,message_class => 4,
+ message_type => e_close,
+ session_id => 3735928559,session_state => 3,
+ source_name => <<"MSC-A">>},
+ ?assertEqual(Map, gsup_protocol:decode(Bin)),
+ ?assertEqual(Bin, gsup_protocol:encode(Map)).
+
+e_abort_test() ->
+ Bin = <<16#4b, ?TEST_IMSI_IE,
+ %% Session ID and state (end)
+ 16#30, 16#04, 16#de, 16#ad, 16#be, 16#ef,
+ 16#31, 16#01, 16#03,
+
+ ?TEST_CLASS_INTER_MSC_IE,
+ %% cause bssap
+ 16#64, 16#01, 16#51
+ >>,
+ Map = #{
+ imsi => <<"123456789012345">>,message_class => 4,
+ bssap_cause => 81,message_type => e_abort,
+ session_id => 3735928559,session_state => 3},
+ ?assertEqual(Map, gsup_protocol:decode(Bin)),
+ ?assertEqual(Bin, gsup_protocol:encode(Map)).
+
+e_routing_error_test() ->
+ Bin = <<16#4e, ?TEST_IMSI_IE,
+ %% Session ID and state (end)
+ 16#30, 16#04, 16#de, 16#ad, 16#be, 16#ef,
+ 16#31, 16#01, 16#03,
+
+ ?TEST_CLASS_INTER_MSC_IE,
+ ?TEST_SOURCE_NAME_IE,
+ ?TEST_DESTINATION_NAME_IE
+ >>,
+ Map = #{
+ destination_name => <<"MSC-B">>,
+ imsi => <<"123456789012345">>,message_class => 4,
+ message_type => e_routing_err,
+ session_id => 3735928559,session_state => 3,
+ source_name => <<"MSC-A">>},
+ ?assertEqual(Map, gsup_protocol:decode(Bin)),
+ ?assertEqual(Bin, gsup_protocol:encode(Map)).
+