aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel <0xc0decafe@users.noreply.github.com>2015-12-15 17:11:26 +0100
committerDaniel <0xc0decafe@users.noreply.github.com>2015-12-15 17:11:26 +0100
commit07769ca8ec29c50cff5b23e34b873411514c7940 (patch)
tree48fb77de9e7918a87f896f59a355c81937418f92
Initial commit, including basic HLR testing functionality.
-rw-r--r--.gitignore10
-rw-r--r--LICENSE28
-rw-r--r--README.md2
-rw-r--r--config.example26
-rw-r--r--patches/osmo_map.patch64
-rw-r--r--patches/osmo_sccp.patch187
-rw-r--r--patches/osmo_ss7.patch437
-rw-r--r--rebar.config7
-rw-r--r--src/hex.erl33
-rw-r--r--src/map_msgs.erl590
-rw-r--r--src/sms_7bit_encoding.erl67
-rw-r--r--src/ss7test.app.src12
-rw-r--r--src/ss7test_app.erl544
-rw-r--r--src/ss7test_app.hrl6
-rw-r--r--src/ss7test_helper.erl48
-rw-r--r--src/ss7test_sup.erl27
-rw-r--r--ss7test.iml20
17 files changed, 2108 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8e46d5a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,10 @@
+.eunit
+deps
+*.o
+*.beam
+*.plt
+erl_crash.dump
+ebin
+rel/example_project
+.concrete/DEV_MODE
+.rebar
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..bfc45fc
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,28 @@
+Copyright (c) 2015, Daniel Mende <mail@c0decafe.de>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+* Neither the name of ss7MAPer nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ae192a0
--- /dev/null
+++ b/README.md
@@ -0,0 +1,2 @@
+# ss7MAPer
+SS7 MAP (pen-)testing toolkit
diff --git a/config.example b/config.example
new file mode 100644
index 0000000..4e0bb82
--- /dev/null
+++ b/config.example
@@ -0,0 +1,26 @@
+{sctp, [
+ {local_ip, {10,1,2,3}},
+ {local_port, 2905},
+ {remote_ip, {10,1,2,4}},
+ {remote_port, 2905}
+]}.
+{m3ua, [
+ {local_pc, 10},
+ {remote_pc, 20},
+ {network_ind, 2},
+ {route_ctx, 1},
+ {asp_id, none},
+ {net_appearance, none}
+]}.
+{sccp, [
+ {gt_local, [1,2,3,4,5,6,7,8,9]}
+]}.
+{target, [
+ {gt_hlr, [1,2,3,4,5,6,7,8,9,0]},
+ {gt_vlr, [1,2,3,4,5,6,7,8,9,1]},
+ {gt_msc, []},
+ {gt_sgsn, []},
+ {msisdn, [0,9,8,7,6,5,4,3,2,1]},
+ {service_center, [9,8,7,6,5,4,3,2,1]}
+ {forward_number, [9,8,7,6,5,4,3,2,0]}
+]}.
diff --git a/patches/osmo_map.patch b/patches/osmo_map.patch
new file mode 100644
index 0000000..255914b
--- /dev/null
+++ b/patches/osmo_map.patch
@@ -0,0 +1,64 @@
+diff --git a/ebin/osmo_map.app b/ebin/osmo_map.app
+index 6502f41..6919668 100644
+--- a/ebin/osmo_map.app
++++ b/ebin/osmo_map.app
+@@ -4,7 +4,8 @@
+ {modules, [map, map_codec, map_helper,
+ tcap_asn, tcap_helper,
+ map_only,
+- map_app_sup, map_ss_server, map_dlg_server]},
++ map_app_sup, map_ss_server, map_dlg_server
++ ]},
+ {registered, []},
+ {applications, []},
+ {env, [
+diff --git a/rebar.config b/rebar.config
+index c4805bc..c80d3e2 100644
+--- a/rebar.config
++++ b/rebar.config
+@@ -1,3 +1,6 @@
+ {sub_dirs, ["rel"]}.
++{deps, [
++ {osmo_ss7, "1", {git, "git://git.osmocom.org/erlang/osmo_ss7/", "master"}}
++ ]}.
+ {asn1_opts, [ber,verbose]}.
+ {eunit_opts, [verbose, {report,{eunit_surefire,[{dir,"."}]}}]}.
+diff --git a/src/map_dlg_server.erl b/src/map_dlg_server.erl
+index 08b0fe4..c40ad57 100644
+--- a/src/map_dlg_server.erl
++++ b/src/map_dlg_server.erl
+@@ -3,7 +3,7 @@
+
+ -behaviour(gen_server).
+
+--include_lib("TCAP/include/tcap.hrl").
++-include_lib("tcap.hrl").
+
+ -export([init/1, handle_cast/2, code_change/3, handle_call/3, handle_info/2, terminate/2]).
+
+diff --git a/src/map_helper.erl b/src/map_helper.erl
+index ea1e291..31da750 100644
+--- a/src/map_helper.erl
++++ b/src/map_helper.erl
+@@ -35,7 +35,7 @@
+ -author('Harald Welte <laforge@gnumonks.org>').
+
+ -include_lib("osmo_ss7/include/isup.hrl").
+--include_lib("osmo_map/include/map.hrl").
++-include("map.hrl").
+ -include_lib("osmo_map/include/map_operations.hrl").
+
+ -export([postproc/2, postproc_gt/2, postproc_imsi/2, postproc_msisdn/2,
+diff --git a/src/map_ss_server.erl b/src/map_ss_server.erl
+index 005cd8b..7b022a2 100644
+--- a/src/map_ss_server.erl
++++ b/src/map_ss_server.erl
+@@ -1,7 +1,7 @@
+ -module(map_ss_server).
+ -author('Harald Welte <laforge@gnumonks.org>').
+
+--include_lib("TCAP/include/tcap.hrl").
++-include_lib("tcap.hrl").
+
+ -behaviour(gen_server).
+
diff --git a/patches/osmo_sccp.patch b/patches/osmo_sccp.patch
new file mode 100644
index 0000000..9721bbb
--- /dev/null
+++ b/patches/osmo_sccp.patch
@@ -0,0 +1,187 @@
+diff --git a/ebin/osmo_sccp.app b/ebin/osmo_sccp.app
+index 42d396e..e771403 100644
+--- a/ebin/osmo_sccp.app
++++ b/ebin/osmo_sccp.app
+@@ -9,7 +9,19 @@
+ sccp_scoc,
+ sccp_user,
+ osmo_sccp_tcap,
+- sccp_ssn_dump
++ sccp_ssn_dump,
++ 'DialoguePDUs',
++ 'Remote-Operations-Generic-ROS-PDUs',
++ 'Remote-Operations-Information-Objects',
++ 'Remote-Operations-Useful-Definitions',
++ 'TC-TMP',
++ 'TC-Testing-User',
++ 'TCAP-Examples',
++ 'TCAP-Tools',
++ 'TCAPMessages',
++ 'TR',
++ 'UnidialoguePDUs'
++
+ ]},
+ {registered, [osmo_sccp_app]},
+ {mod, {osmo_sccp_app, []}},
+diff --git a/rebar.config b/rebar.config
+index 7851051..87c0d28 100644
+--- a/rebar.config
++++ b/rebar.config
+@@ -1,3 +1,6 @@
+ {erl_opts, [debug_info]}.
+-{sub_dirs, ["rel"]}.
++{sub_dirs, ["rel"]}.
++{deps, [
++ {osmo_ss7, "1", {git, "git://git.osmocom.org/erlang/osmo_ss7/", "master"}}
++ ]}.
+ {eunit_opts, [verbose, {report,{eunit_surefire,[{dir,"."}]}}]}.
+diff --git a/src/osmo_sccp_tcap.erl b/src/osmo_sccp_tcap.erl
+index 96cc32f..42fe8d4 100644
+--- a/src/osmo_sccp_tcap.erl
++++ b/src/osmo_sccp_tcap.erl
+@@ -9,7 +9,7 @@
+
+ -include_lib("osmo_ss7/include/osmo_util.hrl").
+ -include_lib("osmo_ss7/include/sccp.hrl").
+--include_lib("TCAP/include/sccp.hrl").
++-include("sccp.hrl").
+
+ %% callbacks needed for gen_server behaviour
+ -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+diff --git a/src/sccp_scrc.erl b/src/sccp_scrc.erl
+index e103172..6872d2b 100644
+--- a/src/sccp_scrc.erl
++++ b/src/sccp_scrc.erl
+@@ -31,7 +31,7 @@
+
+ -module(sccp_scrc).
+ -behaviour(gen_fsm).
+--export([start_link/1, init/1, terminate/3, idle/2, handle_info/3]).
++-export([start_link/1, init/1, stop/0, terminate/3, idle/2, handle_info/3, handle_event/3]).
+
+ -include_lib("osmo_ss7/include/osmo_util.hrl").
+ -include_lib("osmo_ss7/include/sccp.hrl").
+@@ -42,7 +42,8 @@
+ -record(scrc_state, {
+ scoc_conn_ets,
+ next_local_ref,
+- sup_pid % pid() of the supervisor
++ sup_pid, % pid() of the supervisor
++ ni
+ }).
+ % TODO: Integrate with proper SCCP routing / GTT implementation
+
+@@ -68,11 +69,15 @@ start_link(InitData) ->
+ gen_fsm:start_link({local, sccp_scrc}, sccp_scrc,
+ [{sup_pid,self()}|InitData], [{debug, []}]).
+
++stop() ->
++ gen_fsm:send_all_state_event(?MODULE, stop).
++
+ % gen_fsm init callback, called by start_link()
+ init(InitPropList) ->
+ io:format("SCRC Init PropList~p ~n", [InitPropList]),
+ UserPid = proplists:get_value(sup_pid, InitPropList),
+- LoopData = #scrc_state{sup_pid = UserPid, next_local_ref = 0},
++ LoopData = #scrc_state{sup_pid = UserPid, next_local_ref = 0,
++ ni = proplists:get_value(ni, InitPropList)},
+ TableRef = ets:new(scoc_by_ref, [set]),
+ put(scoc_by_ref, TableRef),
+ ok = ss7_links:bind_service(?MTP3_SERV_SCCP, "osmo_sccp"),
+@@ -183,7 +188,7 @@ idle(#primitive{subsystem = 'MTP', gen_name = 'TRANSFER',
+ case sccp_routing:route_mtp3_sccp_in(Mtp3) of
+ {remote, SccpMsg2, LsName, Dpc} ->
+ io:format("routed to remote?!?~n"),
+- {ok, M3} = create_mtp3_out(SccpMsg2, LsName, Dpc),
++ {ok, M3} = create_mtp3_out(SccpMsg2, LsName, Dpc, LoopDat#scrc_state.ni),
+ % generate a MTP-TRANSFER.req primitive to the lower layer
+ send_mtp_transfer_down(M3, LsName),
+ LoopDat1 = LoopDat;
+@@ -241,13 +246,16 @@ idle(#primitive{subsystem = 'OCRC', gen_name = 'CONNECTION',
+ LoopDat2 = send_sccp_local_out(LoopDat, SccpMsg),
+ {next_state, idle, LoopDat2}.
+
++handle_event(stop, _StateName, StateData) ->
++ {stop, normal, StateData}.
++
+ send_mtp_transfer_down(Mtp3) when is_record(Mtp3, mtp3_msg) ->
+ ss7_links:mtp3_tx(Mtp3).
+
+ send_mtp_transfer_down(Mtp3, LsName) when is_record(Mtp3, mtp3_msg) ->
+ ss7_links:mtp3_tx(Mtp3, LsName).
+
+-create_mtp3_out(SccpMsg, LsName, Dpc) when is_record(SccpMsg, sccp_msg) ->
++create_mtp3_out(SccpMsg, LsName, Dpc, Ni) when is_record(SccpMsg, sccp_msg) ->
+ case Dpc of
+ undefined ->
+ {error, dpc_undefined};
+@@ -263,7 +271,7 @@ create_mtp3_out(SccpMsg, LsName, Dpc) when is_record(SccpMsg, sccp_msg) ->
+ M3R = #mtp3_routing_label{sig_link_sel = 0,
+ origin_pc = Opc,
+ dest_pc = Dpc},
+- M3 = #mtp3_msg{network_ind = ?MTP3_NETIND_INTERNATIONAL,
++ M3 = #mtp3_msg{network_ind = Ni,
+ service_ind = ?MTP3_SERV_SCCP,
+ routing_label = M3R,
+ payload = SccpEnc},
+@@ -271,21 +279,21 @@ create_mtp3_out(SccpMsg, LsName, Dpc) when is_record(SccpMsg, sccp_msg) ->
+ end
+ end.
+
+-create_mtp3_out(SccpMsg, LsName) when is_record(SccpMsg, sccp_msg) ->
++create_mtp3_out(SccpMsg, LsName, Ni) when is_record(SccpMsg, sccp_msg) ->
+ CalledParty = proplists:get_value(called_party_addr,
+ SccpMsg#sccp_msg.parameters),
+ % we _have_ to have a destination point code here
+ Dpc = CalledParty#sccp_addr.point_code,
+- create_mtp3_out(SccpMsg, LsName, Dpc).
++ create_mtp3_out(SccpMsg, LsName, Dpc, Ni).
+
+ send_sccp_local_out(LoopDat, SccpMsg) when is_record(SccpMsg, sccp_msg) ->
+ case sccp_routing:route_local_out(SccpMsg) of
+ {remote, SccpMsg2, LsName, Dpc} ->
+- % FIXME: get to MTP-TRANSFER.req
+- {ok, M3} = create_mtp3_out(SccpMsg2, LsName, Dpc),
+- % generate a MTP-TRANSFER.req primitive to the lower layer
+- send_mtp_transfer_down(M3, LsName),
+- LoopDat;
++ % FIXME: get to MTP-TRANSFER.req
++ {ok, M3} = create_mtp3_out(SccpMsg2, LsName, Dpc, LoopDat#scrc_state.ni),
++ % generate a MTP-TRANSFER.req primitive to the lower layer
++ Ret = send_mtp_transfer_down(M3, LsName),
++ LoopDat;
+ {local, SccpMsg2, UserPid} ->
+ deliver_to_scoc_sclc(LoopDat, SccpMsg2, UserPid);
+ {error, Reason} ->
+diff --git a/src/sccp_user.erl b/src/sccp_user.erl
+index 3f51034..0f508f6 100644
+--- a/src/sccp_user.erl
++++ b/src/sccp_user.erl
+@@ -41,7 +41,7 @@
+ terminate/2, code_change/3]).
+
+ % our published API
+--export([start_link/0]).
++-export([start_link/0, stop/0]).
+
+ % client functions, may internally talk to our sccp_user server
+ -export([bind_ssn/1, bind_ssn/2, unbind_ssn/2, pid_for_ssn/2, local_ssn_avail/2,
+@@ -66,6 +66,9 @@ init(_Arg) ->
+ {keypos, #scu_record.ssn_pc}]),
+ {ok, #scu_state{user_table = UserTbl}}.
+
++stop() ->
++ gen_server:cast(?MODULE, stop).
++
+ % client side code
+
+ bind_ssn(Ssn) when is_integer(Ssn) ->
+@@ -143,6 +146,8 @@ handle_call({unbind_ssn, Ssn, Pc}, {FromPid, _FromRef}, S) ->
+ ets:delete_object(Tbl, DelRec),
+ {reply, ok, S}.
+
++handle_cast(stop, State) ->
++ {stop, normal, State};
+ handle_cast(Info, S) ->
+ error_logger:error_report(["unknown handle_cast",
+ {module, ?MODULE},
diff --git a/patches/osmo_ss7.patch b/patches/osmo_ss7.patch
new file mode 100644
index 0000000..d71e653
--- /dev/null
+++ b/patches/osmo_ss7.patch
@@ -0,0 +1,437 @@
+diff --git a/include/osmo_ss7.hrl b/include/osmo_ss7.hrl
+index 80dcb3e..8ccdf42 100644
+--- a/include/osmo_ss7.hrl
++++ b/include/osmo_ss7.hrl
+@@ -14,7 +14,10 @@
+ sls :: non_neg_integer(),
+ local :: record(sigtran_peer),
+ remote :: record(sigtran_peer),
+- role :: role()
++ role :: role(),
++ asp_id :: integer(),
++ route_ctx :: integer(),
++ net_app :: integer()
+ }).
+
+
+diff --git a/include/sccp.hrl b/include/sccp.hrl
+index 46b4c11..58036cc 100644
+--- a/include/sccp.hrl
++++ b/include/sccp.hrl
+@@ -66,6 +66,8 @@
+ -define(SCCP_SSN_RES_NAT, 2#000001100).
+ -define(SCCP_SSN_BISDN, 2#000001101).
+ -define(SCCP_SSN_TC_TEST, 2#000001110).
++-define(SCCP_SSN_SGSN, 2#010010101).
++-define(SCCP_SSN_CAP, 2#010010010).
+
+ % According to Q.731 Section 3.4.2.3.1
+ -define(SCCP_NAI_SUBSCRIBER, 2#00000001).
+diff --git a/src/m3ua_codec.erl b/src/m3ua_codec.erl
+index 5b29b7e..e5976e8 100644
+--- a/src/m3ua_codec.erl
++++ b/src/m3ua_codec.erl
+@@ -86,11 +86,17 @@ parse_m3ua_opt(Opt, Msg) ->
+
+
+ encode_m3ua_msg(#m3ua_msg{version = Version, msg_class = MsgClass,
++ msg_type = ?M3UA_MSGT_ASPSM_ASPUP, payload = OptList}) ->
++ OptBin = encode_m3ua_opts(OptList),
++ MsgLen = byte_size(OptBin) + 8,
++ <<Version:8, 0:8, MsgClass:8, ?M3UA_MSGT_ASPSM_ASPUP:8, MsgLen:32/big, OptBin/binary>>;
++encode_m3ua_msg(#m3ua_msg{version = Version, msg_class = MsgClass,
+ msg_type = MsgType, payload = OptList}) ->
+ OptBin = encode_m3ua_opts(OptList),
+- MsgLen = byte_size(OptBin) + 8,
++ MsgLen = byte_size(OptBin) + 8,
+ <<Version:8, 0:8, MsgClass:8, MsgType:8, MsgLen:32/big, OptBin/binary>>.
+
++
+ encode_m3ua_opts(OptList) when is_list(OptList) ->
+ encode_m3ua_opts(OptList, <<>>).
+
+@@ -114,6 +120,8 @@ encode_m3ua_opt(?M3UA_IEI_PROTOCOL_DATA, Mtp3) when is_record(Mtp3, mtp3_msg) ->
+ end,
+ PayBin = <<Opc:32/big, Dpc:32/big, Si:8, Ni:8, MpD:8, Sls:8, Payload/binary>>,
+ encode_m3ua_opt(?M3UA_IEI_PROTOCOL_DATA, PayBin);
++encode_m3ua_opt(Iei, Data) when is_integer(Iei), is_binary(Data), Data == <<>> ->
++ <<>>;
+ encode_m3ua_opt(Iei, Data) when is_integer(Iei), is_binary(Data) ->
+ Length = byte_size(Data) + 4,
+ PadLen = get_num_pad_bytes(Length),
+diff --git a/src/m3ua_core.erl b/src/m3ua_core.erl
+index 2adc424..4198cb7 100644
+--- a/src/m3ua_core.erl
++++ b/src/m3ua_core.erl
+@@ -40,7 +40,7 @@
+ -include("sccp.hrl").
+ -include("m3ua.hrl").
+
+--export([start_link/1]).
++-export([start_link/1, stop/0]).
+
+ -export([init/1, terminate/3, code_change/4, handle_event/3, handle_info/3]).
+
+@@ -59,11 +59,18 @@
+ sctp_remote_ip,
+ sctp_remote_port,
+ sctp_sock,
+- sctp_assoc_id
++ sctp_assoc_id,
++ asp_id,
++ net_app,
++ route_ctx
+ }).
+
+ start_link(InitOpts) ->
+- gen_fsm:start_link(?MODULE, InitOpts, [{debug, [trace]}]).
++ %~ gen_fsm:start_link(?MODULE, InitOpts, [{debug, [trace]}]).
++ gen_fsm:start_link(?MODULE, InitOpts, []).
++
++stop() ->
++ gen_fsm:send_all_state_event(?MODULE, stop).
+
+ reconnect_sctp(L = #m3ua_state{sctp_remote_ip = Ip, sctp_remote_port = Port, sctp_sock = Sock}) ->
+ timer:sleep(1*1000),
+@@ -91,13 +98,24 @@ build_openopts(PropList) ->
+ [{active, once}, {reuseaddr, true}] ++
+ lists:flatten(lists:map(fun build_openopt/1, PropList)).
+
++build_extarg(Arg, _) when Arg == none ->
++ <<>>;
++build_extarg(Arg, Len) ->
++ <<Arg:Len>>.
++
+ init(InitOpts) ->
+ {ok, SctpSock} = gen_sctp:open(build_openopts(InitOpts)),
++ Asp_id = proplists:get_value(asp_id, InitOpts),
++ Route_ctx = proplists:get_value(route_ctx, InitOpts),
++ Net_app = proplists:get_value(net_app, InitOpts),
+ LoopDat = #m3ua_state{role = asp, sctp_sock = SctpSock,
+ user_fun = proplists:get_value(user_fun, InitOpts),
+ user_args = proplists:get_value(user_args, InitOpts),
+ sctp_remote_ip = proplists:get_value(sctp_remote_ip, InitOpts),
+- sctp_remote_port = proplists:get_value(sctp_remote_port, InitOpts)},
++ sctp_remote_port = proplists:get_value(sctp_remote_port, InitOpts),
++ asp_id = build_extarg(Asp_id,32),
++ route_ctx = build_extarg(Route_ctx,32),
++ net_app = build_extarg(Net_app,32)},
+ LoopDat2 = reconnect_sctp(LoopDat),
+ {ok, asp_down, LoopDat2}.
+
+@@ -145,6 +163,8 @@ send_msg_start_tack(LoopDat, State, MsgClass, MsgType, Params) ->
+
+
+
++handle_event(stop, State, LoopDat) ->
++ {stop, normal, LoopDat};
+ handle_event(Event, State, LoopDat) ->
+ io:format("Unknown Event ~p in state ~p~n", [Event, State]),
+ {next_state, State, LoopDat}.
+@@ -191,13 +211,16 @@ handle_info({sctp, Socket, _RemoteIp, _RemotePort, {ANC, SPC}},
+ {State, LoopDat};
+ addr_made_prim ->
+ % FIXME: do we need to change remote_ip in our LoopDat?
++ {State, LoopDat};
++ addr_confirmed ->
++ % FIXME: do we need to change remote_ip in our LoopDat?
+ {State, LoopDat}
+ end,
+ inet:setopts(Socket, [{active, once}]),
+ {next_state, NewState, LoopDat2};
+
+ handle_info({sctp, Socket, RemoteIp, RemotePort, {[Anc], Data}}, State, LoopDat) ->
+- io:format("SCTP rx data: ~p ~p~n", [Anc, Data]),
++ %~ io:format("SCTP rx data: ~p ~p~n", [Anc, Data]),
+ % process incoming SCTP data
+ if Socket == LoopDat#m3ua_state.sctp_sock,
+ RemoteIp == LoopDat#m3ua_state.sctp_remote_ip,
+@@ -218,13 +241,19 @@ handle_info({sctp, Socket, RemoteIp, RemotePort, {_Anc, Data}}, _State, LoopDat)
+ {next_state, asp_down, LoopDat}.
+
+
+-
+ asp_down(#primitive{subsystem = 'M', gen_name = 'ASP_UP',
+ spec_name = request, parameters = _Params}, LoopDat) ->
+ % M-ASP_UP.req from user, generate message and send to remote peer
+- send_msg_start_tack(LoopDat, asp_down, ?M3UA_MSGC_ASPSM, ?M3UA_MSGT_ASPSM_ASPUP, []);
++ %%%%%%%%%%%%%%%%%%%%%%%%%%%%
++ % added asp identifier
++ % added network appearance
++ %%%%%%%%%%%%%%%%%%%%%%%%%%%%
++ send_msg_start_tack(LoopDat, asp_down, ?M3UA_MSGC_ASPSM, ?M3UA_MSGT_ASPSM_ASPUP,
++ [{?M3UA_IEI_ASP_ID, LoopDat#m3ua_state.asp_id}]);
+ asp_down({timer_expired, t_ack, {?M3UA_MSGC_ASPSM, ?M3UA_MSGT_ASPSM_ASPUP, Params}}, LoopDat) ->
+- send_msg_start_tack(LoopDat, asp_down, ?M3UA_MSGC_ASPSM, ?M3UA_MSGT_ASPSM_ASPUP, Params);
++ send_msg_start_tack(LoopDat, asp_down, ?M3UA_MSGC_ASPSM, ?M3UA_MSGT_ASPSM_ASPUP,
++ lists:append([{?M3UA_IEI_NET_APPEARANCE, LoopDat#m3ua_state.net_app},
++ {?M3UA_IEI_ASP_ID, LoopDat#m3ua_state.asp_id}], Params));
+
+ asp_down(#m3ua_msg{msg_class = ?M3UA_MSGC_ASPSM,
+ msg_type = ?M3UA_MSGT_ASPSM_ASPUP_ACK}, LoopDat) ->
+@@ -240,8 +269,14 @@ asp_down(M3uaMsg, LoopDat) when is_record(M3uaMsg, m3ua_msg) ->
+ asp_inactive(#primitive{subsystem = 'M', gen_name = 'ASP_ACTIVE',
+ spec_name = request, parameters = _Params}, LoopDat) ->
+ % M-ASP_ACTIVE.req from user, generate message and send to remote peer
++ %%%%%%%%%%%%%%%%%%%%%%%%%%%%
++ % added routing context
++ % added network appearance
++ %%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ send_msg_start_tack(LoopDat, asp_inactive, ?M3UA_MSGC_ASPTM, ?M3UA_MSGT_ASPTM_ASPAC,
+- [{?M3UA_IEI_TRAF_MODE_TYPE, <<0,0,0,1>>}]);
++ [{?M3UA_IEI_NET_APPEARANCE, LoopDat#m3ua_state.net_app},
++ {?M3UA_IEI_TRAF_MODE_TYPE, <<0,0,0,2>>},
++ {?M3UA_IEI_ROUTE_CTX, LoopDat#m3ua_state.route_ctx}]);
+
+ asp_inactive({timer_expired, t_ack, {?M3UA_MSGC_ASPTM, ?M3UA_MSGT_ASPTM_ASPAC, Params}}, LoopDat) ->
+ send_msg_start_tack(LoopDat, asp_inactive, ?M3UA_MSGC_ASPTM, ?M3UA_MSGT_ASPTM_ASPAC, Params);
+@@ -310,7 +345,10 @@ asp_active({timer_expired, t_ack, {?M3UA_MSGC_ASPTM, ?M3UA_MSGT_ASPTM_ASPIA, Par
+ asp_active(#primitive{subsystem = 'MTP', gen_name = 'TRANSFER',
+ spec_name = request, parameters = Params}, LoopDat) ->
+ % MTP-TRANSFER.req from user app: Send message to remote peer
+- OptList = [{?M3UA_IEI_PROTOCOL_DATA, Params}],
++ %%%%%%%%%%%%%%%%%%%%%%%%%%%%
++ % added routing context
++ %%%%%%%%%%%%%%%%%%%%%%%%%%%%
++ OptList = [{?M3UA_IEI_ROUTE_CTX, LoopDat#m3ua_state.route_ctx}, {?M3UA_IEI_PROTOCOL_DATA, Params}],
+ Msg = #m3ua_msg{version = 1, msg_class = ?M3UA_MSGC_TRANSFER,
+ msg_type = ?M3UA_MSGT_XFR_DATA,
+ payload = OptList},
+diff --git a/src/sccp_codec.erl b/src/sccp_codec.erl
+index 2374d73..9119269 100644
+--- a/src/sccp_codec.erl
++++ b/src/sccp_codec.erl
+@@ -326,7 +326,11 @@ encode_sccp_addr(#sccp_addr{res_nat_use = ResNatUse,
+ {PCind, PCbin} = encode_pc(PointCode),
+ ResNatOut = undef_or_true(ResNatUse),
+ RoutIndOut = undef_or_true(RoutInd),
+- <<ResNatOut:1, RoutIndOut:1, GTind:4, SSNind:1, PCind:1, PCbin/binary, SSNbin/binary, GTbin/binary>>.
++ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++ % removed pointcode
++ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++ %<<ResNatOut:1, RoutIndOut:1, GTind:4, SSNind:1, PCind:1, PCbin/binary, SSNbin/binary, GTbin/binary>>.
++ <<ResNatOut:1, RoutIndOut:1, GTind:4, SSNind:1, 0:1, SSNbin/binary, GTbin/binary>>.
+
+
+ encode_sccp_opt({AddrTag, AddrVal}) when AddrTag == ?SCCP_PNC_CALLED_PARTY_ADDRESS;
+diff --git a/src/sctp_core.erl b/src/sctp_core.erl
+index 1e9fc21..730046e 100644
+--- a/src/sctp_core.erl
++++ b/src/sctp_core.erl
+@@ -222,7 +222,7 @@ handle_info({sctp, Socket, _RemoteIp, _RemotePort, {ANC, SAC}},
+
+ handle_info({sctp, Socket, RemoteIp, RemotePort, {[Anc], Data}}, State, LoopDat) ->
+ Module = LoopDat#sctp_state.module,
+- io:format("SCTP rx data: ~p ~p~n", [Anc, Data]),
++ %~ io:format("SCTP rx data: ~p ~p~n", [Anc, Data]),
+ % process incoming SCTP data
+ if Socket == LoopDat#sctp_state.sctp_sock,
+ RemoteIp == LoopDat#sctp_state.sctp_remote_ip,
+diff --git a/src/ss7_link_m3ua.erl b/src/ss7_link_m3ua.erl
+index 15ba837..4d7551d 100644
+--- a/src/ss7_link_m3ua.erl
++++ b/src/ss7_link_m3ua.erl
+@@ -41,8 +41,9 @@
+ -include_lib("osmo_ss7/include/osmo_ss7.hrl").
+
+ -export([start_link/1, init/1]).
+-
+--export([handle_cast/2]).
++-export([stop/0]).
++-export([handle_cast/2, terminate/2]).
++-export([get_link_state/1]).
+
+ -record(loop_dat, {
+ m3ua_pid,
+@@ -50,16 +51,20 @@
+ }).
+
+ start_link(Args) ->
+- gen_server:start_link(?MODULE, Args, [{debug, [trace]}]).
++ %~ gen_server:start_link(?MODULE, Args, [{debug, [trace]}]).
++ gen_server:start_link(?MODULE, Args, []).
+
+ init(L = #sigtran_link{type = m3ua, name = Name, linkset_name = LinksetName,
+- sls = Sls, local = Local, remote = Remote}) ->
++ sls = Sls, local = Local, remote = Remote, asp_id = Asp_id,
++ route_ctx = Route_ctx, net_app = Net_app}) ->
+ #sigtran_peer{ip = LocalIp, port = LocalPort} = Local,
+ #sigtran_peer{ip = RemoteIp, port = RemotePort} = Remote,
+ % start the M3UA link to the SG
+ Opts = [{user_pid, self()},
+ {sctp_remote_ip, RemoteIp}, {sctp_remote_port, RemotePort},
+ {sctp_local_ip, LocalIp}, {sctp_local_port, LocalPort},
++ {asp_id, Asp_id}, {route_ctx, Route_ctx},
++ {net_app, Net_app},
+ {user_fun, fun m3ua_tx_to_user/2}, {user_args, self()}],
+ {ok, M3uaPid} = m3ua_core:start_link(Opts),
+ % FIXME: register this link with SCCP_SCRC
+@@ -70,10 +75,16 @@ init(L = #sigtran_link{type = m3ua, name = Name, linkset_name = LinksetName,
+ % {ok, ScrcPid} = sccp_scrc:start_link([{mtp_tx_action, {callback_fn, fun scrc_tx_to_mtp/2, M3uaPid}}]),
+ % loop(#loop_dat{m3ua_pid = M3uaPid, scrc_pid = ScrcPid}).
+
++stop() ->
++ gen_server:cast(?MODULE, stop).
++
+
+ set_link_state(#sigtran_link{linkset_name = LinksetName, sls = Sls}, State) ->
+ ok = ss7_links:set_link_state(LinksetName, Sls, State).
+
++get_link_state(#sigtran_link{linkset_name = LinksetName, sls = Sls}) ->
++ ss7_links:get_link_state(LinksetName, Sls).
++
+ scrc_tx_to_mtp(Prim, Args) ->
+ M3uaPid = Args,
+ gen_fsm:send_event(M3uaPid, Prim).
+@@ -114,11 +125,14 @@ handle_cast(#primitive{subsystem = 'M', gen_name = 'ASP_INACTIVE'}, L) ->
+ io:format("~p: ASP_INACTIVE.ind~n", [?MODULE]),
+ set_link_state(L#loop_dat.link, up),
+ {noreply, L};
++handle_cast(stop, State) ->
++ gen_fsm:send_all_state_event(State#loop_dat.m3ua_pid, stop),
++ {stop, normal, State};
+ handle_cast(P, L) ->
+- io:format("~p: Ignoring M3UA prim ~p~n", [?MODULE, P]),
++ %~ io:format("~p: Ignoring M3UA prim ~p~n", [?MODULE, P]),
+ {noreply, L}.
+
+-terminate(Reason, _S) ->
++terminate(Reason, State) ->
+ io:format("terminating ~p with reason ~p", [?MODULE, Reason]),
+ ok.
+
+diff --git a/src/ss7_links.erl b/src/ss7_links.erl
+index 3aef33c..794a923 100644
+--- a/src/ss7_links.erl
++++ b/src/ss7_links.erl
+@@ -43,11 +43,12 @@
+ terminate/2, code_change/3]).
+
+ % our published API
+--export([start_link/0, reload_config/0]).
++-export([start_link/0, reload_config/0, stop/0]).
+
+ % client functions, may internally talk to our sccp_user server
+ -export([register_linkset/3, unregister_linkset/1]).
+ -export([register_link/3, unregister_link/2, set_link_state/3]).
++-export([get_link_state/2]).
+ -export([bind_service/2, unbind_service/1]).
+
+ -export([get_pid_for_link/2, get_pid_for_dpc_sls/2,
+@@ -95,7 +96,8 @@
+ % initialization code
+
+ start_link() ->
+- gen_server:start_link({local, ?MODULE}, ?MODULE, [], [{debug, [trace]}]).
++ %~ gen_server:start_link({local, ?MODULE}, ?MODULE, [], [{debug, [trace]}]).
++ gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+
+ init(_Arg) ->
+ LinksetTbl = ets:new(ss7_linksets, [ordered_set, named_table,
+@@ -111,6 +113,11 @@ init(_Arg) ->
+ {ok, #su_state{linkset_tbl = LinksetTbl, link_tbl = LinkTbl,
+ service_tbl = ServiceTbl}}.
+
++stop() ->
++ gen_server:cast(?MODULE, stop).
++
++
++
+ % client side API
+
+ % all write operations go through gen_server:call(), as only the ?MODULE
+@@ -145,6 +152,9 @@ unregister_link(LinksetName, Sls) ->
+ set_link_state(LinksetName, Sls, State) ->
+ gen_server:call(?MODULE, {set_link_state, {LinksetName, Sls, State}}).
+
++get_link_state(LinksetName, Sls) ->
++ gen_server:call(?MODULE, {get_link_state, {LinksetName, Sls}}).
++
+ -spec bind_service(non_neg_integer(), string()) ->
+ ok | error().
+
+@@ -389,6 +399,15 @@ handle_call({set_link_state, {LsName, Sls, State}}, {FromPid, _}, S) ->
+ {reply, ok, S}
+ end;
+
++handle_call({get_link_state, {LsName, Sls}}, {FromPid, _}, S) ->
++ #su_state{link_tbl = LinkTbl} = S,
++ case ets:lookup(LinkTbl, {LsName, Sls}) of
++ [] ->
++ {reply, {error, no_such_link}, S};
++ [Link] ->
++ {reply, {ok, Link#slink.state}, S}
++ end;
++
+ handle_call({bind_service, {SNum, SName}}, {FromPid, _},
+ #su_state{service_tbl = ServTbl} = S) ->
+ NewServ = #service{name = SName, service_nr = SNum,
+@@ -408,6 +427,9 @@ handle_call({unbind_service, {SNum}}, {FromPid, _},
+ ets:delete(ServTbl, SNum),
+ {reply, ok, S}.
+
++
++handle_cast(stop, State) ->
++ {stop, normal, State};
+ handle_cast(Info, S) ->
+ error_logger:error_report(["unknown handle_cast",
+ {module, ?MODULE},
+@@ -432,8 +454,11 @@ handle_info(Info, S) ->
+ {info, Info}, {state, S}]),
+ {noreply, S}.
+
+-terminate(Reason, _S) ->
++terminate(Reason, S) ->
+ io:format("terminating ~p with reason ~p", [?MODULE, Reason]),
++ ets:delete(S#su_state.linkset_tbl),
++ ets:delete(S#su_state.link_tbl),
++ ets:delete(S#su_state.service_tbl),
+ ok.
+
+ code_change(_OldVsn, State, _Extra) ->
+diff --git a/src/ss7_routes.erl b/src/ss7_routes.erl
+index 8965de7..116e172 100644
+--- a/src/ss7_routes.erl
++++ b/src/ss7_routes.erl
+@@ -48,10 +48,10 @@
+ -include_lib("osmo_ss7/include/mtp3.hrl").
+
+ % gen_fsm callbacks
+--export([init/1, handle_call/3, handle_info/2, terminate/2, code_change/3]).
++-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
+
+ % our published API
+--export([start_link/0]).
++-export([start_link/0, stop/0]).
+
+ % client functions, may internally talk to our sccp_user server
+ -export([create_route/3, delete_route/3, flush_routes/0]).
+@@ -71,13 +71,17 @@
+ % initialization code
+
+ start_link() ->
+- gen_server:start_link({local, ?MODULE}, ?MODULE, [], [{debug, [trace]}]).
++ %~ gen_server:start_link({local, ?MODULE}, ?MODULE, [], [{debug, [trace]}]).
++ gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+
+ init(_Arg) ->
+ RouteTbl = ets:new(ss7_routes, [ordered_set, named_table,
+ {keypos, #ss7route.remote_pc_mask}]),
+ process_flag(trap_exit, true),
+ {ok, #sr_state{route_tbl = RouteTbl}}.
++
++stop() ->
++ gen_server:cast(?MODULE, stop).
+
+ % client side API
+
+@@ -130,6 +134,9 @@ dump_single_route(#ss7route{remote_pc_mask = {Pc, Mask},
+
+ % server side code
+
++handle_cast(stop, State) ->
++ {stop, normal, State}.
++
+ handle_call({create_route, {RemotePc, RemoteMask, Name}},
+ {_FromPid, _FromRef}, S) ->
+ #sr_state{route_tbl = Tbl} = S,
diff --git a/rebar.config b/rebar.config
new file mode 100644
index 0000000..06fd2c9
--- /dev/null
+++ b/rebar.config
@@ -0,0 +1,7 @@
+{erl_opts, [debug_info]}.
+{deps, [
+ {osmo_ss7, "1", {git, "git://git.osmocom.org/erlang/osmo_ss7/", "master"}},
+ {osmo_sccp, "1", {git, "git://git.osmocom.org/erlang/osmo_sccp/", "master"}},
+ {osmo_map, "1", {git, "git://git.osmocom.org/erlang/osmo_map/", "master"}}
+ ]}.
+{eunit_opts, [verbose, {report,{eunit_surefire,[{dir,"."}]}}]}.
diff --git a/src/hex.erl b/src/hex.erl
new file mode 100644
index 0000000..8529f95
--- /dev/null
+++ b/src/hex.erl
@@ -0,0 +1,33 @@
+-module(hex).
+-export([bin_to_hexstr/1,hexstr_to_bin/1]).
+
+hex(N) when N < 10 ->
+ $0+N;
+hex(N) when N >= 10, N < 16 ->
+ $a+(N-10).
+
+int(C) when $0 =< C, C =< $9 ->
+ C - $0;
+int(C) when $A =< C, C =< $F ->
+ C - $A + 10;
+int(C) when $a =< C, C =< $f ->
+ C - $a + 10.
+
+to_hex(N) when N < 256 ->
+ [hex(N div 16), hex(N rem 16)].
+
+list_to_hexstr([]) ->
+ [];
+list_to_hexstr([H|T]) ->
+ to_hex(H) ++ list_to_hexstr(T).
+
+bin_to_hexstr(Bin) ->
+ list_to_hexstr(binary_to_list(Bin)).
+
+hexstr_to_bin(S) ->
+ list_to_binary(hexstr_to_list(S)).
+
+hexstr_to_list([X,Y|T]) ->
+ [int(X)*16 + int(Y) | hexstr_to_list(T)];
+hexstr_to_list([]) ->
+ [].
diff --git a/src/map_msgs.erl b/src/map_msgs.erl
new file mode 100644
index 0000000..e70b9c7
--- /dev/null
+++ b/src/map_msgs.erl
@@ -0,0 +1,590 @@
+-module(map_msgs).
+-author('Daniel Mende <mail@c0decafe.de>').
+
+-include_lib("osmo_map/src/map.hrl").
+-include("ss7test_app.hrl").
+
+-compile([export_all]).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% default values msgs
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% SCCP Definitions
+-define(LOCAL_GLOBAL_TITLE, [1,2,3,4,5,6,7,8,9]).
+-define(REMOTE_GLOBAL_TITLE, [1,2,3,4,5,6,7,8,0]).
+-define(NI, 2).
+
+% MAP Definitions
+-define(IMSI, hex:hexstr_to_bin("01020304050607f8")).
+-define(IMSI_AS_NR, ss7test_helper:encode_msisdn(?NUMBER_EXTENSION_NONE,
+ ?NUMBER_NATURE_INTERNATIONAL, ?NUMBER_LAND_MOBILE,
+ [0,1,0,2,0,3,0,4,0,5,0,6,0,7,8])).
+-define(TMSI, hex:hexstr_to_bin("1234")).
+
+-define(MSISDN, ss7test_helper:encode_msisdn(?NUMBER_EXTENSION_NONE,
+ ?NUMBER_NATURE_INTERNATIONAL, ?NUMBER_PLAN_ISDN,
+ [1,2,3,4,5,6,7,8,9,0])).
+
+-define(SERVICE_CENTER_ADDRESS, hex:hexstr_to_bin("010203040506f1")).
+-define(SERVICE_CENTER_ADDRESS_OA, hex:hexstr_to_bin("010203040506f1")).
+-define(SERVICE_CENTER_ADDRESS_DA, hex:hexstr_to_bin("010203040506f1")).
+-define(GGSN, ss7test_helper:encode_msisdn(?NUMBER_EXTENSION_NONE,
+ ?NUMBER_NATURE_INTERNATIONAL, ?NUMBER_PLAN_ISDN,
+ [1,2,3,4,5,6,7,8,9,0])).
+
+
+
+% SMS Definitions
+-define(ORIGINATING_ADDRESS, hex:hexstr_to_bin("0b9010203040506f1")).
+-define(DESTINATION_ADDRESS, ss7test_helper:encode_msisdn(?NUMBER_EXTENSION_NONE,
+ ?NUMBER_NATURE_INTERNATIONAL, ?NUMBER_PLAN_ISDN,
+ [1,2,3,4,5,6,7,8,9,0])).
+-define(SOURCE_ADDRESS, ss7test_helper:encode_msisdn(?NUMBER_EXTENSION_NONE,
+ ?NUMBER_NATURE_INTERNATIONAL, ?NUMBER_PLAN_ISDN,
+ [1,2,3,4,5,6,7,8,9,0])).
+-define(SMS_DEST_NR, [1,2,3,4,5,6,7,8,9,0]).
+-define(SMS_SRC_NR, [1,2,3,4,5,6,7,8,9,0]).
+
+-define(IMEI, ss7test_helper:encode_phonenumner([1,2,3,4,5,6,7,8,9,0])).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% helper
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+get_transactionId() ->
+ <<1,1,0,0>>.
+
+build_dialog_request(Context) ->
+ build_dialog_request(Context, asn1_NOVALUE).
+build_dialog_request(Context, Extra) ->
+ DialoguePDU = {'dialogueRequest',
+ {'AARQ-apdu',
+ [version1],
+ Context,
+ Extra}},
+ {ok, EncDialoguePDU} = map:encode('DialoguePDU', DialoguePDU),
+ EncDialoguePDU.
+
+encode_map_pdu(Dialog, Local, MapData) ->
+ MapPDU = {'begin',
+ {'MapSpecificPDUs_begin',
+ get_transactionId(),
+ {'EXTERNAL',
+ ?'dialogue-as-id',
+ asn1_NOVALUE,asn1_NOVALUE,
+ {'single-ASN1-type', Dialog}},
+ [{basicROS,
+ {invoke,
+ {'MapSpecificPDUs_begin_components_SEQOF_basicROS_invoke',
+ {present,1},
+ asn1_NOVALUE,
+ {local,Local},
+ MapData}}}]}},
+ {ok, EncMapPDU} = map:encode('MapSpecificPDUs', MapPDU),
+ EncMapPDU.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% SMS
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+create_testSMSPDU_mt() ->
+ SMSText = "ERNW test SMS 31337",
+ EncSMSText = 'sms_7bit_encoding':to_7bit(SMSText),
+ SMSLen = string:len(SMSText),
+ Timestamp = hex:hexstr_to_bin("51803001258080"),
+ DA = ss7test_helper:encode_phonenumber(?NUMBER_EXTENSION_NONE,
+ ?NUMBER_NATURE_INTERNATIONAL, ?NUMBER_PLAN_ISDN,
+ ?SMS_SRC_NR),
+ <<0:4, 4:4, DA/binary, 0:8, 0:8, Timestamp/binary, SMSLen:8, EncSMSText/binary>>.
+
+create_testSMSPDU_mo() ->
+ SMSText = "ERNW test SMS 31337",
+ EncSMSText = 'sms_7bit_encoding':to_7bit(SMSText),
+ SMSLen = string:len(SMSText),
+ DA = ss7test_helper:encode_phonenumber(?NUMBER_EXTENSION_NONE,
+ ?NUMBER_NATURE_INTERNATIONAL, ?NUMBER_PLAN_ISDN,
+ ?SMS_DEST_NR),
+ <<1:8, 23:8, DA/binary, 0:8, 0:8, SMSLen:8, EncSMSText/binary>>.
+
+create_mt_forwardSM_v2() ->
+ create_mt_forwardSM_v2(create_testSMSPDU_mt()).
+create_mt_forwardSM_v2(SMSPDU) ->
+ MapData = { 'ForwardSM-Arg',
+ {imsi, ?IMSI},
+ {serviceCentreAddressOA, ?SERVICE_CENTER_ADDRESS_DA},
+ SMSPDU,
+ asn1_NOVALUE, asn1_NOVALUE},
+ Dialog = build_dialog_request({0,4,0,0,1,0,25,2}),
+ encode_map_pdu(Dialog, 46, MapData).
+
+create_mt_forwardSM() ->
+ create_mt_forwardSM(create_testSMSPDU_mt()).
+create_mt_forwardSM(SMSPDU) ->
+ MapData = {'MT-ForwardSM-Arg',
+ {serviceCentreAddressDA, ?SERVICE_CENTER_ADDRESS_DA},
+ {msisdn, ?SOURCE_ADDRESS},
+ SMSPDU,
+ asn1_NOVALUE, %moreMessagesToSend
+ asn1_NOVALUE %extensionContainer
+ },
+ Dialog = build_dialog_request(?'shortMsgMT-RelayContext-v3'),
+ encode_map_pdu(Dialog, 44, MapData).
+
+create_mo_forwardSM_v2() ->
+ create_mo_forwardSM_v2(create_testSMSPDU_mo()).
+create_mo_forwardSM_v2(SMSPDU) ->
+ MapData = { 'ForwardSM-Arg',
+ {serviceCentreAddressDA, ?SERVICE_CENTER_ADDRESS_DA},
+ {msisdn, ?MSISDN},
+ SMSPDU,
+ asn1_NOVALUE, asn1_NOVALUE},
+ Dialog = build_dialog_request({0,4,0,0,1,0,21,2}),
+ encode_map_pdu(Dialog, 46, MapData).
+
+create_mo_forwardSM() ->
+ create_mo_forwardSM(create_testSMSPDU_mo()).
+create_mo_forwardSM(SMSPDU) ->
+ MapData = {'MO-ForwardSM-Arg',
+ {serviceCentreAddressDA, ?SERVICE_CENTER_ADDRESS_DA},
+ {msisdn, ?MSISDN},
+ SMSPDU,
+ asn1_NOVALUE,
+ asn1_NOVALUE % ?IMSI_FROM
+ },
+ Dialog = build_dialog_request(?'shortMsgMO-RelayContext-v3'),
+ encode_map_pdu(Dialog, 46, MapData).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% to HLR
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+create_sendRoutingInfoForSM() ->
+ create_sendRoutingInfoForSM(?MSISDN, ?SERVICE_CENTER_ADDRESS).
+create_sendRoutingInfoForSM(Msisdn, Ssaddr) ->
+ MapData = { 'send-routing-info-for-sm-arg',
+ Msisdn,
+ false,
+ Ssaddr,
+ asn1_NOVALUE,
+ asn1_NOVALUE, %true,
+ asn1_NOVALUE, %0,
+ asn1_NOVALUE, %hex:hexstr_to_bin("0b912374140404f7"),
+ asn1_NOVALUE},
+ MapPDU = {'begin',
+ {'MapSpecificPDUs_begin',
+ get_transactionId(),
+ {'EXTERNAL',
+ {0,0,17,773,1,1,1},
+ asn1_NOVALUE,
+ asn1_NOVALUE,
+ {'single-ASN1-type',<<96,11,161,9,6,7,4,0,0,1,0,20,3>>}},
+ [{'basicROS',
+ {'invoke',
+ {'MapSpecificPDUs_begin_components_SEQOF_basicROS_invoke',
+ {'present', 1},
+ asn1_NOVALUE,
+ {'local', 45},
+ MapData}}}]}},
+ {ok, EncMapPDU} = map:encode('MapSpecificPDUs', MapPDU),
+ EncMapPDU.
+
+create_sendRoutingInfo() ->
+ create_sendRoutingInfo(?MSISDN, ?LOCAL_GLOBAL_TITLE).
+create_sendRoutingInfo(Msisdn, OrGsmSCF) ->
+ MapData = {'SendRoutingInfoArg',
+ Msisdn,
+ asn1_NOVALUE,
+ asn1_NOVALUE,
+ basicCall,
+ asn1_NOVALUE,
+ asn1_NOVALUE,
+ ss7test_helper:encode_msisdn(?NUMBER_EXTENSION_NONE,
+ ?NUMBER_NATURE_INTERNATIONAL, ?NUMBER_PLAN_ISDN,
+ OrGsmSCF), %gsmc-OrGsmSCF-Address
+ <<7,0,1,68,40,9,150,31>>, %callReferenceNumber
+ asn1_NOVALUE,
+ asn1_NOVALUE,
+ asn1_NOVALUE,
+ {'CamelInfo',
+ [phase1,phase2,phase3,phase4],
+ asn1_NOVALUE,
+ asn1_NOVALUE,
+ ['o-csi','d-csi','t-csi']},
+ asn1_NOVALUE,
+ {'ExtensionContainer',
+ [{'PrivateExtension',{1,2,826,0,1249,58,1,0},<<164,5,48,3,129,1,6>>}, %Nokia ExtensionType Extension
+ {'PrivateExtension',{0,34,5},<<224,2,132,0>>}], %Nokia srbtSupportIndicator
+ asn1_NOVALUE},
+ asn1_NOVALUE,
+ asn1_NOVALUE,
+ asn1_NOVALUE,
+ asn1_NOVALUE,
+ asn1_NOVALUE,
+ asn1_NOVALUE, %'NULL',
+ asn1_NOVALUE,
+ asn1_NOVALUE, %'NULL',
+ asn1_NOVALUE,
+ asn1_NOVALUE,
+ asn1_NOVALUE,
+ asn1_NOVALUE,
+ asn1_NOVALUE,
+ asn1_NOVALUE,
+ asn1_NOVALUE,
+ asn1_NOVALUE},
+ MapPDU = {'begin',
+ {'MapSpecificPDUs_begin',
+ get_transactionId(),
+ {'EXTERNAL',
+ {0,0,17,773,1,1,1},
+ asn1_NOVALUE,asn1_NOVALUE,
+ {'single-ASN1-type',
+ <<96,15,128,2,7,128,161,9,6,7,4,0,0,1,0,5,3>>}},
+ [{basicROS,
+ {invoke,
+ {'MapSpecificPDUs_begin_components_SEQOF_basicROS_invoke',
+ {present,1},
+ asn1_NOVALUE,
+ {local,22},
+ MapData}}}]}},
+ {ok, EncMapPDU} = map:encode('MapSpecificPDUs', MapPDU),
+ EncMapPDU.
+
+%~ create_sendRoutingInfoForGprs() ->
+ %~ MapData = {'SendRoutingInforForGprsArg',
+ %~ ?IMSI,
+ %~ asn1_NOVALUE,
+ %~ ?GGSN},
+ %~ MapPDU = {'begin',
+ %~ {'MapSpecificPDUs_begin',
+ %~ get_transactionId(),
+ %~ {'EXTERNAL',
+ %~ {0,0,17,773,1,1,1},
+ %~ asn1_NOVALUE,asn1_NOVALUE,
+ %~ {'single-ASN1-type',<<96,15,128,2,7,128,161,9,6,7,4,0,0,1,0,33,4>>}},
+ %~ [{basicROS,
+ %~ {invoke,
+ %~ {'MapSpecificPDUs_begin_components_SEQOF_basicROS_invoke',
+ %~ {present,1},
+ %~ asn1_NOVALUE,
+ %~ {local,24},
+ %~ MapData}}}]}},
+ %~ {ok, EncMapPDU} = map:encode('MapSpecificPDUs', MapPDU),
+ %~ EncMapPDU.
+
+%~ create_registerSS() ->
+ %~ create_registerSS(?IMSI, ?DESTINATION_ADDRESS).
+create_registerSS(Imsi, Origin, DestinationNumber) ->
+ MapDialoguePDU = {'map-open',
+ {'MAP-OpenInfo',
+ ss7test_helper:encode_msisdn(?NUMBER_EXTENSION_NONE,
+ ?NUMBER_NATURE_INTERNATIONAL, ?NUMBER_LAND_MOBILE,
+ Imsi), %destinationReference
+ ss7test_helper:encode_msisdn(?NUMBER_EXTENSION_NONE,
+ ?NUMBER_NATURE_INTERNATIONAL, ?NUMBER_PLAN_ISDN,
+ Origin), %originationReference
+ asn1_NOVALUE }},
+ {ok, EncMapDialoguePDU} = map:encode('MAP-DialoguePDU', MapDialoguePDU),
+ DialoguePDU = {'dialogueRequest',
+ {'AARQ-apdu',
+ [version1],
+ ?'networkFunctionalSsContext-v2', %?'ss-InvocationNotificationContext-v3',
+ [{'EXTERNAL',
+ ?'map-DialogueAS',
+ asn1_NOVALUE,asn1_NOVALUE,
+ {'single-ASN1-type',
+ EncMapDialoguePDU}}]}},
+ {ok, EncDialoguePDU} = map:encode('DialoguePDU', DialoguePDU),
+ MapData = {'RegisterSS-Arg',
+ ?allForwardingSS, %ss-Code
+ asn1_NOVALUE,
+ DestinationNumber, %forwardedToNumber
+ asn1_NOVALUE,
+ asn1_NOVALUE,
+ asn1_NOVALUE,
+ asn1_NOVALUE,
+ asn1_NOVALUE},
+ MapPDU = {'begin',
+ {'MapSpecificPDUs_begin',
+ get_transactionId(),
+ {'EXTERNAL',
+ ?'dialogue-as-id',
+ asn1_NOVALUE,asn1_NOVALUE,
+ {'single-ASN1-type',
+ EncDialoguePDU
+ }},
+ [{basicROS,
+ {invoke,
+ {'MapSpecificPDUs_begin_components_SEQOF_basicROS_invoke',
+ {present,1},
+ asn1_NOVALUE,
+ {local,10},
+ MapData}}}]}},
+ {ok, EncMapPDU} = map:encode('MapSpecificPDUs', MapPDU),
+ EncMapPDU.
+
+create_eraseSS(Imsi, Origin) ->
+ MapDialoguePDU = {'map-open',
+ {'MAP-OpenInfo',
+ ss7test_helper:encode_msisdn(?NUMBER_EXTENSION_NONE,
+ ?NUMBER_NATURE_INTERNATIONAL, ?NUMBER_LAND_MOBILE,
+ Imsi), %destinationReference
+ ss7test_helper:encode_msisdn(?NUMBER_EXTENSION_NONE,
+ ?NUMBER_NATURE_INTERNATIONAL, ?NUMBER_PLAN_ISDN,
+ Origin), %originationReference
+ asn1_NOVALUE }},
+ {ok, EncMapDialoguePDU} = map:encode('MAP-DialoguePDU', MapDialoguePDU),
+ DialoguePDU = {'dialogueRequest',
+ {'AARQ-apdu',
+ [version1],
+ ?'networkFunctionalSsContext-v2', %?'ss-InvocationNotificationContext-v3',
+ [{'EXTERNAL',
+ ?'map-DialogueAS',
+ asn1_NOVALUE,asn1_NOVALUE,
+ {'single-ASN1-type',
+ EncMapDialoguePDU}}]}},
+ {ok, EncDialoguePDU} = map:encode('DialoguePDU', DialoguePDU),
+ MapData = {'SS-ForBS-Code-Arg',
+ ?allForwardingSS, %ss-Code
+ asn1_NOVALUE, %basicService
+ asn1_NOVALUE}, %longFTN-Supported
+ MapPDU = {'begin',
+ {'MapSpecificPDUs_begin',
+ get_transactionId(),
+ {'EXTERNAL',
+ ?'dialogue-as-id',
+ asn1_NOVALUE,asn1_NOVALUE,
+ {'single-ASN1-type',
+ EncDialoguePDU
+ }},
+ [{basicROS,
+ {invoke,
+ {'MapSpecificPDUs_begin_components_SEQOF_basicROS_invoke',
+ {present,1},
+ asn1_NOVALUE,
+ {local,11},
+ MapData}}}]}},
+ {ok, EncMapPDU} = map:encode('MapSpecificPDUs', MapPDU),
+ EncMapPDU.
+
+create_updateLocation() ->
+ create_updateLocation(?IMSI, ?LOCAL_GLOBAL_TITLE, ?LOCAL_GLOBAL_TITLE).
+create_updateLocation(Imsi, MscNr, VlrNr) ->
+ MapData = {'UpdateLocationArg',
+ Imsi,
+ ss7test_helper:encode_msisdn(?NUMBER_EXTENSION_NONE,
+ ?NUMBER_NATURE_INTERNATIONAL, ?NUMBER_PLAN_ISDN,
+ MscNr), %msc-Number
+ ss7test_helper:encode_msisdn(?NUMBER_EXTENSION_NONE,
+ ?NUMBER_NATURE_INTERNATIONAL, ?NUMBER_PLAN_ISDN,
+ VlrNr), %vlr-Number
+ asn1_NOVALUE, %<<59,151,2,0>>, %lmsi
+ asn1_NOVALUE,
+ {'VLR-Capability',
+ [phase1,phase2,phase3,phase4],
+ asn1_NOVALUE,asn1_NOVALUE,asn1_NOVALUE,
+ asn1_NOVALUE,'NULL',
+ [lcsCapabilitySet1,lcsCapabilitySet2],
+ ['o-csi','d-csi','vt-csi','mt-sms-csi'],
+ asn1_NOVALUE,asn1_NOVALUE},
+ asn1_NOVALUE,asn1_NOVALUE,asn1_NOVALUE,
+ asn1_NOVALUE,asn1_NOVALUE,asn1_NOVALUE,
+ 'NULL'},
+ MapPDU = {'begin',
+ {'MapSpecificPDUs_begin',
+ get_transactionId(),
+ {'EXTERNAL',
+ ?'dialogue-as-id',
+ asn1_NOVALUE,asn1_NOVALUE,
+ {'single-ASN1-type',
+ <<96,15,128,2,7,128,161,9,6,7,4,0,0,1,0,1,3>>}},
+ [{basicROS,
+ {invoke,
+ {'MapSpecificPDUs_begin_components_SEQOF_basicROS_invoke',
+ {present,1},
+ asn1_NOVALUE,
+ {local,2},
+ MapData}}}]}},
+ {ok, EncMapPDU} = map:encode('MapSpecificPDUs', MapPDU),
+ EncMapPDU.
+
+create_anyTimeInerrogation() ->
+ create_anyTimeInerrogation(?IMSI, ?LOCAL_GLOBAL_TITLE).
+create_anyTimeInerrogation(Imsi, GsmSCF) ->
+ MapData = {'AnyTimeInterrogationArg',
+ {imsi, Imsi}, %{msisdn,?MSISDN},
+ {'RequestedInfoMAP-MS-DataTypes',asn1_NOVALUE,
+ 'NULL',asn1_NOVALUE,asn1_NOVALUE,
+ asn1_NOVALUE,asn1_NOVALUE,asn1_NOVALUE,
+ asn1_NOVALUE,asn1_NOVALUE,asn1_NOVALUE},
+ ss7test_helper:encode_msisdn(?NUMBER_EXTENSION_NONE,
+ ?NUMBER_NATURE_INTERNATIONAL, ?NUMBER_PLAN_ISDN,
+ GsmSCF), %gsmSCF-Address
+ asn1_NOVALUE},
+ Dialog = build_dialog_request(?'anyTimeInfoEnquiryContext-v3'),
+ encode_map_pdu(Dialog, 71, MapData).
+
+create_sendAuthenticationInfo() ->
+ create_sendAuthenticationInfo(?IMSI).
+create_sendAuthenticationInfo(Imsi) ->
+ create_sendAuthenticationInfo(Imsi, 5).
+create_sendAuthenticationInfo(Imsi, Nr) ->
+ MapData = { 'SendAuthenticationInfoArg',
+ Imsi,
+ Nr,
+ asn1_NOVALUE,
+ asn1_NOVALUE,
+ asn1_NOVALUE,
+ asn1_NOVALUE,
+ asn1_NOVALUE,
+ asn1_NOVALUE,
+ asn1_NOVALUE,
+ asn1_NOVALUE},
+ MapPDU = {'begin',
+ {'MapSpecificPDUs_begin',
+ get_transactionId(),
+ {'EXTERNAL',
+ ?'dialogue-as-id',
+ asn1_NOVALUE,asn1_NOVALUE,
+ {'single-ASN1-type',<<96,11,161,9,6,7,4,0,0,1,0,14,3>>}},
+ [{basicROS,
+ {invoke,
+ {'MapSpecificPDUs_begin_components_SEQOF_basicROS_invoke',
+ {present,1},
+ asn1_NOVALUE,
+ {local,56},
+ MapData}}}]}},
+ {ok, EncMapPDU} = map:encode('MapSpecificPDUs', MapPDU),
+ EncMapPDU.
+
+create_sendImsi() ->
+ create_sendImsi(?MSISDN).
+create_sendImsi(Msisdn) ->
+ MapData = Msisdn,
+ Dialog = build_dialog_request(?'imsiRetrievalContext-v2'),
+ encode_map_pdu(Dialog, 58, MapData).
+
+create_purgeMs() ->
+ create_purgeMs(?IMSI, ?LOCAL_GLOBAL_TITLE).
+create_purgeMs(Imsi, VlrNr) ->
+ MapData = {'PurgeMs-Arg',
+ Imsi,
+ ss7test_helper:encode_msisdn(?NUMBER_EXTENSION_NONE,
+ ?NUMBER_NATURE_INTERNATIONAL, ?NUMBER_PLAN_ISDN,
+ VlrNr), %VLR number
+ asn1_NOVALUE, %SGSN number
+ asn1_NOVALUE}, %Last Known Location
+ Dialog = build_dialog_request(?'msPurgingContext-v3'),
+ encode_map_pdu(Dialog, 67, MapData).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% to MSC/VLR
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+create_provideSubscriberInfo() ->
+ MapData = {'ProvideSubscriberInfoArg',
+ ?IMSI,
+ asn1_NOVALUE,
+ {'RequestedInfoMAP-MS-DataTypes',
+ 'NULL', %locationInformation
+ 'NULL', %subscriberState
+ asn1_NOVALUE, %extensionContainer
+ 'NULL', %currentLocation
+ asn1_NOVALUE, %requestedDomain
+ 'NULL', %imei
+ asn1_NOVALUE, %ms-classmark
+ asn1_NOVALUE, %mnpRequestedInfo
+ asn1_NOVALUE, %t-adsData
+ asn1_NOVALUE %requestedNodes
+ },
+ asn1_NOVALUE,
+ asn1_NOVALUE},
+ Dialog = build_dialog_request(?'subscriberInfoEnquiryContext-v3'),
+ encode_map_pdu(Dialog, 70, MapData).
+
+create_sendIdentification() ->
+ MapData = { 'SendIdentificationArg',
+ ?TMSI,
+ 5, %NumberOfRequestedVectors
+ asn1_NOVALUE, %segmentationProhibited
+ asn1_NOVALUE, %ExtensionContainer
+ asn1_NOVALUE, %msc-Number
+ asn1_NOVALUE, %previous-LAI
+ asn1_NOVALUE %hopCounter
+ },
+ Dialog = build_dialog_request(?'interVlrInfoRetrievalContext-v3'),
+ encode_map_pdu(Dialog, 55, MapData).
+
+create_provideRoamingNumber() ->
+ MapData = {'ProvideRoamingNumberArg',
+ ?IMSI,
+ ss7test_helper:encode_msisdn(?NUMBER_EXTENSION_NONE,
+ ?NUMBER_NATURE_INTERNATIONAL, ?NUMBER_PLAN_ISDN,
+ [1,2,3,4,5,6,7,8,9]), %msc-Number
+ ?MSISDN,
+ asn1_NOVALUE,
+ {'ExternalSignalInfo','gsm-0408',
+ <<4,1,160>>,
+ asn1_NOVALUE},
+ asn1_NOVALUE,asn1_NOVALUE,
+ ss7test_helper:encode_msisdn(?NUMBER_EXTENSION_NONE,
+ ?NUMBER_NATURE_INTERNATIONAL, ?NUMBER_PLAN_ISDN,
+ [1,2,3,4,5,6,7,8,9,0]), %gmsc-Address
+ <<112,4,1,54,22,114,55>>, %callReferenceNumber
+ asn1_NOVALUE,asn1_NOVALUE,asn1_NOVALUE,
+ asn1_NOVALUE,
+ [phase1,phase2,phase3],
+ asn1_NOVALUE,'NULL',asn1_NOVALUE,asn1_NOVALUE,
+ asn1_NOVALUE,asn1_NOVALUE,asn1_NOVALUE,
+ asn1_NOVALUE,asn1_NOVALUE},
+ Dialog = build_dialog_request(?'roamingNumberEnquiryContext-v3'),
+ encode_map_pdu(Dialog, 4, MapData).
+
+create_cancelLocation() ->
+ MapData = {'CancelLocationArg',
+ {imsi,?IMSI},
+ updateProcedure,
+ asn1_NOVALUE,
+ asn1_NOVALUE},
+ Dialog = build_dialog_request(?'locationCancellationContext-v3'), %v2: {0,4,0,0,1,0,2,2}
+ encode_map_pdu(Dialog, 3, MapData).
+
+create_insertSubscriberData() ->
+ MapData = {'InsertSubscriberDataArg',
+ asn1_NOVALUE, %imsi
+ asn1_NOVALUE, %msisdn
+ asn1_NOVALUE, %additional msisdn
+ asn1_NOVALUE, %category
+ asn1_NOVALUE, %subscriber status
+ asn1_NOVALUE, %bearer service list
+ asn1_NOVALUE, %teleservice list
+ asn1_NOVALUE, %forwarding information list
+ asn1_NOVALUE, %call barring information list
+ asn1_NOVALUE, %CUG information list
+ asn1_NOVALUE, %SS-Data list
+ asn1_NOVALUE, %eMLPP subscription data
+ asn1_NOVALUE, %MC-subscription data
+ asn1_NOVALUE, %
+ asn1_NOVALUE,
+ asn1_NOVALUE,
+ asn1_NOVALUE,
+ asn1_NOVALUE,
+ asn1_NOVALUE,
+ asn1_NOVALUE,
+ asn1_NOVALUE,
+ asn1_NOVALUE,
+ asn1_NOVALUE,
+ asn1_NOVALUE,
+ asn1_NOVALUE,
+ asn1_NOVALUE,
+ asn1_NOVALUE,
+ asn1_NOVALUE,
+ asn1_NOVALUE,
+ asn1_NOVALUE,
+ asn1_NOVALUE,
+ asn1_NOVALUE,
+ asn1_NOVALUE,
+ asn1_NOVALUE
+ },
+ Dialog = build_dialog_request('subscriberDataMngtContext-v3'),
+ encode_map_pdu(Dialog, 7, MapData).
+
diff --git a/src/sms_7bit_encoding.erl b/src/sms_7bit_encoding.erl
new file mode 100644
index 0000000..4c39b78
--- /dev/null
+++ b/src/sms_7bit_encoding.erl
@@ -0,0 +1,67 @@
+-module(sms_7bit_encoding).
+
+-author("dawid.figiel@gmail.com").
+-compile(export_all).
+
+%% 7-bit encoding
+%% ---------------------------------------------------------------------
+%% Function for converting string to 7-bit encoding according to:
+%% GSM 03.38 Version 5.3.0
+%% ---------------------------------------------------------------------
+%% Initial Function call: to_7bit(String).
+%% ---------------------------------------------------------------------
+%% Input: String containing only ASCII characters
+%% ---------------------------------------------------------------------
+%% Output: Binary encoded String
+%% ---------------------------------------------------------------------
+to_7bit([]) -> <<>>;
+to_7bit(String) -> to_7bit(list_to_binary(String),<<>>,1).
+
+to_7bit(<<Char1:8>>,<<>>,_Cntr) -> <<<<Char1>>/binary>>;
+to_7bit(<<_Char1:8>>,Out,8) -> Out;
+to_7bit(<< Char1:8>>,Out,7) ->
+%% << Out/binary,<<((Char1 bsr 6) bor 26)>>/binary >>;
+ << Out/binary,<<((Char1 bsr 6) bor 64)>>/binary >>;
+to_7bit(<<Char1:8>>,Out,Cntr) ->
+ << Out/binary,<<(Char1 bsr (Cntr - 1))>>/binary >>;
+to_7bit(<<_Char:8,In/binary>>,Out,8)->
+ to_7bit(In,Out,1);
+to_7bit(<<Char1:8,Char2:8,In/binary>>,Out,Cntr)->
+ SRChar1 = Char1 bsr (Cntr - 1),
+ NewChar1 = <<Char2:Cntr,SRChar1:(8-Cntr)>>,
+ to_7bit(<<<<Char2>>/binary,In/binary>>,<<Out/binary,NewChar1/binary>>,Cntr+1).
+
+%% 7-bit decoding
+%% ---------------------------------------------------------------------
+%% Function for converting 7-bit encoding to String according to:
+%% GSM 03.38 Version 5.3.0
+%% ---------------------------------------------------------------------
+%% Initial Function call: from_7bit(SevenBitEncodedBinary).
+%% ---------------------------------------------------------------------
+%% Input: Binary encoded String
+%% ---------------------------------------------------------------------
+%% Output: String containing only ASCII characters
+%% ---------------------------------------------------------------------
+from_7bit(List) when is_list(List) -> from_7bit(list_to_binary(List));
+from_7bit(<<>>) -> [];
+from_7bit(Bin) ->
+ from_7bit(Bin,<<>>,<<>>,1).
+
+%%from_7bit(<<>>,<<13>>,Out,8) ->
+from_7bit(<<>>,<<32>>,Out,8) ->
+ binary_to_list(Out);
+from_7bit(<<>>,<<CharN>>,Out,8) ->
+ binary_to_list(<<Out/binary,CharN:8>>);
+from_7bit(<<>>,<<0>>,Out,_Cntr) ->
+ binary_to_list(Out);
+from_7bit(<<>>,<<CharN>>,Out,Cntr) ->
+ binary_to_list(<<Out/binary,0:(9-Cntr),CharN:(Cntr-1)>>);
+from_7bit(<<CharN:1,CharO:7,In/binary>>,<<CharI>>,Out,8) ->
+ from_7bit(In,<<CharN>>,<<Out/binary,0:1,CharI:7,0:1,CharO:7>>,2);
+from_7bit(<<Byte:8,In/binary>>,<<>>,<<>>,1) ->
+ CharN = Byte bsr 7,
+ from_7bit(In,<<CharN>>,<<(Byte band 127)>>,2);
+from_7bit(<<Byte:8,In/binary>>,<<CharI>>,Out,Cntr) ->
+ Char = (Byte bsl (Cntr - 1)) bor CharI,
+ CharN = Byte bsr (8 - Cntr),
+ from_7bit(In,<<CharN>>,<<Out/binary,0:1,Char:7>>,Cntr+1).
diff --git a/src/ss7test.app.src b/src/ss7test.app.src
new file mode 100644
index 0000000..cb8c804
--- /dev/null
+++ b/src/ss7test.app.src
@@ -0,0 +1,12 @@
+{application, ss7test,
+ [
+ {description, ""},
+ {vsn, "1"},
+ {registered, []},
+ {applications, [
+ kernel,
+ stdlib
+ ]},
+ {mod, { ss7test_app, ["./config"]}},
+ {env, []}
+ ]}.
diff --git a/src/ss7test_app.erl b/src/ss7test_app.erl
new file mode 100644
index 0000000..cc0bb22
--- /dev/null
+++ b/src/ss7test_app.erl
@@ -0,0 +1,544 @@
+-module(ss7test_app).
+-author('Daniel Mende <mail@c0decafe.de>').
+
+-behaviour(application).
+
+%% Application callbacks
+-export([start/2, stop/1]).
+
+-include_lib("osmo_ss7/include/osmo_util.hrl").
+-include_lib("osmo_ss7/include/osmo_ss7.hrl").
+-include_lib("osmo_ss7/include/m3ua.hrl").
+-include_lib("osmo_ss7/include/sccp.hrl").
+-include_lib("osmo_ss7/include/isup.hrl").
+-include_lib("osmo_map/src/tcap_asn.hrl").
+-include("ss7test_app.hrl").
+
+-define(TRACE, false).
+
+-record(loop_dat, {
+ scrc_pid,
+ m3ua_pid,
+ ss7links_pid,
+ ss7routes_pid,
+ link,
+ local_pc,
+ remote_pc,
+ gt_local,
+ gt_hlr,
+ gt_vlr,
+ gt_msc,
+ gt_sgsn,
+ msisdn,
+ imsi,
+ scenter,
+ fnumber
+ }).
+
+%% change routing context in m3ua_code.erl => fixed
+%% add pointcode to sccp in sccp_codec.erl
+%% change network indicator in sccp_scrc.erl => fixed
+
+
+%% ===================================================================
+%% Application callbacks
+%% ===================================================================
+
+start(_, Configfile) ->
+ LoopDat = init(Configfile),
+ spawn(ss7test_app, loop, [LoopDat]).
+
+stop(_State) ->
+ ok.
+
+%% ===================================================================
+
+init(Configfile) ->
+ Config = case file:consult(Configfile) of
+ {ok, C} ->
+ C;
+ {error, E} ->
+ io:fwrite("\e[91;1mError reading config file ~p:~n~p~n\e[39;49;0m", [Configfile, E]),
+ exit(config_error)
+ end,
+ {sctp, SCTP_Config} = lists:keyfind(sctp, 1, Config),
+ {m3ua, M3UA_Config} = lists:keyfind(m3ua, 1, Config),
+ {sccp, Sccp} = lists:keyfind(sccp, 1, Config),
+ {target, Target} = lists:keyfind(target, 1, Config),
+ {gt_local, GT_Local} = lists:keyfind(gt_local, 1, Sccp),
+ {gt_hlr, GT_Hlr} = lists:keyfind(gt_hlr, 1, Target),
+ {gt_vlr, GT_Vlr} = lists:keyfind(gt_vlr, 1, Target),
+ {gt_msc, GT_Msc} = lists:keyfind(gt_msc, 1, Target),
+ {gt_sgsn, GT_Sgsn} = lists:keyfind(gt_sgsn, 1, Target),
+ {msisdn, Msisdn} = lists:keyfind(msisdn, 1, Target),
+ Msisdn_enc = ss7test_helper:encode_msisdn(?NUMBER_EXTENSION_NONE,
+ ?NUMBER_NATURE_INTERNATIONAL, ?NUMBER_PLAN_ISDN,
+ Msisdn),
+ {service_center, SCenter} = lists:keyfind(service_center, 1, Target),
+ SCenter_enc = ss7test_helper:encode_msisdn(?NUMBER_EXTENSION_NONE,
+ ?NUMBER_NATURE_INTERNATIONAL, ?NUMBER_PLAN_ISDN,
+ SCenter),
+ {forward_number, FNumber} = lists:keyfind(forward_number, 1, Target),
+ FNumber_enc = ss7test_helper:encode_msisdn(?NUMBER_EXTENSION_NONE,
+ ?NUMBER_NATURE_INTERNATIONAL, ?NUMBER_PLAN_ISDN,
+ FNumber),
+ % start link server and create linkset
+ {ok, SS7linksPid} = ss7_links:start_link(),
+ sys:trace(ss7_links, ?TRACE),
+ {local_pc, Local_PC} = lists:keyfind(local_pc, 1, M3UA_Config),
+ {remote_pc, Remote_PC} = lists:keyfind(remote_pc, 1, M3UA_Config),
+ ok = ss7_links:register_linkset(Local_PC, Remote_PC, "test_linkset"),
+ %start route server and add route
+ {ok, SS7routesPid} = ss7_routes:start_link(),
+ ok = ss7_routes:create_route(Remote_PC, 16#ffff, "test_linkset"),
+ % create m3ua link
+ {local_ip, Local_IP} = lists:keyfind(local_ip, 1, SCTP_Config),
+ {local_port, Local_Port} = lists:keyfind(local_port, 1, SCTP_Config),
+ Local = #sigtran_peer{ip = Local_IP, port = Local_Port},
+ {remote_ip, Remote_IP} = lists:keyfind(remote_ip, 1, SCTP_Config),
+ {remote_port, Remote_Port} = lists:keyfind(remote_port, 1, SCTP_Config),
+ Remote = #sigtran_peer{ip = Remote_IP, port = Remote_Port},
+ {asp_id, Asp_ID} = lists:keyfind(asp_id, 1, M3UA_Config),
+ {route_ctx, Route_CTX} = lists:keyfind(route_ctx, 1, M3UA_Config),
+ {net_appearance, Net_Appearance} = lists:keyfind(net_appearance, 1, M3UA_Config),
+ Link = #sigtran_link{type = m3ua, name = "test_link", linkset_name = "test_linkset",
+ sls = 0, local = Local, remote = Remote, asp_id = Asp_ID,
+ route_ctx = Route_CTX, net_app = Net_Appearance},
+ {ok, M3uaPid} = ss7_link_m3ua:start_link(Link),
+ % instantiate SCCP routing instance
+ {network_ind, Network_Ind} = lists:keyfind(network_ind, 1, M3UA_Config),
+ {ok, ScrcPid} = sccp_scrc:start_link([{mtp_tx_action, {callback_fn, fun scrc_tx_to_mtp/2, M3uaPid}},
+ {ni, Network_Ind}]),
+ sys:trace(sccp_scrc, ?TRACE),
+ {ok, _SccpPid} = sccp_user:start_link(),
+ sys:trace(sccp_user, ?TRACE),
+ io:format("Waiting for M3UA link comming up...~n"),
+ wait_for_link(Link),
+ #loop_dat{m3ua_pid = M3uaPid, scrc_pid = ScrcPid, ss7links_pid = SS7linksPid, ss7routes_pid = SS7routesPid, link = Link,
+ local_pc = Local_PC, remote_pc = Remote_PC, gt_local = GT_Local, gt_hlr = GT_Hlr, gt_vlr = GT_Vlr, gt_msc = GT_Msc,
+ gt_sgsn = GT_Sgsn, msisdn = Msisdn_enc, scenter = SCenter_enc, fnumber = FNumber_enc}.
+
+wait_for_link(Link) ->
+ case ss7_link_m3ua:get_link_state(Link) of
+ {ok, down} ->
+ timer:sleep(100),
+ wait_for_link(Link);
+ {ok, active} ->
+ {ok};
+ _ ->
+ {error}
+ end.
+
+loop(L) ->
+ receive
+ {sccp, Prim} ->
+ {primitive, 'N', 'UNITDATA', indication, Data} = Prim,
+ {sccp_msg, _, ProtData} = Data,
+ {user_data, _UserData} = lists:keyfind(user_data, 1, ProtData),
+ %~ io:format("Rx: ~p~n", [map:decode('MapSpecificPDUs', UserData)]),
+ loop(L);
+ {send, {Gts, {Lssn, Rssn}}, Data} ->
+ Tlssn = case Lssn of
+ hlr -> ?SCCP_SSN_HLR;
+ vlr -> ?SCCP_SSN_VLR;
+ msc -> ?SCCP_SSN_MSC;
+ sgsn -> ?SCCP_SSN_SGSN;
+ cap -> ?SCCP_SSN_CAP;
+ _ -> ?SCCP_SSN_UNKNOWN
+ end,
+ Trssn = case Rssn of
+ hlr -> ?SCCP_SSN_HLR;
+ vlr -> ?SCCP_SSN_VLR;
+ msc -> ?SCCP_SSN_MSC;
+ sgsn -> ?SCCP_SSN_SGSN;
+ cap -> ?SCCP_SSN_CAP;
+ _ -> ?SCCP_SSN_UNKNOWN
+ end,
+ send_tcap(L, Gts, {Tlssn, Trssn}, Data),
+ loop(L);
+ {test_hlr} ->
+ io:format("Testing HLR~n"),
+ test_hlr(L),
+ loop(L);
+ {link_state} ->
+ io:format("~p~n", [ss7_link_m3ua:get_link_state(L#loop_dat.link)]),
+ loop(L);
+ Stop ->
+ M3uaPid = L#loop_dat.m3ua_pid,
+ %~ ScrcPid = L#loop_dat.scrc_pid,
+ SS7linksPid = L#loop_dat.ss7links_pid,
+ %~ SS7routesPid = L#loop_dat.ss7routes_pid,
+ io:format("Received ~p~n", [Stop]),
+ sccp_user:stop(),
+ sccp_scrc:stop(),
+ %~ ss7_link_m3ua:stop(),
+ gen_server:cast(M3uaPid, stop),
+ ss7_routes:stop(),
+ %~ ss7_links:stop(),
+ gen_server:cast(SS7linksPid, stop),
+ exit(stop_received)
+ end.
+
+test_hlr(L) ->
+ LocalSsn = ?SCCP_SSN_MSC,
+ ok = sccp_user:bind_ssn(LocalSsn),
+ Gts = {L#loop_dat.gt_local, L#loop_dat.gt_hlr},
+ test_sri(Gts, L),
+ test_srifs(Gts, L),
+ L2 = test_si(Gts, L),
+ test_sai(Gts, L2, 100),
+ test_sai(Gts, L2, 10),
+ test_sai(Gts, L2, 5),
+ test_rss(Gts, L2),
+ test_ess(Gts, L2),
+ test_ul(Gts, L2),
+ test_ati(Gts, L2),
+ test_pms(Gts, L2),
+ ok = sccp_user:unbind_ssn(LocalSsn, undefined).
+
+test_sri(Gts, L) ->
+ %~ ========
+ %~ sendRoutingInfo
+ %~ ========
+ io:format("~n\e[93;1m# Testing sendRoutingInfo...\n\e[39;49;0m"),
+ send_tcap(L, Gts, {?SCCP_SSN_MSC, ?SCCP_SSN_HLR}, map_msgs:create_sendRoutingInfo(L#loop_dat.msisdn, L#loop_dat.gt_local)),
+ receive
+ {sccp, {primitive, 'N', 'UNITDATA', indication, Data}} ->
+ case decode_tcap(Data) of
+ {ok, Results} ->
+ io:format("\e[97;1mGot answer for sendRoutingInfo~n~w~n\e[39;49;0m", [Results]),
+ case Results of
+ [{basicROS, {returnError, {_, {present, Present}, {local, Local}, _}}}] ->
+ io:format("\e[91;1mReceived Error: Present ~w, Local ~w~n\e[39;49;0m", [Present, Local]);
+ [{basicROS,
+ {returnResult,
+ {'MapSpecificPDUs_end_components_SEQOF_basicROS_returnResult',
+ {present,1},{'MapSpecificPDUs_end_components_SEQOF_basicROS_returnResult_result',
+ {local,22},_}}}}|_] ->
+ io:format("\e[91;1mReceived SendRoutingInfoRes~n\e[39;49;0m");
+ _ ->
+ io:format("\e[92;1mNo Error.~n\e[39;49;0m")
+ end;
+ _->
+ io:format("\e[91;1mError decoding SendRoutingInfo\n\e[39;49;0m")
+ end;
+ _->
+ io:format("\e[91;1mError no data received for SendRoutingInfo\n\e[39;49;0m")
+ after 2000 ->
+ io:format("\e[91;1mError timeout on receiving SendRoutingInfo\n\e[39;49;0m")
+ end,
+ L.
+
+test_srifs(Gts, L) ->
+ %~ ========
+ %~ sendRoutingInfoForSM
+ %~ ========
+ io:format("~n\e[93;1m# Testing sendRoutingInfoForSM...\n\e[39;49;0m"),
+ send_tcap(L, Gts, {?SCCP_SSN_MSC, ?SCCP_SSN_HLR}, map_msgs:create_sendRoutingInfoForSM(L#loop_dat.msisdn, L#loop_dat.scenter)),
+ receive
+ {sccp, {primitive, 'N', 'UNITDATA', indication, Data}} ->
+ case decode_tcap(Data) of
+ {ok, Results} ->
+ io:format("\e[97;1mGot answer for sendRoutingInfoForSM\n~w\n\e[39;49;0m", [Results]),
+ case Results of
+ [{basicROS, {returnError, {_, {present, Present}, {local, Local}, _}}}|_] ->
+ case {Present, Local} of
+ {1, 6} ->
+ io:format("\e[91;1mSubscriber is absent~n\e[39;49;0m");
+ _ ->
+ io:format("\e[92;1mReceived Error: Present ~w, Local ~w~n\e[39;49;0m", [Present, Local])
+ end;
+ _ ->
+ io:format("\e[92;1mNo Error.~n\e[39;49;0m")
+ end;
+ _->
+ io:format("\e[91;1mError decoding SendRoutingInfoForSM\n\e[39;49;0m")
+ end;
+ _->
+ io:format("\e[91;1mError no data received for SendRoutingInfoForSM\n\e[39;49;0m")
+ after 2000 ->
+ io:format("\e[91;1mError timeout on receiving SendRoutingInfoForSM\n\e[39;49;0m")
+ end,
+ L.
+
+test_si(Gts, L) ->
+ %~ ========
+ %~ sendImsi
+ %~ ========
+ io:format("~n\e[93;1m# Testing sendImsi...\n\e[39;49;0m"),
+ send_tcap(L, Gts, {?SCCP_SSN_MSC, ?SCCP_SSN_HLR}, map_msgs:create_sendImsi(L#loop_dat.msisdn)),
+ receive
+ {sccp, {primitive, 'N', 'UNITDATA', indication, Data}} ->
+ case decode_tcap(Data) of
+ {ok, Results} ->
+ io:format("\e[97;1mGot answer for sendImsi\n~w\n\e[39;49;0m", [Results]),
+ case Results of
+ [{basicROS, {returnError, {_, {present, Present}, {local, Local}, _}}}] ->
+ io:format("\e[91;1mReceived Error: Present ~w, Local ~w~n\e[39;49;0m", [Present, Local]),
+ L;
+ [{basicROS,
+ {returnResult,
+ {'MapSpecificPDUs_end_components_SEQOF_basicROS_returnResult',
+ {present,1},
+ {'MapSpecificPDUs_end_components_SEQOF_basicROS_returnResult_result',
+ {local,58},
+ Imsi }}}}|_] ->
+ io:format("\e[91;1mReceived IMSI ~w~n\e[39;49;0m", [Imsi]),
+ L#loop_dat{imsi = Imsi}
+ end;
+ _->
+ io:format("\e[91;1mError decoding sendImsi\n\e[39;49;0m"),
+ L
+ end;
+ _->
+ io:format("\e[91;1mError no data received for sendImsi\n\e[39;49;0m"),
+ L
+ after 2000 ->
+ io:format("\e[91;1mError timeout on receiving sendImsi\n\e[39;49;0m"),
+ L
+ end.
+
+test_sai(Gts, L, Nr) ->
+ %~ ========
+ %~ sendAuthenticationInfo
+ %~ ========
+ io:format("~n\e[93;1m# Testing sendAuthenticationInfo...\n\e[39;49;0m"),
+ send_tcap(L, Gts, {?SCCP_SSN_MSC, ?SCCP_SSN_HLR}, map_msgs:create_sendAuthenticationInfo(L#loop_dat.imsi, Nr)),
+ receive
+ {sccp, {primitive, 'N', 'UNITDATA', indication, Data}} ->
+ case decode_tcap(Data) of
+ {ok, Results} ->
+ io:format("\e[97;1mGot answer for sendAuthenticationInfo\n~w\n\e[39;49;0m", [Results]),
+ case Results of
+ [{basicROS, {returnError, {_, {present, Present}, {local, Local}, _}}}] ->
+ io:format("\e[91;1mReceived Error: Present ~w, Local ~w~n\e[39;49;0m", [Present, Local]);
+ [{basicROS, {reject, _}}|_] ->
+ if
+ Nr>5 -> io:format("\e[92;1mAsked for ~w (>5) vectors, got rejected~n\e[39;49;0m", [Nr]);
+ Nr<6 -> io:format("\e[91;1mAsked for ~w (<=5) vectors, got rejected~n\e[39;49;0m", [Nr])
+ end;
+ [{basicROS,
+ {returnResult,
+ {'MapSpecificPDUs_end_components_SEQOF_basicROS_returnResult',
+ {present,1}, {'MapSpecificPDUs_end_components_SEQOF_basicROS_returnResult_result',
+ {local,56}, {'SendAuthenticationInfoRes',
+ {quintupletList,ResList},_,_}}}}}|_] ->
+ NrRes = length(ResList),
+ if
+ Nr>5 ->
+ if
+ NrRes>5 -> io:format("\e[91;1mAsked for ~w vectors, got ~w (>5) result vectors~n\e[39;49;0m", [Nr,NrRes])
+ end;
+ Nr<6 ->
+ if
+ NrRes==Nr -> io:format("\e[92;1mAsked for ~w vectors, got ~w (=~w) result vectors~n\e[39;49;0m", [Nr,NrRes,Nr]);
+ true -> io:format("\e[93;1mAsked for ~w vectors, got ~w (!=~w) result vectors~n\e[39;49;0m", [Nr,NrRes,Nr])
+ end
+ end;
+ _ ->
+ io:format("\e[92;1mNo Error.~n\e[39;49;0m")
+ end;
+ _->
+ io:format("\e[91;1mError decoding sendAuthenticationInfo\n\e[39;49;0m")
+ end;
+ _->
+ io:format("\e[91;1mError no data received for sendAuthenticationInfo\n\e[39;49;0m")
+ after 2000 ->
+ io:format("\e[91;1mError timeout on receiving sendAuthenticationInfo\n\e[39;49;0m")
+ end,
+ L.
+
+test_rss(Gts, L) ->
+ %~ ========
+ %~ registerSS
+ %~ ========
+ io:format("~n\e[93;1m# Testing registerSS...\n\e[39;49;0m"),
+ send_tcap(L, Gts, {?SCCP_SSN_MSC, ?SCCP_SSN_HLR}, map_msgs:create_registerSS(ss7test_helper:decode_imsi(L#loop_dat.imsi), L#loop_dat.gt_vlr, L#loop_dat.fnumber)),
+ receive
+ {sccp, {primitive, 'N', 'UNITDATA', indication, Data}} ->
+ case decode_tcap(Data) of
+ {ok, Results} ->
+ io:format("\e[97;1mGot answer for registerSS\n~w\n\e[39;49;0m", [Results]),
+ case Results of
+ [{basicROS, {returnError, {_, {present, Present}, {local, Local}, _}}}] ->
+ io:format("\e[91;1mReceived Error: Present ~w, Local ~w~n\e[39;49;0m", [Present, Local]);
+ [{basicROS,
+ {returnResult,
+ {'MapSpecificPDUs_end_components_SEQOF_basicROS_returnResult',
+ {present,1}, {'MapSpecificPDUs_end_components_SEQOF_basicROS_returnResult_result',
+ {local,10},_}}}}|_] ->
+ io:format("\e[91;1mReceived forwardingInfo, registerSS is working~n\e[39;49;0m");
+ _ ->
+ io:format("\e[92;1mNo Error.~n\e[39;49;0m")
+ end;
+ _->
+ io:format("\e[91;1mError decoding registerSS\n\e[39;49;0m")
+ end;
+ _->
+ io:format("\e[91;1mError no data received for registerSS\n\e[39;49;0m")
+ after 2000 ->
+ io:format("\e[91;1mError timeout on receiving registerSS\n\e[39;49;0m")
+ end,
+ L.
+
+test_ess(Gts, L) ->
+ %~ ========
+ %~ eraseSS
+ %~ ========
+ io:format("~n\e[93;1m# Testing eraseSS...\n\e[39;49;0m"),
+ send_tcap(L, Gts, {?SCCP_SSN_MSC, ?SCCP_SSN_HLR}, map_msgs:create_eraseSS(ss7test_helper:decode_imsi(L#loop_dat.imsi), L#loop_dat.gt_vlr)),
+ receive
+ {sccp, {primitive, 'N', 'UNITDATA', indication, Data}} ->
+ case decode_tcap(Data) of
+ {ok, Results} ->
+ io:format("\e[97;1mGot answer for eraseSS\n~w\n\e[39;49;0m", [Results]),
+ case Results of
+ [{basicROS, {returnError, {_, {present, Present}, {local, Local}, _}}}] ->
+ io:format("\e[91;1mReceived Error: Present ~w, Local ~w~n\e[39;49;0m", [Present, Local]);
+ [{basicROS,
+ {returnResult,
+ {'MapSpecificPDUs_end_components_SEQOF_basicROS_returnResult',
+ {present,1}, {'MapSpecificPDUs_end_components_SEQOF_basicROS_returnResult_result',
+ {local,11},_}}}}|_] ->
+ io:format("\e[91;1mReceived forwardingInfo, eraseSS is working~n\e[39;49;0m");
+ _ ->
+ io:format("\e[92;1mNo Error.~n\e[39;49;0m")
+ end;
+ _->
+ io:format("\e[91;1mError decoding eraseSS\n\e[39;49;0m")
+ end;
+ _->
+ io:format("\e[91;1mError no data received for eraseSS\n\e[39;49;0m")
+ after 2000 ->
+ io:format("\e[91;1mError timeout on receiving eraseSS\n\e[39;49;0m")
+ end,
+ L.
+
+test_ul(Gts, L) ->
+ %~ ========
+ %~ updateLocation
+ %~ ========
+ io:format("~n\e[93;1m# Testing updateLocation...\n\e[39;49;0m"),
+ send_tcap(L, Gts, {?SCCP_SSN_MSC, ?SCCP_SSN_HLR}, map_msgs:create_updateLocation(L#loop_dat.imsi, L#loop_dat.gt_local, L#loop_dat.gt_local)),
+ receive
+ {sccp, {primitive, 'N', 'UNITDATA', indication, Data}} ->
+ case decode_tcap(Data) of
+ {ok, Results} ->
+ io:format("\e[97;1mGot answer for updateLocation\n~w\n\e[39;49;0m", [Results]),
+ case Results of
+ [{basicROS, {returnError, {_, {present, Present}, {local, Local}, _}}}] ->
+ io:format("\e[91;1mReceived Error: Present ~w, Local ~w~n\e[39;49;0m", [Present, Local]);
+ [{basicROS,{invoke,{'MapSpecificPDUs_continue_components_SEQOF_basicROS_invoke',{present,2},asn1_NOVALUE,{local,7},_}}}|_] ->
+ io:format("\e[91;1mReceived insertSubscriberData, updateLocation is working~n\e[39;49;0m");
+ _ ->
+ io:format("\e[92;1mNo Error.~n\e[39;49;0m")
+ end;
+ _->
+ io:format("\e[91;1mError decoding updateLocation\n\e[39;49;0m")
+ end;
+ _->
+ io:format("\e[91;1mError no data received for updateLocation\n\e[39;49;0m")
+ after 2000 ->
+ io:format("\e[91;1mError timeout on receiving updateLocation\n\e[39;49;0m")
+ end,
+ L.
+
+test_ati(Gts, L) ->
+ %~ ========
+ %~ anyTimeInterrogation
+ %~ ========
+ io:format("~n\e[93;1m# Testing anyTimeInterrogation...\n\e[39;49;0m"),
+ send_tcap(L, Gts, {?SCCP_SSN_MSC, ?SCCP_SSN_HLR}, map_msgs:create_anyTimeInerrogation(L#loop_dat.imsi, L#loop_dat.gt_local)),
+ receive
+ {sccp, {primitive, 'N', 'UNITDATA', indication, Data}} ->
+ case decode_tcap(Data) of
+ {ok, Results} ->
+ io:format("\e[97;1mGot answer for anyTimeInerrogation\n~w\n\e[39;49;0m", [Results]),
+ case Results of
+ [{basicROS, {returnError, {_, {present, Present}, {local, Local}, _}}}] ->
+ case {Present, Local} of
+ {1, 49} ->
+ io:format("\e[92;1manyTimeInterrogation is forbidden~n\e[39;49;0m");
+ _ ->
+ io:format("\e[91;1mReceived Error: Present ~w, Local ~w~n\e[39;49;0m", [Present, Local])
+ end;
+ _ ->
+ io:format("\e[92;1mNo Error.~n\e[39;49;0m")
+ end;
+ _->
+ io:format("\e[91;1mError decoding anyTimeInerrogation\n\e[39;49;0m")
+ end;
+ _->
+ io:format("\e[91;1mError no data received for anyTimeInerrogation\n\e[39;49;0m")
+ after 2000 ->
+ io:format("\e[91;1mError timeout on receiving anyTimeInerrogation\n\e[39;49;0m")
+ end,
+ L.
+
+test_pms(Gts, L) ->
+ %~ ========
+ %~ purgeMS
+ %~ ========
+ io:format("~n\e[93;1m# Testing purgeMS...\n\e[39;49;0m"),
+ send_tcap(L, Gts, {?SCCP_SSN_MSC, ?SCCP_SSN_HLR}, map_msgs:create_purgeMs(L#loop_dat.imsi, L#loop_dat.gt_local)),
+ receive
+ {sccp, {primitive, 'N', 'UNITDATA', indication, Data}} ->
+ case decode_tcap(Data) of
+ {ok, Results} ->
+ io:format("\e[97;1mGot answer for purgeMs\n~w\n\e[39;49;0m", [Results]),
+ case Results of
+ [{basicROS, {returnError, {_, {present, Present}, {local, Local}, _}}}] ->
+ io:format("\e[91;1mReceived Error: Present ~w, Local ~w~n\e[39;49;0m", [Present, Local]);
+ [{basicROS,
+ {returnResult,
+ {'MapSpecificPDUs_end_components_SEQOF_basicROS_returnResult',
+ {present,1},{'MapSpecificPDUs_end_components_SEQOF_basicROS_returnResult_result',
+ {local,67},_}}}}|_] ->
+ io:format("\e[91;1mReceived purgeMS-Res, purgeMS is working~n\e[39;49;0m");
+ _ ->
+ io:format("\e[92;1mNo Error.~n\e[39;49;0m")
+ end;
+ _->
+ io:format("\e[91;1mError decoding purgeMs\n\e[39;49;0m")
+ end;
+ _->
+ io:format("\e[91;1mError no data received for purgeMs\n\e[39;49;0m")
+ after 2000 ->
+ io:format("\e[91;1mError timeout on receiving purgeMs\n\e[39;49;0m")
+ end,
+ L.
+
+scrc_tx_to_mtp(Prim, Args) ->
+ M3uaPid = Args,
+ gen_fsm:send_event(M3uaPid, Prim).
+
+send_tcap(L, {Lgt, Rgt}, {Sssn, Dssn}, PDU) ->
+ CallingP = #sccp_addr{ssn = Sssn, point_code = L#loop_dat.local_pc,
+ global_title = #global_title{gti = ?SCCP_GTI_TT_NP_ENC_NAT, trans_type = ?SCCP_GTI_NO_GT,
+ encoding = 0, numbering_plan = 1, phone_number = Lgt,
+ nature_of_addr_ind = ?ISUP_ADDR_NAT_INTERNATIONAL}},
+ CalledP = #sccp_addr{ssn = Dssn, point_code = L#loop_dat.remote_pc,
+ global_title = #global_title{gti = ?SCCP_GTI_TT_NP_ENC_NAT, trans_type = ?SCCP_GTI_NO_GT,
+ encoding = 0, numbering_plan = 1, phone_number = Rgt,
+ nature_of_addr_ind = ?ISUP_ADDR_NAT_INTERNATIONAL}},
+ Opts = [{protocol_class, {1, 8}}, {called_party_addr, CalledP},
+ {calling_party_addr, CallingP}, {user_data, PDU}],
+ gen_fsm:send_event(L#loop_dat.scrc_pid, osmo_util:make_prim('N','UNITDATA',request,Opts)).
+
+decode_tcap(Data) ->
+ {sccp_msg, _, ProtData} = Data,
+ {user_data, UserData} = lists:keyfind(user_data, 1, ProtData),
+ {ok, TcapData} = map:decode('MapSpecificPDUs', UserData),
+ case TcapData of
+ {'end', {'MapSpecificPDUs_end', _Transaction, % <<1,1,0,0>>,
+ {'EXTERNAL',
+ {0,0,17,773,1,1,1},
+ _,_, %asn1_NOVALUE,asn1_NOVALUE,
+ _Dialog}, Results}} -> {};
+ {continue, {'MapSpecificPDUs_continue', _STransaction, _Transaction,
+ {'EXTERNAL',
+ {0,0,17,773,1,1,1},
+ _,_, %asn1_NOVALUE,asn1_NOVALUE,
+ _Dialog}, Results}} -> {}
+ end,
+ {ok, Results}.
diff --git a/src/ss7test_app.hrl b/src/ss7test_app.hrl
new file mode 100644
index 0000000..49703c0
--- /dev/null
+++ b/src/ss7test_app.hrl
@@ -0,0 +1,6 @@
+% Number encoding Definitions
+
+-define(NUMBER_EXTENSION_NONE, 1).
+-define(NUMBER_NATURE_INTERNATIONAL, 1).
+-define(NUMBER_PLAN_ISDN, 1).
+-define(NUMBER_LAND_MOBILE, 6).
diff --git a/src/ss7test_helper.erl b/src/ss7test_helper.erl
new file mode 100644
index 0000000..f6821b0
--- /dev/null
+++ b/src/ss7test_helper.erl
@@ -0,0 +1,48 @@
+-module(ss7test_helper).
+-author('Daniel Mende <mail@c0decafe.de>').
+
+-export([encode_phonenumber/4, encode_msisdn/4,
+ decode_imsi/1,
+ remove_firstN/2,
+ tup2bin/1]).
+
+decode_imsi(<<>>) ->
+ [];
+decode_imsi(Imsi) ->
+ <<First:4,Second:4,Rest/bits>> = Imsi,
+ Dec = decode_imsi(Rest),
+ if
+ First==15 -> lists:append([Second], Dec);
+ true -> lists:append([Second,First], Dec)
+ end.
+
+%% ===================================================================
+%% Phone Number helper
+%% ===================================================================
+
+encode_phonenumber(Extension, NatureOfNumber, NumberPlan, Number) ->
+ {EncNumber, Digits} = encode_phonenumber(Number),
+ <<Digits:8, Extension:1, NatureOfNumber:3, NumberPlan:4, EncNumber/binary>>.
+encode_phonenumber([First,Second|Tail]) ->
+ {EncNumber, Digits} = encode_phonenumber(Tail),
+ {<<Second:4, First:4, EncNumber/binary>>, Digits + 2};
+encode_phonenumber([Last]) ->
+ {<<15:4, Last:4>>, 1};
+encode_phonenumber([]) ->
+ {<<>>, 0}.
+
+encode_msisdn(Extension, NatureOfNumber, NumberPlan, Number) ->
+ {EncNumber, _} = encode_phonenumber(Number),
+ <<Extension:1, NatureOfNumber:3, NumberPlan:4, EncNumber/binary>>.
+
+%% ===================================================================
+%% List helper
+%% ===================================================================
+
+remove_firstN(_, []) -> [];
+remove_firstN(1, [_|T]) -> T;
+remove_firstN(N, [_|T]) -> remove_firstN(N-1, T).
+
+
+tup2bin(Tupel) ->
+ binary:list_to_bin([element(I,Tupel) || I <- lists:seq(1,tuple_size(Tupel))]).
diff --git a/src/ss7test_sup.erl b/src/ss7test_sup.erl
new file mode 100644
index 0000000..1b47715
--- /dev/null
+++ b/src/ss7test_sup.erl
@@ -0,0 +1,27 @@
+-module(ss7test_sup).
+
+-behaviour(supervisor).
+
+%% API
+-export([start_link/0]).
+
+%% Supervisor callbacks
+-export([init/1]).
+
+%% Helper macro for declaring children of supervisor
+-define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}).
+
+%% ===================================================================
+%% API functions
+%% ===================================================================
+
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+%% ===================================================================
+%% Supervisor callbacks
+%% ===================================================================
+
+init([]) ->
+ {ok, { {one_for_one, 5, 10}, []} }.
+
diff --git a/ss7test.iml b/ss7test.iml
new file mode 100644
index 0000000..7626f17
--- /dev/null
+++ b/ss7test.iml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="ERLANG_MODULE" version="4">
+ <component name="FacetManager">
+ <facet type="erlang" name="Erlang">
+ <configuration />
+ </facet>
+ </component>
+ <component name="NewModuleRootManager" inherit-compiler-output="false">
+ <output url="file://$MODULE_DIR$/ebin" />
+ <output-test url="file://$MODULE_DIR$/.eunit" />
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="module" module-name="osmo_ss7" />
+ <orderEntry type="module" module-name="osmo_map" />
+ </component>
+</module> \ No newline at end of file