/*
* SNMPStats Module
* Copyright (C) 2006 SOMA Networks, INC.
* Written by: Jeffrey Magder (jmagder@somanetworks.com)
*
* This file is part of Kamailio, a free SIP server.
*
* Kamailio is free software; you can redistribute it and/or modify it
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version
*
* Kamailio is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*
*/

/*!
* \file
* \brief SNMP statistic module, interprocess buffer
* \author jmagder
*
* This file implements the interprocess buffer, used for marshalling data
* exchange from the usrloc module to the kamailioSIPRegUserTable,
* kamailioSIPContactTable, and indirectly the kamailioSIPRegUserLookupTable.
*
* Details on why the interprocess buffer is needed can be found in the comments
* at the top of interprocess_buffer.h
* \ingroup snmpstats
* - Module: \ref snmpstats
*/

#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>

#include "interprocess_buffer.h"
#include "snmpSIPContactTable.h"
#include "snmpSIPRegUserTable.h"
#include "hashTable.h"
#include "utilities.h"

#include "../usrloc/ul_callback.h"

/*!
* The hash table:
*
*    1) maps all aor's to snmp's UserIndex for help in deleting SNMP Rows.
*
*    2) maps a given aor to a contact list.
*/
hashSlot_t *hashTable = NULL;

/*! All interprocess communication is stored between these two declarations. */
interprocessBuffer_t *frontRegUserTableBuffer = NULL;
interprocessBuffer_t *endRegUserTableBuffer = NULL;

/*! This is to protect the potential racecondition in which a command is added to
* the buffer while it is being consumed */
gen_lock_t           *interprocessCBLock = NULL;

/*!
* This function takes an element of the interprocess buffer passed to it, and
* handles populating the respective user and contact tables with its contained
* data.
*/
static void executeInterprocessBufferCmd(interprocessBuffer_t *currentBuffer);

/*!
* Initialize shared memory used to buffer communication between the usrloc
* module and the SNMPStats module.  (Specifically, the user and contact tables)
*/
int initInterprocessBuffers(void)
{
/* Initialize the shared memory that will be used to buffer messages
* over the usrloc module to RegUserTable callback. */
frontRegUserTableBuffer =  shm_malloc(sizeof(interprocessBuffer_t));
endRegUserTableBuffer   =  shm_malloc(sizeof(interprocessBuffer_t));

if(frontRegUserTableBuffer == NULL || endRegUserTableBuffer == NULL)
{
LM_ERR("no more shared memory\n");
return -1;
}

memset(frontRegUserTableBuffer, 0x00, sizeof(interprocessBuffer_t));
memset(endRegUserTableBuffer,   0x00, sizeof(interprocessBuffer_t));

/* Initialize a lock to the interprocess buffer.  The lock will be used
* to control race-conditions that would otherwise occur if an snmp
* command was received while the interprocess buffer was being consumed.
*/
interprocessCBLock = lock_alloc();
if(interprocessCBLock==NULL)
{
LM_ERR("cannot allocate the lock\n");
shm_free(frontRegUserTableBuffer);
frontRegUserTableBuffer = NULL;
shm_free(endRegUserTableBuffer);
endRegUserTableBuffer = NULL;
return -1;
}
lock_init(interprocessCBLock);

hashTable = createHashTable(HASH_SIZE);
if(hashTable == NULL)
{
LM_ERR("no more shared memory\n");
lock_destroy(interprocessCBLock);
lock_dealloc(interprocessCBLock);
shm_free(frontRegUserTableBuffer);
frontRegUserTableBuffer = NULL;
shm_free(endRegUserTableBuffer);
endRegUserTableBuffer = NULL;
return -1;
}

return 1;
}

