/*
 * SNMPStats Module
 * Copyright (C) 2006 SOMA Networks, INC.
 * Written by: Jeffrey Magder (jmagder@somanetworks.com)
 *
 * This file is part of opensips, a free SIP server.
 *
 * opensips is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version
 *
 * opensips 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
 *
 * History:
 * --------
 * 2006-11-23 initial version (jmagder)
 *
 * This file implements the interprocess buffer, used for marshalling data
 * exchange from the usrloc module to the openserSIPRegUserTable,
 * openserSIPContactTable, and indirectly the openserSIPRegUserLookupTable.

 * Details on why the interprocess buffer is needed can be found in the comments
 * at the top of interprocess_buffer.h
 */


#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 "openserSIPContactTable.h"
#include "openserSIPRegUserTable.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;

/* 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;

/*
 * 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();
	lock_init(interprocessCBLock);

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

	return 1;
}


void IBAlarmHandler(unsigned int reg, void *clientarg)
{
	consumeInterprocessBuffer();
}


int setInterprocessBuffersAlarm(void)
{
	if ( snmp_alarm_register(5/*seconds*/,SA_REPEAT,IBAlarmHandler,NULL)==0){
		LM_ERR("failed to set consumer snmp alarm\n");
		return -1;
	}
	return 0;
}


/* 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(void *binding, ul_cb_type type, ul_cb_extra *_)
{
	ucontact_t *contactInfo = (ucontact_t *)binding;
	char *p;

	interprocessBuffer_t *currentBufferElement;

	if (frontRegUserTableBuffer==NULL)
		return;

	currentBufferElement = shm_malloc( sizeof(interprocessBuffer_t) +
		contactInfo->aor->len+1 + contactInfo->c.len+1 );

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

	p = (char*)(currentBufferElement + 1 );
	/* 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 address
	 * pointed to could be removed and reallocated to another thread before
	 * we get a chance to consume our interprocess buffer.  */
	currentBufferElement->stringName =  p;
	memcpy( p , contactInfo->aor->s, contactInfo->aor->len );
	p[contactInfo->aor->len] = 0;  /* make it NULL terminated */
	p += contactInfo->aor->len + 1;

	currentBufferElement->stringContact = p;
	memcpy( p , contactInfo->c.s, contactInfo->c.len );
	p[contactInfo->c.len] = 0;  /* make it NULL terminated */

	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 openserSIPRegUserTable 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,
 * UL_CONTACT_EXPIRE or UL_CONTACT_DELETE 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);

	}

}


/*
 * 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 &&
	currentBuffer->callbackType != UL_CONTACT_DELETE)
	{
		/* Currently we only support UL_CONTACT_INSERT, UL_CONTACT_DELETE 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_NOTICE("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
	 * add them */
	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("openserSIPRegUserTable 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(endRegUserTableBuffer) {
		endRegUserTableBuffer->next = NULL;
		shm_free(endRegUserTableBuffer);
		endRegUserTableBuffer = NULL;
	}

	if(frontRegUserTableBuffer) {
		if (frontRegUserTableBuffer->next != NULL) {

			currentBuffer = frontRegUserTableBuffer->next;

			frontRegUserTableBuffer->next = NULL;

			while (currentBuffer != NULL) {
				previousBuffer = currentBuffer;
				currentBuffer = currentBuffer->next;
				shm_free(previousBuffer);
			}
		} else {
			LM_DBG("Nothing to clean\n");
		}
		shm_free(frontRegUserTableBuffer);
		frontRegUserTableBuffer = NULL;
	}
}

