blob: 4005696cd098ca133fba44b43859182c5d22cc13 [file] [log] [blame]
Harald Welte3bf7cb62011-04-03 00:25:34 +02001% M3UA in accordance with RFC4666 (http://tools.ietf.org/html/rfc4666)
2
3% (C) 2011 by Harald Welte <laforge@gnumonks.org>
4%
5% All Rights Reserved
6%
7% This program is free software; you can redistribute it and/or modify
8% it under the terms of the GNU Affero General Public License as
9% published by the Free Software Foundation; either version 3 of the
10% License, or (at your option) any later version.
11%
12% This program is distributed in the hope that it will be useful,
13% but WITHOUT ANY WARRANTY; without even the implied warranty of
14% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15% GNU General Public License for more details.
16%
17% You should have received a copy of the GNU Affero General Public License
18% along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20-module(m3ua_core).
21-author('Harald Welte <laforge@gnumonks.org>').
22
23-include_lib("kernel/include/inet_sctp.hrl").
Harald Weltee393ea82011-04-04 16:00:06 +020024-include("osmo_util.hrl").
Harald Welte3bf7cb62011-04-03 00:25:34 +020025-include("sccp.hrl").
26-include("m3ua.hrl").
27
28-export([start_link/1]).
29
Harald Welteb2d3abf2011-04-04 11:26:11 +020030-export([init/1, terminate/3, code_change/4, handle_event/3, handle_info/3]).
Harald Welte3bf7cb62011-04-03 00:25:34 +020031
32% FSM states:
33-export([asp_down/2, asp_inactive/2, asp_active/2]).
34
35-define(T_ACK_TIMEOUT, 2*60*100).
36
37% Loop Data
38-record(m3ua_state, {
39 role, % asp | sgp
40 asp_state, % down, inactive, active
41 t_ack,
42 user_pid,
Harald Weltee393ea82011-04-04 16:00:06 +020043 user_ref,
Harald Welte3bf7cb62011-04-03 00:25:34 +020044 sctp_remote_ip,
45 sctp_remote_port,
Harald Welte8a0ab002011-04-03 22:16:12 +020046 sctp_local_port,
Harald Welte3bf7cb62011-04-03 00:25:34 +020047 sctp_sock,
48 sctp_assoc_id
49 }).
50
51start_link(InitOpts) ->
52 gen_fsm:start_link(?MODULE, InitOpts, [{debug, [trace]}]).
53
54reconnect_sctp(L = #m3ua_state{sctp_remote_ip = Ip, sctp_remote_port = Port, sctp_sock = Sock}) ->
55 io:format("SCTP Reconnect ~p:~p~n", [Ip, Port]),
Harald Welte8a0ab002011-04-03 22:16:12 +020056 InitMsg = #sctp_initmsg{num_ostreams = 2, max_instreams = 2},
Harald Welte3bf7cb62011-04-03 00:25:34 +020057 case gen_sctp:connect(Sock, Ip, Port, [{active, once}, {reuseaddr, true},
58 {sctp_initmsg, InitMsg}]) of
59 {ok, Assoc} ->
Harald Welte9544cab2011-04-04 17:03:23 +020060 send_prim_to_user(L, osmo_util:make_prim('M','SCTP_ESTABLISH',confirm)),
Harald Welte3bf7cb62011-04-03 00:25:34 +020061 L#m3ua_state{sctp_assoc_id = Assoc#sctp_assoc_change.assoc_id};
62 {error, Error } ->
Harald Welte6fc5b292011-04-04 10:38:30 +020063 io:format("SCTP Error ~p, reconnecting~n", [Error]),
Harald Welte3bf7cb62011-04-03 00:25:34 +020064 reconnect_sctp(L)
65 end.
66
67init(InitOpts) ->
Harald Welte8a0ab002011-04-03 22:16:12 +020068 OpenOptsBase = [{active, once}, {reuseaddr, true}],
69 LocalPort = proplists:get_value(sctp_local_port, InitOpts),
70 case LocalPort of
71 undefined ->
72 OpenOpts = OpenOptsBase;
73 _ ->
74 OpenOpts = OpenOptsBase ++ [{port, LocalPort}]
75 end,
76 {ok, SctpSock} = gen_sctp:open(OpenOpts),
Harald Welte3bf7cb62011-04-03 00:25:34 +020077 LoopDat = #m3ua_state{role = asp, sctp_sock = SctpSock,
78 user_pid = proplists:get_value(user_pid, InitOpts),
Harald Weltee393ea82011-04-04 16:00:06 +020079 user_ref = proplists:get_value(user_ref, InitOpts),
Harald Welte3bf7cb62011-04-03 00:25:34 +020080 sctp_remote_ip = proplists:get_value(sctp_remote_ip, InitOpts),
Harald Welte8a0ab002011-04-03 22:16:12 +020081 sctp_remote_port = proplists:get_value(sctp_remote_port, InitOpts),
82 sctp_local_port = LocalPort},
Harald Welte3bf7cb62011-04-03 00:25:34 +020083 LoopDat2 = reconnect_sctp(LoopDat),
84 {ok, asp_down, LoopDat2}.
85
Harald Welteb2d3abf2011-04-04 11:26:11 +020086terminate(Reason, _State, LoopDat) ->
87 io:format("Terminating ~p (Reason: ~p)~n", [?MODULE, Reason]),
88 gen_sctp:close(LoopDat#m3ua_state.sctp_sock).
89
90code_change(_OldVsn, StateName, StateData, _Extra) ->
91 {ok, StateName, StateData}.
92
Harald Welte3bf7cb62011-04-03 00:25:34 +020093% Helper function to send data to the SCTP peer
94send_sctp_to_peer(LoopDat, PktData) when is_binary(PktData) ->
95 #m3ua_state{sctp_sock = Sock, sctp_assoc_id = Assoc} = LoopDat,
96 SndRcvInfo = #sctp_sndrcvinfo{assoc_id = Assoc, ppid = 3, stream = 0},
97 gen_sctp:send(Sock, SndRcvInfo, PktData);
98
99% same as above, but for un-encoded #m3ua_msg{}
100send_sctp_to_peer(LoopDat, M3uaMsg) when is_record(M3uaMsg, m3ua_msg) ->
101 MsgBin = m3ua_codec:encode_m3ua_msg(M3uaMsg),
102 send_sctp_to_peer(LoopDat, MsgBin).
103
Harald Weltee393ea82011-04-04 16:00:06 +0200104
105send_prim_to_user(LoopDat, Prim) when is_record(LoopDat, m3ua_state), is_record(Prim, primitive) ->
106 #m3ua_state{user_pid = Pid, user_ref = Ref} = LoopDat,
107 Pid ! {m3ua, Ref, Prim}.
108
Harald Welte3bf7cb62011-04-03 00:25:34 +0200109% helper to send one of the up/down/act/inact management messages + start timer
110send_msg_start_tack(LoopDat, State, MsgClass, MsgType, Params) ->
111 % generate and send the respective message
112 Msg = #m3ua_msg{version = 1, msg_class = MsgClass, msg_type = MsgType, payload = Params},
113 send_sctp_to_peer(LoopDat, Msg),
114 % start T(ack) timer and wait for ASP_UP_ACK
Harald Weltee393ea82011-04-04 16:00:06 +0200115 timer:cancel(LoopDat#m3ua_state.t_ack),
Harald Welte6fc5b292011-04-04 10:38:30 +0200116 {ok, Tack} = timer:apply_after(?T_ACK_TIMEOUT, gen_fsm, send_event,
Harald Welte3bf7cb62011-04-03 00:25:34 +0200117 [self(), {timer_expired, t_ack, {MsgClass, MsgType, Params}}]),
118 {next_state, State, LoopDat#m3ua_state{t_ack = Tack}}.
119
120
Harald Welte3bf7cb62011-04-03 00:25:34 +0200121
Harald Welte6fc5b292011-04-04 10:38:30 +0200122handle_event(Event, State, LoopDat) ->
123 io:format("Unknown Event ~p in state ~p~n", [Event, State]),
124 {next_state, State, LoopDat}.
125
126
127
128handle_info({sctp, Socket, _RemoteIp, _RemotePort, {ANC, SAC}},
Harald Welte3bf7cb62011-04-03 00:25:34 +0200129 _State, LoopDat) when is_record(SAC, sctp_assoc_change) ->
130 io:format("SCTP Assoc Change ~p ~p~n", [ANC, SAC]),
Harald Welte6fc5b292011-04-04 10:38:30 +0200131 #sctp_assoc_change{state = SacState, outbound_streams = _OutStreams,
Harald Welteb2d3abf2011-04-04 11:26:11 +0200132 inbound_streams = _InStreams, assoc_id = _AssocId} = SAC,
Harald Welte3bf7cb62011-04-03 00:25:34 +0200133 case SacState of
134 comm_up ->
Harald Weltee393ea82011-04-04 16:00:06 +0200135 % primmitive to the user
136 send_prim_to_user(LoopDat, osmo_util:make_prim('M','SCTP_ESTABLISH',confirm)),
Harald Welte3bf7cb62011-04-03 00:25:34 +0200137 LoopDat2 = LoopDat;
138 comm_lost ->
Harald Weltee393ea82011-04-04 16:00:06 +0200139 send_prim_to_user(LoopDat, osmo_util:make_prim('M','SCTP_RELEASE',indication)),
Harald Welte3bf7cb62011-04-03 00:25:34 +0200140 LoopDat2 = reconnect_sctp(LoopDat);
141 addr_unreachable ->
142 LoopDat2 = reconnect_sctp(LoopDat)
143 end,
144 inet:setopts(Socket, [{active, once}]),
145 {next_state, asp_down, LoopDat2};
146
Harald Welte6fc5b292011-04-04 10:38:30 +0200147handle_info({sctp, Socket, RemoteIp, RemotePort, {[Anc], Data}}, State, LoopDat) ->
Harald Welte3bf7cb62011-04-03 00:25:34 +0200148 io:format("SCTP rx data: ~p ~p~n", [Anc, Data]),
Harald Weltee393ea82011-04-04 16:00:06 +0200149 % process incoming SCTP data
Harald Welte6fc5b292011-04-04 10:38:30 +0200150 if Socket == LoopDat#m3ua_state.sctp_sock,
151 RemoteIp == LoopDat#m3ua_state.sctp_remote_ip,
152 RemotePort == LoopDat#m3ua_state.sctp_remote_port,
153 3 == Anc#sctp_sndrcvinfo.ppid ->
154 Ret = rx_sctp(Anc, Data, State, LoopDat);
155 true ->
156 io:format("unknown SCTP: ~p ~p~n", [Anc, Data]),
157 Ret = {next_state, State, LoopDat}
158 end,
Harald Welte3bf7cb62011-04-03 00:25:34 +0200159 inet:setopts(Socket, [{active, once}]),
Harald Welte6fc5b292011-04-04 10:38:30 +0200160 Ret;
Harald Welte3bf7cb62011-04-03 00:25:34 +0200161
Harald Welte6fc5b292011-04-04 10:38:30 +0200162handle_info({sctp, Socket, RemoteIp, RemotePort, {_Anc, Data}}, _State, LoopDat)
Harald Welte3bf7cb62011-04-03 00:25:34 +0200163 when is_record(Data, sctp_shutdown_event) ->
164 io:format("SCTP remote ~p:~p shutdown~n", [RemoteIp, RemotePort]),
165 inet:setopts(Socket, [{active, once}]),
166 {next_state, asp_down, LoopDat}.
167
168
169
170asp_down(#primitive{subsystem = 'M', gen_name = 'ASP_UP',
Harald Welte6fc5b292011-04-04 10:38:30 +0200171 spec_name = request, parameters = _Params}, LoopDat) ->
172 send_msg_start_tack(LoopDat, asp_down, ?M3UA_MSGC_ASPSM, ?M3UA_MSGT_ASPSM_ASPUP, []);
Harald Welte3bf7cb62011-04-03 00:25:34 +0200173asp_down({timer_expired, t_ack, {?M3UA_MSGC_ASPSM, ?M3UA_MSGT_ASPSM_ASPUP, Params}}, LoopDat) ->
174 send_msg_start_tack(LoopDat, asp_down, ?M3UA_MSGC_ASPSM, ?M3UA_MSGT_ASPSM_ASPUP, Params);
175
176asp_down(#m3ua_msg{msg_class = ?M3UA_MSGC_ASPSM,
177 msg_type = ?M3UA_MSGT_ASPSM_ASPUP_ACK}, LoopDat) ->
Harald Welte6fc5b292011-04-04 10:38:30 +0200178 timer:cancel(LoopDat#m3ua_state.t_ack),
Harald Welte3bf7cb62011-04-03 00:25:34 +0200179 % transition into ASP_INACTIVE
Harald Weltee393ea82011-04-04 16:00:06 +0200180 send_prim_to_user(LoopDat, osmo_util:make_prim('M','ASP_UP',confirm)),
Harald Welte6fc5b292011-04-04 10:38:30 +0200181 {next_state, asp_inactive, LoopDat};
182
183asp_down(M3uaMsg, LoopDat) when is_record(M3uaMsg, m3ua_msg) ->
184 rx_m3ua(M3uaMsg, asp_down, LoopDat).
Harald Welte3bf7cb62011-04-03 00:25:34 +0200185
186
Harald Welte6fc5b292011-04-04 10:38:30 +0200187asp_inactive(#primitive{subsystem = 'M', gen_name = 'ASP_ACTIVE',
188 spec_name = request, parameters = _Params}, LoopDat) ->
189 send_msg_start_tack(LoopDat, asp_inactive, ?M3UA_MSGC_ASPTM, ?M3UA_MSGT_ASPTM_ASPAC,
190 [{?M3UA_IEI_TRAF_MODE_TYPE, <<0,0,0,1>>}]);
Harald Welte3bf7cb62011-04-03 00:25:34 +0200191
192asp_inactive({timer_expired, t_ack, {?M3UA_MSGC_ASPTM, ?M3UA_MSGT_ASPTM_ASPAC, Params}}, LoopDat) ->
193 send_msg_start_tack(LoopDat, asp_inactive, ?M3UA_MSGC_ASPTM, ?M3UA_MSGT_ASPTM_ASPAC, Params);
194
195asp_inactive(#primitive{subsystem = 'M', gen_name = 'ASP_DOWN',
Harald Welte6fc5b292011-04-04 10:38:30 +0200196 spec_name = request, parameters = _Params}, LoopDat) ->
197 send_msg_start_tack(LoopDat, asp_inactive, ?M3UA_MSGC_ASPSM, ?M3UA_MSGT_ASPSM_ASPDN, []);
Harald Welte3bf7cb62011-04-03 00:25:34 +0200198
199asp_inactive({timer_expired, t_ack, {?M3UA_MSGC_ASPSM, ?M3UA_MSGT_ASPSM_ASPDN, Params}}, LoopDat) ->
200 send_msg_start_tack(LoopDat, asp_inactive, ?M3UA_MSGC_ASPSM, ?M3UA_MSGT_ASPSM_ASPDN, Params);
201
202asp_inactive(#m3ua_msg{msg_class = ?M3UA_MSGC_ASPTM,
203 msg_type = ?M3UA_MSGT_ASPTM_ASPAC_ACK}, LoopDat) ->
Harald Welte6fc5b292011-04-04 10:38:30 +0200204 timer:cancel(LoopDat#m3ua_state.t_ack),
Harald Welte3bf7cb62011-04-03 00:25:34 +0200205 % transition into ASP_ACTIVE
Harald Weltee393ea82011-04-04 16:00:06 +0200206 % signal this to the user
207 send_prim_to_user(LoopDat, osmo_util:make_prim('M','ASP_ACTIVE',confirm)),
Harald Welte3bf7cb62011-04-03 00:25:34 +0200208 {next_state, asp_active, LoopDat};
209
210asp_inactive(#m3ua_msg{msg_class = ?M3UA_MSGC_ASPSM,
211 msg_type = ?M3UA_MSGT_ASPSM_ASPDN_ACK}, LoopDat) ->
Harald Welte6fc5b292011-04-04 10:38:30 +0200212 timer:cancel(LoopDat#m3ua_state.t_ack),
Harald Welte3bf7cb62011-04-03 00:25:34 +0200213 % transition into ASP_DOWN
Harald Weltee393ea82011-04-04 16:00:06 +0200214 % signal this to the user
215 send_prim_to_user(LoopDat, osmo_util:make_prim('M','ASP_DOWN',confirm)),
Harald Welte6fc5b292011-04-04 10:38:30 +0200216 {next_state, asp_down, LoopDat};
217
218asp_inactive(M3uaMsg, LoopDat) when is_record(M3uaMsg, m3ua_msg) ->
219 rx_m3ua(M3uaMsg, asp_inactive, LoopDat).
Harald Welte3bf7cb62011-04-03 00:25:34 +0200220
221
222
223asp_active(#m3ua_msg{msg_class = ?M3UA_MSGC_ASPSM,
224 msg_type = ?M3UA_MSGT_ASPSM_ASPDN_ACK}, LoopDat) ->
Harald Welte6fc5b292011-04-04 10:38:30 +0200225 timer:cancel(LoopDat#m3ua_state.t_ack),
Harald Welte3bf7cb62011-04-03 00:25:34 +0200226 % transition into ASP_DOWN
Harald Weltee393ea82011-04-04 16:00:06 +0200227 % signal this to the user
228 send_prim_to_user(LoopDat, osmo_util:make_prim('M','ASP_DOWN',confirm)),
Harald Welte3bf7cb62011-04-03 00:25:34 +0200229 {next_state, asp_down, LoopDat};
230
231asp_active(#m3ua_msg{msg_class = ?M3UA_MSGC_ASPTM,
232 msg_type = ?M3UA_MSGT_ASPTM_ASPIA_ACK}, LoopDat) ->
Harald Welte6fc5b292011-04-04 10:38:30 +0200233 timer:cancel(LoopDat#m3ua_state.t_ack),
Harald Welte3bf7cb62011-04-03 00:25:34 +0200234 % transition into ASP_INACTIVE
Harald Weltee393ea82011-04-04 16:00:06 +0200235 % signal this to the user
236 send_prim_to_user(LoopDat, osmo_util:make_prim('M','ASP_INACTIVE',confirm)),
Harald Welte3bf7cb62011-04-03 00:25:34 +0200237 {next_state, asp_inactive, LoopDat};
238
239asp_active(#primitive{subsystem = 'M', gen_name = 'ASP_DOWN',
Harald Welte6fc5b292011-04-04 10:38:30 +0200240 spec_name = request, parameters = _Params}, LoopDat) ->
241 send_msg_start_tack(LoopDat, asp_active, ?M3UA_MSGC_ASPSM, ?M3UA_MSGT_ASPSM_ASPDN, []);
Harald Welte3bf7cb62011-04-03 00:25:34 +0200242
243asp_active({timer_expired, t_ack, {?M3UA_MSGC_ASPSM, ?M3UA_MSGT_ASPSM_ASPDN, Params}}, LoopDat) ->
244 send_msg_start_tack(LoopDat, asp_active, ?M3UA_MSGC_ASPSM, ?M3UA_MSGT_ASPSM_ASPDN, Params);
245
246asp_active(#primitive{subsystem = 'M', gen_name = 'ASP_INACTIVE',
Harald Welte6fc5b292011-04-04 10:38:30 +0200247 spec_name = request, parameters = _Params}, LoopDat) ->
248 send_msg_start_tack(LoopDat, asp_active, ?M3UA_MSGC_ASPTM, ?M3UA_MSGT_ASPTM_ASPIA, []);
Harald Welte3bf7cb62011-04-03 00:25:34 +0200249
250asp_active({timer_expired, t_ack, {?M3UA_MSGC_ASPTM, ?M3UA_MSGT_ASPTM_ASPIA, Params}}, LoopDat) ->
251 send_msg_start_tack(LoopDat, asp_active, ?M3UA_MSGC_ASPTM, ?M3UA_MSGT_ASPTM_ASPIA, Params);
252
253asp_active(#primitive{subsystem = 'MTP', gen_name = 'TRANSFER',
254 spec_name = request, parameters = Params}, LoopDat) ->
255 % Send message to remote peer
256 OptList = [{?M3UA_IEI_PROTOCOL_DATA, Params}],
257 Msg = #m3ua_msg{version = 1, msg_class = ?M3UA_MSGC_TRANSFER,
258 msg_type = ?M3UA_MSGT_XFR_DATA,
259 payload = OptList},
260 send_sctp_to_peer(LoopDat, Msg),
261 {next_state, asp_active, LoopDat};
262asp_active(#m3ua_msg{version = 1, msg_class = ?M3UA_MSGC_TRANSFER,
263 msg_type = ?M3UA_MSGT_XFR_DATA, payload = Params}, LoopDat) ->
Harald Weltee393ea82011-04-04 16:00:06 +0200264 % Send primitive to the user
265 Mtp3 = proplists:get_value(?M3UA_IEI_PROTOCOL_DATA, Params),
266 send_prim_to_user(LoopDat, osmo_util:make_prim('M','TRANSFER',indication,[Mtp3])),
Harald Welte6fc5b292011-04-04 10:38:30 +0200267 {next_state, asp_active, LoopDat};
268asp_active(#m3ua_msg{msg_class = ?M3UA_MSGC_ASPTM,
269 msg_type = ?M3UA_MSGT_ASPTM_ASPIA_ACK}, LoopDat) ->
270 timer:cancel(LoopDat#m3ua_state.t_ack),
271 % transition to ASP_INACTIVE
272 {next_state, asp_inactive, LoopDat};
273
Harald Welte6fc5b292011-04-04 10:38:30 +0200274asp_active(M3uaMsg, LoopDat) when is_record(M3uaMsg, m3ua_msg) ->
275 rx_m3ua(M3uaMsg, asp_active, LoopDat).
276
277
Harald Weltee393ea82011-04-04 16:00:06 +0200278
Harald Welteb2d3abf2011-04-04 11:26:11 +0200279rx_sctp(_Anc, Data, State, LoopDat) ->
Harald Welte6fc5b292011-04-04 10:38:30 +0200280 M3uaMsg = m3ua_codec:parse_m3ua_msg(Data),
281 gen_fsm:send_event(self(), M3uaMsg),
282 {next_state, State, LoopDat}.
283
284
Harald Weltee393ea82011-04-04 16:00:06 +0200285
Harald Welte6fc5b292011-04-04 10:38:30 +0200286rx_m3ua(Msg = #m3ua_msg{version = 1, msg_class = ?M3UA_MSGC_MGMT,
287 msg_type = ?M3UA_MSGT_MGMT_NTFY}, State, LoopDat) ->
Harald Weltee393ea82011-04-04 16:00:06 +0200288 send_prim_to_user(LoopDat, osmo_util:make_prim('M','NOTIFY',indication,[Msg])),
Harald Welte6fc5b292011-04-04 10:38:30 +0200289 {next_state, State, LoopDat};
290
291rx_m3ua(Msg = #m3ua_msg{version = 1, msg_class = ?M3UA_MSGC_ASPSM,
292 msg_type = ?M3UA_MSGT_ASPSM_BEAT}, State, LoopDat) ->
293 % Send BEAT_ACK using the same payload as the BEAT msg
Harald Welte6fc5b292011-04-04 10:38:30 +0200294 send_sctp_to_peer(LoopDat, Msg#m3ua_msg{msg_type = ?M3UA_MSGT_ASPSM_BEAT_ACK}),
295 {next_state, State, LoopDat};
296
Harald Weltee393ea82011-04-04 16:00:06 +0200297rx_m3ua(Msg = #m3ua_msg{version = 1, msg_class = ?M3UA_MSGC_MGMT,
298 msg_type = ?M3UA_MSGT_MGMT_ERR}, State, LoopDat) ->
299 send_prim_to_user(LoopDat, osmo_util:make_prim('M','ERROR',indication,[Msg])),
300 {next_state, State, LoopDat};
301
302rx_m3ua(Msg = #m3ua_msg{version = 1, msg_class = ?M3UA_MSGC_SSNM,
303 msg_type = MsgType, payload = Params}, State, LoopDat) ->
304 Mtp = map_ssnm_to_mtp_prim(MsgType),
305 send_prim_to_user(LoopDat, Mtp),
306 {next_state, State, LoopDat};
307
Harald Welte6fc5b292011-04-04 10:38:30 +0200308rx_m3ua(Msg = #m3ua_msg{}, State, LoopDat) ->
309 io:format("M3UA Unknown messge ~p in state ~p~n", [Msg, State]),
310 {next_state, State, LoopDat}.
Harald Weltee393ea82011-04-04 16:00:06 +0200311
312map_ssnm_to_mtp_prim(MsgType) ->
313 Mtp = #primitive{subsystem = 'MTP', spec_name = indiciation},
314 case MsgType of
315 ?M3UA_MSGT_SSNM_DUNA -> Mtp#primitive{gen_name = 'PAUSE'};
316 ?M3UA_MSGT_SSNM_DAVA -> Mtp#primitive{gen_name = 'RESUME'};
317 ?M3UA_MSGT_SSNM_SCON -> Mtp#primitive{gen_name = 'STATUS'};
318 ?M3UA_MSGT_SSNM_DUPU -> Mtp#primitive{gen_name = 'STATUS'}
319 end.