/*! USRLOC Callback Handler:
*
* This function should be registered to receive callbacks from the usrloc
* module.  It can be called for any of the callbacks listed in ul_callback.h.
* The callback type will be passed in 'type', and the contact the callback
* applies to will be supplied in 'contactInfo.  This information will be copied
* into the interprocess buffer.  The interprocess buffer will be consumed at a
* later time, when consumeInterprocessBuffer() is called.
*
* This callback is thread safe with respect to the consumeInterprocessBuffer()
* function.  Specifically, the interprocess buffer should not be corrupted by
* any race conditions between this function and the consumeInterprocessBuffer()
* function.
*/
void handleContactCallbacks(ucontact_t *contactInfo, int type, void *param)
{
char *contact;

interprocessBuffer_t *currentBufferElement;

currentBufferElement = shm_malloc(sizeof(interprocessBuffer_t));

if (currentBufferElement == NULL)
{
goto error;
}

/* We need to maintain our own copies of the AOR and contact address to
* prevent the corruption of our internal data structures.
*
* If we do not maintain our own copies, then the AOR and contact adress
* pointed to could be removed and reallocated to another thread before
* we get a chance to consume our interprocess buffer.  */
convertStrToCharString(&(contactInfo->c), &contact);

currentBufferElement->stringContact = contact;
currentBufferElement->contactInfo   = contactInfo;
currentBufferElement->callbackType  = type;
currentBufferElement->next          = NULL;

/* A lock is necessary to prevent a race condition.  Specifically, it
* could happen that we find the front of the buffer to be non-null,
* are scheduled out, the entire buffer (or part of it) is consumed and
* freed, and then we assign our list to deallocated memory. */
lock_get(interprocessCBLock);

/* This is the first element to be added. */
if (frontRegUserTableBuffer->next == NULL) {
frontRegUserTableBuffer->next     = currentBufferElement;
} else {
endRegUserTableBuffer->next->next = currentBufferElement;
}

endRegUserTableBuffer->next   = currentBufferElement;

lock_release(interprocessCBLock);

return;

error:
LM_ERR("Not enough shared memory for  kamailioSIPRegUserTable insert."
" (%s)\n", contactInfo->c.s);
}

/*! Interprocess Buffer consumption Function.  This function will iterate over
* every element of the interprocess buffer, and add or remove the specified
* contacts and users.  Whether the contacts are added or removed is dependent
* on if the original element was added as a result of a UL_CONTACT_INSERT or
* UL_CONTACT_EXPIRE callback.
*
* The function will free any memory occupied by the interprocess buffer.
*
* \note This function is believed to be thread safe.  Specifically, it protects
*       corruption of the interprocess buffer through the interprocessCBLock.
*       This ensures no corruption of the buffer by race conditions.  The lock
*       has been designed to be occupied for as short a period as possible, so
*       as to prevent long waits.  Specifically, once we start consumption of
*       the list, other processes are free to continue even before we are done.
*       This is made possible by simply changing the head of the interprocess
*       buffer, and then releasing the lock.
*/
void consumeInterprocessBuffer(void)
{
interprocessBuffer_t *previousBuffer;
interprocessBuffer_t *currentBuffer;

/* There is nothing to consume, so just exit. */
if (frontRegUserTableBuffer->next == NULL)
{
return;
}

/* We are going to consume the entire buffer, but we don't want the
* buffer to change midway through.  So assign the front of the buffer
* to NULL so that any other callbacks from the usrloc module will be
* appended to a new list.  We need to be careful to get a lock first
* though, to avoid race conditions. */
lock_get(interprocessCBLock);

currentBuffer = frontRegUserTableBuffer->next;

frontRegUserTableBuffer->next = NULL;
endRegUserTableBuffer->next   = NULL;

lock_release(interprocessCBLock);

while (currentBuffer != NULL) {

executeInterprocessBufferCmd(currentBuffer);

/* We need to assign the current buffer to a temporary place
* before we move onto the next buffer.  Otherwise the memory
* could be modified between freeing it and moving onto the next
* buffer element. */
previousBuffer = currentBuffer;
currentBuffer = currentBuffer->next;
shm_free(previousBuffer->stringName);
shm_free(previousBuffer->stringContact);
shm_free(previousBuffer);

}

}

