blob: 3979ad4eb4b356b7d0c288d51f3a5e3627331bdc [file] [log] [blame]
Harald Welte26bdef22012-01-16 22:22:17 +01001% M2PA in accordance with RFC4165 (http://tools.ietf.org/html/rfc4665)
2
3% (C) 2011-2012 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(sctp_m2pa).
21-author('Harald Welte <laforge@gnumonks.org>').
22-behaviour(sctp_core).
23
24-include_lib("kernel/include/inet_sctp.hrl").
25-include("osmo_util.hrl").
26-include("m2pa.hrl").
27
28-export([init/1, terminate/3, code_change/4, handle_event/3, handle_info/3]).
29
Harald Welte91b79652012-01-17 10:12:34 +010030-export([rx_sctp/4, mtp_xfer/2, state_change/3, prim_up/3]).
Harald Welte26bdef22012-01-16 22:22:17 +010031
32-record(m2pa_state, {
33 last_bsn_received,
34 last_fsn_sent,
35 lsc_pid,
36 iac_pid
37 }).
38
39%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
40% gen_fsm callbacks
41%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
42
43init(_InitOpts) ->
44 % start MTP2 IAC FSM pointing LSC, AERM and TXC to us
45 {ok, Lsc} = gen_fsm:start_link(mtp2_lsc, [self(), self(), self(),self()], [{debug, [trace]}]),
46 {ok, Iac} = gen_fsm:sync_send_event(Lsc, get_iac_pid),
47 gen_fsm:send_event(Lsc, power_on),
48 {ok, #m2pa_state{last_bsn_received=16#ffffff, last_fsn_sent=16#ffffff,
49 lsc_pid=Lsc, iac_pid=Iac}}.
50
51terminate(Reason, _State, _LoopDat) ->
52 io:format("Terminating ~p (Reason ~p)~n", [?MODULE, Reason]),
53 ok.
54
55code_change(_OldVsn, _State, LoopDat, _Extra) ->
56 {ok, LoopDat}.
57
58handle_event(_Event, State, LoopDat) ->
59 {next_state, State, LoopDat}.
60
61handle_info({lsc_txc, What}, State, LoopDat) when
62 What == start; What == retrieval_request_and_fsnc ->
63 {next_state, State, LoopDat};
64handle_info({Who, What}, established, LoopDat) when Who == iac_txc; Who == lsc_txc ->
65 Ls = iac_to_ls(What),
66 send_linkstate(Ls, LoopDat),
67 {next_state, established, LoopDat};
68handle_info(_Info, State, LoopDat) ->
69 {next_state, State, LoopDat}.
70
71%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
72% sctp_core callbacks
73%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
74
Harald Welte91b79652012-01-17 10:12:34 +010075prim_up(Prim, State, LoopDat) ->
76 % default: forward all primitives to the user
77 {ok, Prim, LoopDat}.
78
79
Harald Welteb064a922012-01-19 23:18:34 +010080% sctp_core indicates that we have received some data...
Harald Welte26bdef22012-01-16 22:22:17 +010081rx_sctp(#sctp_sndrcvinfo{ppid = ?M2PA_PPID}, Data, State, LoopDat) ->
82 {ok, M2pa} = m2pa_codec:parse_msg(Data),
83 FsnRecv = M2pa#m2pa_msg.fwd_seq_nr,
84 % FIXME: check sequenc number linearity
85 case M2pa of
86 #m2pa_msg{msg_class = ?M2PA_CLASS_M2PA,
87 msg_type = ?M2PA_TYPE_USER} ->
88 Mtp3 = M2pa#m2pa_msg.mtp3,
Harald Welteb064a922012-01-19 23:18:34 +010089 LoopDat2 = LoopDat#m2pa_state{last_bsn_received = FsnRecv},
90 case Mtp3 of
91 undefined ->
92 ok;
93 _ ->
94 send_userdata_ack(LoopDat2)
95 end,
Harald Welte26bdef22012-01-16 22:22:17 +010096 Prim = osmo_util:make_prim('MTP','TRANSFER',indication, Mtp3),
Harald Welteb064a922012-01-19 23:18:34 +010097 {ok, Prim, LoopDat2};
Harald Welte26bdef22012-01-16 22:22:17 +010098 #m2pa_msg{msg_type = ?M2PA_TYPE_LINK} ->
99 handle_linkstate(M2pa, LoopDat),
100 {ignore, LoopDat};
101 _ ->
102 % do something with link related msgs
103 io:format("M2PA Unknown message ~p in state ~p~n", [M2pa, State]),
104 {ignore, State, LoopDat}
105 end.
106
107% MTP-TRANSFER.req has arrived at sctp_core, encapsulate+tx it
108mtp_xfer(Mtp3, LoopDat) ->
109 Fsn = inc_seq_nr(LoopDat#m2pa_state.last_fsn_sent),
110 M2pa = #m2pa_msg{msg_class = ?M2PA_CLASS_M2PA,
111 msg_type = ?M2PA_TYPE_USER,
112 fwd_seq_nr = Fsn,
113 back_seq_nr = LoopDat#m2pa_state.last_bsn_received,
114 mtp3 = Mtp3},
115 M2paBin = m2pa_codec:encode_msg(M2pa),
116 LoopDat2 = LoopDat#m2pa_state{last_fsn_sent = Fsn},
117 tx_sctp(?M2PA_STREAM_USER, M2paBin),
118 LoopDat2.
119
120state_change(_, established, LoopDat) ->
121 % emulate a 'start' from LSC
122 gen_fsm:send_event(LoopDat#m2pa_state.lsc_pid, start),
123 LoopDat;
124state_change(established, _, LoopDat) ->
125 gen_fsm:send_event(LoopDat#m2pa_state.lsc_pid, link_failure),
126 LoopDat;
127state_change(_, _, LoopDat) ->
128 LoopDat.
129
130
131%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
132% helper functions
133%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
134
135inc_seq_nr(SeqNr) when is_integer(SeqNr) ->
136 SeqNr + 1 rem 16#FFFFFF.
137
138handle_linkstate(M2pa, LoopDat) when is_record(M2pa, m2pa_msg) ->
139 Linkstate = proplists:get_value(link_state, M2pa#m2pa_msg.parameters),
140 LsMtp2 = ls_to_iac(Linkstate),
141 if LsMtp2 == fisu ->
142 gen_fsm:send_event(LoopDat#m2pa_state.lsc_pid, fisu_msu_received);
143 LsMtp2 == si_po ->
144 gen_fsm:send_event(LoopDat#m2pa_state.lsc_pid, LsMtp2);
145 LsMtp2 == si_n; LsMtp2 == si_e; LsMtp2 == si_o; LsMtp2 == si_os ->
Harald Welte70984972012-01-19 22:50:39 +0100146 gen_fsm:send_event(LoopDat#m2pa_state.lsc_pid, LsMtp2)
147 % IAC will receive the event as pass-through from LSC
148 %gen_fsm:send_event(LoopDat#m2pa_state.iac_pid, LsMtp2)
Harald Welte26bdef22012-01-16 22:22:17 +0100149 end.
150
151% convert M2PA link state to MTP2
152ls_to_iac(?M2PA_LS_OOS) ->
153 si_os;
154ls_to_iac(?M2PA_LS_ALIGNMENT) ->
155 si_o;
156ls_to_iac(?M2PA_LS_PROVING_NORMAL) ->
157 si_n;
158ls_to_iac(?M2PA_LS_PROVING_EMERG) ->
159 si_e;
160ls_to_iac(?M2PA_LS_READY) ->
161 fisu;
162ls_to_iac(?M2PA_LS_PROC_OUTAGE) ->
163 si_po;
164ls_to_iac(?M2PA_LS_PROC_RECOVERED) ->
165 fisu;
166ls_to_iac(?M2PA_LS_BUSY) ->
167 si_b.
168% FIXME: what about BUSY_ENDED?
169
170
171% convert MTP2 link state to M2PA
172iac_to_ls(si_os) ->
173 ?M2PA_LS_OOS;
174iac_to_ls(si_o) ->
175 ?M2PA_LS_ALIGNMENT;
176iac_to_ls(si_n) ->
177 ?M2PA_LS_PROVING_NORMAL;
178iac_to_ls(si_e) ->
179 ?M2PA_LS_PROVING_EMERG;
180iac_to_ls(fisu) ->
181 ?M2PA_LS_READY;
182iac_to_ls(msu) ->
183 ?M2PA_LS_READY;
184iac_to_ls(si_po) ->
185 ?M2PA_LS_PROC_OUTAGE;
186iac_to_ls(si_b) ->
187 ?M2PA_LS_BUSY.
188
189% Chapter 4.1.2 of RFC4165
190ls_stream(?M2PA_LS_PROC_OUTAGE) ->
191 1;
192ls_stream(?M2PA_LS_PROC_RECOVERED) ->
193 1;
194ls_stream(Foo) when is_integer(Foo) ->
195 0.
196
197send_linkstate(Ls, LoopDat) when is_integer(Ls) ->
198 Stream = ls_stream(Ls),
199 M2pa = #m2pa_msg{msg_class = ?M2PA_CLASS_M2PA,
200 msg_type = ?M2PA_TYPE_LINK,
201 fwd_seq_nr = LoopDat#m2pa_state.last_fsn_sent,
202 back_seq_nr = LoopDat#m2pa_state.last_bsn_received,
203 parameters = [{link_state, Ls}]},
204 M2paBin = m2pa_codec:encode_msg(M2pa),
205 tx_sctp(Stream, M2paBin),
206 LoopDat.
207
Harald Welteb064a922012-01-19 23:18:34 +0100208send_userdata_ack(LoopDat) ->
209 M2pa = #m2pa_msg{msg_class = ?M2PA_CLASS_M2PA,
210 msg_type = ?M2PA_TYPE_USER,
211 fwd_seq_nr = LoopDat#m2pa_state.last_fsn_sent,
212 back_seq_nr = LoopDat#m2pa_state.last_bsn_received},
213 M2paBin = m2pa_codec:encode_msg(M2pa),
214 tx_sctp(0, M2paBin).
215
Harald Welte26bdef22012-01-16 22:22:17 +0100216tx_sctp(Stream, Payload) when is_integer(Stream), is_binary(Payload) ->
217 Param = {Stream, ?M2PA_PPID, Payload},
218 % sent to 'ourselves' (behaviour master module)
219 gen_fsm:send_event(self(), osmo_util:make_prim('SCTP','TRANSFER',request,Param)).