mirror of https://github.com/sipwise/kamailio.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
271 lines
9.3 KiB
271 lines
9.3 KiB
/*
|
|
* 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
|
|
*/
|
|
inline 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
|
|
*/
|
|
inline 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;
|
|
|
|
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]", 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;
|
|
}
|
|
|
|
AAAFreeAVPList(&mscc_avp_list);
|
|
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
|
|
*/
|
|
inline 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("Recevied 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;
|
|
}
|
|
|
|
|