/*!
* This function takes an element of the interprocess buffer passed to it, and
* handles populating the respective user and contact tables with its contained
* data.
*/
static void executeInterprocessBufferCmd(interprocessBuffer_t *currentBuffer)
{
int delContactIndex;

aorToIndexStruct_t *currentUser;

if (currentBuffer->callbackType == UL_CONTACT_INSERT)
{
/* Add the user if the user doesn't exist, or increment its
* contact index otherwise. */
updateUser(currentBuffer->stringName);
}
else if (currentBuffer->callbackType != UL_CONTACT_EXPIRE)
{
/* Currently we only support UL_CONTACT_INSERT and
* UL_CONTACT_EXPIRE.  If we receive another callback type, this
* is a bug. */
LM_ERR("found a command on the interprocess buffer that"
" was not an INSERT or EXPIRE");
return;
}

currentUser =
findHashRecord(hashTable, currentBuffer->stringName, HASH_SIZE);

/* This should never happen.  This is more of a sanity check. */
if (currentUser == NULL) {
LM_ERR("Received a request for contact: %s for user: %s who doesn't "
"exists\n", currentBuffer->stringName,
currentBuffer->stringContact);
return;
}

/* This buffer element specified that we need to add a contact.  So lets
if (currentBuffer->callbackType == UL_CONTACT_INSERT) {

/* Increment the contact index, which will be used to generate
* our new row.  */
currentUser->contactIndex++;

/* We should do this after we create the row in the snmptable.
* Its easier to delete the SNMP Row than the contact record. */
if(!insertContactRecord(&(currentUser->contactList),
currentUser->contactIndex,
currentBuffer->stringContact)) {

LM_ERR("kamailioSIPRegUserTable was unable to allocate memory for "
"adding contact: %s to user %s.\n",
currentBuffer->stringName, currentBuffer->stringContact);

/* We didn't use the index, so decrement it so we can
* use it next time around. */
currentUser->contactIndex--;

return;
}

if (!createContactRow(currentUser->userIndex,
currentUser->contactIndex,
currentBuffer->stringContact,
currentBuffer->contactInfo)) {

deleteContactRecord(&(currentUser->contactList),
currentBuffer->stringContact);

}

}
else {

delContactIndex =
deleteContactRecord(&(currentUser->contactList),
currentBuffer->stringContact);

/* This should never happen.  But its probably wise to check and
* to print out debug messages in case there is a hidden bug.  */
if(delContactIndex == 0) {

LM_ERR("Received a request to delete contact: %s for user: %s"
"  who doesn't exist\n", currentBuffer->stringName,
currentBuffer->stringContact);
return;

}

deleteContactRow(currentUser->userIndex, delContactIndex);

deleteUser(hashTable, currentBuffer->stringName, HASH_SIZE);
}
}

void freeInterprocessBuffer(void)
{
interprocessBuffer_t *currentBuffer, *previousBuffer;

if (frontRegUserTableBuffer==NULL
|| frontRegUserTableBuffer->next == NULL
|| endRegUserTableBuffer==NULL) {
LM_DBG("Nothing to clean\n");
return;
}

currentBuffer = frontRegUserTableBuffer->next;

frontRegUserTableBuffer->next = NULL;
endRegUserTableBuffer->next   = NULL;

while (currentBuffer != NULL) {

previousBuffer = currentBuffer;
currentBuffer = currentBuffer->next;
shm_free(previousBuffer->stringName);
shm_free(previousBuffer->stringContact);
shm_free(previousBuffer);

}

if(frontRegUserTableBuffer)
shm_free(frontRegUserTableBuffer);

if(endRegUserTableBuffer)
shm_free(endRegUserTableBuffer);

}