-
Notifications
You must be signed in to change notification settings - Fork 909
/
acctstatemachine.c
274 lines (255 loc) · 8.37 KB
/
acctstatemachine.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
/*
* acctstatemachine.c
*
* Created on: 03 Apr 2013
* Author: jaybeepee
*/
#include "acctstatemachine.h"
#include "diameter_ims.h"
#include "common.h"
/**
* update Granted Service Unit timers based on CCR
*/
void update_gsu_request_timers(cdp_cc_acc_session_t* session, AAAMessage* msg) {
AAA_AVP *avp;
avp = AAAFindMatchingAVP(msg, 0, AVP_Event_Timestamp, 0, 0);
if (avp && avp->data.len == 4) {
session->last_reservation_request_time = ntohl(*((uint32_t*)avp->data.s))-EPOCH_UNIX_TO_EPOCH_NTP;
}
}
/**
* update Granted Service Unit timers based on CCA, for onw we assume on one MSCC per session and only TIME based supported
*/
void update_gsu_response_timers(cdp_cc_acc_session_t* session, AAAMessage* msg) {
AAA_AVP *avp;
AAA_AVP_LIST mscc_avp_list;
AAA_AVP_LIST y;
AAA_AVP *z;
y.head = y.tail = 0;
avp = AAAFindMatchingAVP(msg, 0, AVP_Multiple_Services_Credit_Control, 0, 0);
if (!avp) {
LM_WARN("Trying to update GSU timers but there is no MSCC AVP in the CCA response\n");
return;
}
mscc_avp_list = AAAUngroupAVPS(avp->data);
AAA_AVP *mscc_avp = mscc_avp_list.head;
while (mscc_avp != NULL ) {
LM_DBG("MSCC AVP code is [%i] and data length is [%i]\n", mscc_avp->code, mscc_avp->data.len);
switch (mscc_avp->code) {
case AVP_Granted_Service_Unit:
y = AAAUngroupAVPS(mscc_avp->data);
z = y.head;
while (z) {
switch (z->code) {
case AVP_CC_Time:
session->reserved_units = get_4bytes(z->data.s);
break;
default:
LM_DBG("ignoring AVP in GSU group with code:[%d]\n", z->code);
}
z = z->next;
}
break;
case AVP_Validity_Time:
session->reserved_units_validity_time = get_4bytes(mscc_avp->data.s);
break;
case AVP_Final_Unit_Indication:
y = AAAUngroupAVPS(mscc_avp->data);
z = y.head;
while (z) {
switch (z->code) {
case AVP_Final_Unit_Action:
session->fua = get_4bytes(z->data.s);
break;
default:
LM_DBG("ignoring AVP in FUI group with code:[%d]\n", z->code);
}
z = z->next;
}
break;
}
mscc_avp = mscc_avp->next;
}
if (mscc_avp_list.head)
AAAFreeAVPList(&mscc_avp_list);
if (y.head)
AAAFreeAVPList(&y);
}
/**
* stateful client state machine
* \Note - should be called with a lock on the session and will unlock it - do not use it after!
* @param cc_acc - AAACCAccSession which uses this state machine
* @param ev - Event
* @param msg - AAAMessage
* @returns 0 if msg should be given to the upper layer 1 if not
*/
int cc_acc_client_stateful_sm_process(cdp_session_t* s, int event, AAAMessage* msg)
{
cdp_cc_acc_session_t* x;
int ret = 0;
int rc; //return code for responses
int record_type;
x = &(s->u.cc_acc);
LM_DBG("cc_acc_client_stateful_sm_process: processing CC App in state [%d] and event [%d]\n", x->state, event);
//first run session callbacks
if (s->cb) (s->cb)(event, s);
LM_DBG("finished callback of event %i\n", event);
switch (x->state) {
case ACC_CC_ST_IDLE:
switch (event) {
case ACC_CC_EV_SEND_REQ: //were sending a message - CCR
//assert this is an initial request. we can't move from IDLE with anything else
record_type = get_accounting_record_type(msg);
switch (record_type) {
case 2 /*START RECORD*/:
LM_DBG("sending CCR START record on session\n");
s->application_id = msg->applicationId;
s->u.cc_acc.state = ACC_CC_ST_PENDING_I;
//update our reservation and its timers... if they exist in CCR
update_gsu_request_timers(x, msg);
break;
default:
LM_ERR("Sending CCR with no/incorrect accounting record type AVP. In state IDLE\n");
break;
}
break;
default:
LM_ERR("Received unknown event [%d] in state [%d]\n", event, x->state);
break;
}
break;
case ACC_CC_ST_OPEN:
switch (event) {
case ACC_CC_EV_SEND_REQ: //were sending a message - CCR
//make sure it is either an update or a termination.
record_type = get_accounting_record_type(msg);
switch (record_type) {
case 3 /*UPDATE RECORD*/:
LM_DBG("sending CCR UPDATE record on session\n");
s->u.cc_acc.state = ACC_CC_ST_PENDING_U;
//update our reservation and its timers...
update_gsu_request_timers(x, msg);
break;
case 4: /*TERMINATE RECORD*/
LM_DBG("sending CCR TERMINATE record on session\n");
s->u.cc_acc.state = ACC_CC_ST_PENDING_T;
//update our reservation and its timers...
update_gsu_request_timers(x, msg);
break;
default:
LM_ERR("asked to send CCR with no/incorrect accounting record type AVP. In state IDLE\n");
break;
}
break;
case ACC_CC_EV_RSVN_WARNING:
//nothing we can do here, we have sent callback, client needs to send CCR Update
LM_DBG("Reservation close to expiring\n");
break;
default:
LM_ERR("Received unknown event [%d] in state [%d]\n", event, x->state);
break;
}
break;
case ACC_CC_ST_PENDING_I:
if (event == ACC_CC_EV_RECV_ANS && msg && !is_req(msg)) {
rc = get_result_code(msg);
if (rc >= 2000 && rc < 3000) {
event = ACC_CC_EV_RECV_ANS_SUCCESS;
} else {
event = ACC_CC_EV_RECV_ANS_UNSUCCESS;
}
}
switch (event) {
case ACC_CC_EV_RECV_ANS_SUCCESS:
x->state = ACC_CC_ST_OPEN;
LM_DBG("received success response for CCR START\n");
update_gsu_response_timers(x, msg);
break;
case ACC_CC_EV_RECV_ANS_UNSUCCESS:
//TODO: grant/terminate service callbacks to callback clients
LM_ERR("failed answer on CCR START\n");
x->state = ACC_CC_ST_DISCON;
break;
default:
LM_ERR("Received unknown event [%d] in state [%d]\n", event, x->state);
break;
}
break;
case ACC_CC_ST_PENDING_T:
if (event == ACC_CC_EV_RECV_ANS && msg && !is_req(msg)) {
rc = get_result_code(msg);
if (rc >= 2000 && rc < 3000) {
event = ACC_CC_EV_RECV_ANS_SUCCESS;
} else {
event = ACC_CC_EV_RECV_ANS_UNSUCCESS;
}
}
switch (event) {
case ACC_CC_EV_RECV_ANS_SUCCESS:
x->state = ACC_CC_ST_DISCON;
// update_gsu_response_timers(x, msg);
case ACC_CC_EV_RECV_ANS_UNSUCCESS:
x->state = ACC_CC_ST_DISCON;
default:
LM_DBG("Received event [%d] in state [%d] - cleaning up session regardless\n", event, x->state);
//have to leave session alone because our client app still has to be given this msg
x->discon_time = time(0);
// if (msg) AAAFreeMessage(&msg);
// cdp_session_cleanup(s, NULL);
// s = 0;
}
break;
case ACC_CC_ST_PENDING_U:
/**Richard added Aug 5 - there is a potential race condition where you may send a CCR-U immediately followed by CCR-T
* and then receive a CCA-T while in state ACC_CC_ST_PENDING_U (e.g. if update timer and dialog termination at same time)
* In this event you would incorrectly ignore the CCR-T
* Solution is to change state to change state to ACC_CC_ST_PENDING_T if CCR-T is sent while in this state */
if (event == ACC_CC_EV_SEND_REQ && msg && get_accounting_record_type(msg) == 4 /*TERMINATE RECORD*/) {
LM_ERR("Received CCR-T while in state ACC_CC_ST_PENDING_U, just going to change to ACC_CC_ST_PENDING_T\n");
s->u.cc_acc.state = ACC_CC_ST_PENDING_T;
//update our reservation and its timers...
update_gsu_request_timers(x, msg);
} else {
if (event == ACC_CC_EV_RECV_ANS && msg && !is_req(msg)) {
rc = get_result_code(msg);
if (rc >= 2000 && rc < 3000) {
event = ACC_CC_EV_RECV_ANS_SUCCESS;
} else {
event = ACC_CC_EV_RECV_ANS_UNSUCCESS;
}
}
switch (event) {
case ACC_CC_EV_RECV_ANS_SUCCESS:
x->state = ACC_CC_ST_OPEN;
LM_DBG("success CCA for UPDATE\n");
update_gsu_response_timers(x, msg);
break;
case ACC_CC_EV_RECV_ANS_UNSUCCESS:
//TODO: check whether we grant or terminate service to callback clients
x->state = ACC_CC_ST_DISCON;
LM_ERR("update failed... going back to IDLE/DISCON\n");
break;
default:
LM_ERR("Received unknown event [%d] in state [%d]\n", event, x->state);
break;
}
}
break;
case ACC_CC_ST_DISCON:
switch (event) {
case ACC_CC_EV_SESSION_STALE:
LM_DBG("stale session about to be cleared\n");
cdp_session_cleanup(s, msg);
s = 0;
break;
default:
LM_ERR("Received unknown event [%d] in state [%d]\n", event, x->state);
break;
}
break;
}
if (s) {
AAASessionsUnlock(s->hash);
}
return ret;
}