///////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2000-2003 Intel Corporation 
// All rights reserved. 
//
// Redistribution and use in source and binary forms, with or without 
// modification, are permitted provided that the following conditions are met: 
//
// * Redistributions of source code must retain the above copyright notice, 
// this list of conditions and the following disclaimer. 
// * Redistributions in binary form must reproduce the above copyright notice, 
// this list of conditions and the following disclaimer in the documentation 
// and/or other materials provided with the distribution. 
// * Neither name of Intel Corporation nor the names of its contributors 
// may be used to endorse or promote products derived from this software 
// without specific prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTEL OR 
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 
// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
///////////////////////////////////////////////////////////////////////////


#include "config.h"


#if EXCLUDE_GENA == 0
#ifdef INCLUDE_CLIENT_APIS


#include "EventSubscribe.h"
#include "gena.h"
#include "httpparser.h"
#include "httpreadwrite.h"
#include "parsetools.h"
#include "statcodes.h"
#include "sysdep.h"
#include "uuid.h"
#include "upnpapi.h"

extern ithread_mutex_t GlobalClientSubscribeMutex;

/************************************************************************
* Function : GenaAutoRenewSubscription
*
* Parameters:
*	IN void *input: Thread data(upnp_timeout *) needed to send the renewal
*
* Description:
*	This is a thread function to send the renewal just before the
*	subscription times out.
*
* Returns: VOID
*	
***************************************************************************/
static void
GenaAutoRenewSubscription( IN void *input )
{
	upnp_timeout *event = (upnp_timeout *) input;
	UpnpEventSubscribe *sub_struct = (UpnpEventSubscribe *)event->Event;
	void *cookie;
	Upnp_FunPtr callback_fun;
	struct Handle_Info *handle_info;
	int send_callback = 0;
	int eventType = 0;
	int timeout = 0;
	int errCode = 0;

	if (AUTO_RENEW_TIME == 0) {
		UpnpPrintf( UPNP_INFO, GENA, __FILE__, __LINE__, "GENA SUB EXPIRED");
		UpnpEventSubscribe_set_ErrCode(sub_struct, UPNP_E_SUCCESS);
		send_callback = 1;
		eventType = UPNP_EVENT_SUBSCRIPTION_EXPIRED;
	} else {
		UpnpPrintf(UPNP_INFO, GENA, __FILE__, __LINE__, "GENA AUTO RENEW");
		errCode = genaRenewSubscription(
			event->handle,
			UpnpEventSubscribe_get_SID(sub_struct),
			&timeout);
		UpnpEventSubscribe_set_ErrCode(sub_struct, errCode);
		UpnpEventSubscribe_set_TimeOut(sub_struct, timeout);
		if (errCode != UPNP_E_SUCCESS &&
		    errCode != GENA_E_BAD_SID &&
		    errCode != GENA_E_BAD_HANDLE) {
			send_callback = 1;
			eventType = UPNP_EVENT_AUTORENEWAL_FAILED;
		}
	}

	if (send_callback) {
		HandleReadLock();
		if( GetHandleInfo( event->handle, &handle_info ) != HND_CLIENT ) {
			HandleUnlock();
			free_upnp_timeout(event);

			return;
		}
		UpnpPrintf(UPNP_INFO, GENA, __FILE__, __LINE__, "HANDLE IS VALID");

		// make callback
		callback_fun = handle_info->Callback;
		cookie = handle_info->Cookie;
		HandleUnlock();
		callback_fun(eventType, event->Event, cookie);
	}

	free_upnp_timeout(event);
}


/************************************************************************
 * Function : ScheduleGenaAutoRenew
 *
 * Parameters:
 *	IN int client_handle: Handle that also contains the subscription list
 *	IN int TimeOut: The time out value of the subscription
 *	IN client_subscription * sub: Subscription being renewed
 *
 * Description:
 *	This function schedules a job to renew the subscription just before
 *	time out.
 *
 * Returns: int
 *	return GENA_E_SUCCESS if successful else returns appropriate error
 ***************************************************************************/
static int ScheduleGenaAutoRenew(
	IN int client_handle,
	IN int TimeOut,
	IN ClientSubscription *sub)
{
	UpnpEventSubscribe *RenewEventStruct = NULL;
	upnp_timeout *RenewEvent = NULL;
	int return_code = GENA_SUCCESS;
	ThreadPoolJob job;

	if( TimeOut == UPNP_INFINITE ) {
		return GENA_SUCCESS;
	}

	RenewEventStruct = UpnpEventSubscribe_new();
	if( RenewEventStruct == NULL ) {
		return UPNP_E_OUTOF_MEMORY;
	}

	RenewEvent = (upnp_timeout *) malloc(sizeof(upnp_timeout));
	if( RenewEvent == NULL ) {
		free( RenewEventStruct );
		return UPNP_E_OUTOF_MEMORY;
	}

	// schedule expire event
	UpnpEventSubscribe_set_ErrCode(RenewEventStruct, UPNP_E_SUCCESS);
	UpnpEventSubscribe_set_TimeOut(RenewEventStruct, TimeOut);
	UpnpEventSubscribe_set_SID(RenewEventStruct, UpnpClientSubscription_get_SID(sub));
	UpnpEventSubscribe_set_PublisherUrl(RenewEventStruct, UpnpClientSubscription_get_EventURL(sub));

	// RenewEvent->EventType=UPNP_EVENT_SUBSCRIPTION_EXPIRE;
	RenewEvent->handle = client_handle;
	RenewEvent->Event = RenewEventStruct;

	TPJobInit(&job, (start_routine) GenaAutoRenewSubscription, RenewEvent);
	TPJobSetFreeFunction(&job, (free_routine)free_upnp_timeout);
	TPJobSetPriority(&job, MED_PRIORITY);

	// Schedule the job
	return_code = TimerThreadSchedule(
		&gTimerThread,
		TimeOut - AUTO_RENEW_TIME,
		REL_SEC,
		&job, SHORT_TERM,
		&(RenewEvent->eventId));
	if (return_code != UPNP_E_SUCCESS) {
		free(RenewEvent);
		free(RenewEventStruct);

		return return_code;
	}

	UpnpClientSubscription_set_RenewEventId(sub, RenewEvent->eventId);

	return GENA_SUCCESS;
}

/************************************************************************
 * Function : gena_unsubscribe
 *
 * Parameters:
 *	IN const UpnpString *url: Event URL of the service
 *	IN const UpnpString *sid: The subcription ID.
 *	OUT http_parser_t *response: The UNSUBCRIBE response from the device
 *
 * Description:
 *	This function sends the UNSUBCRIBE gena request and recieves the 
 *	response from the device and returns it as a parameter
 *
 * Returns: int
 *	return 0 if successful else returns appropriate error
 ***************************************************************************/
static int gena_unsubscribe(
	IN const UpnpString *url,
	IN const UpnpString *sid,
	OUT http_parser_t *response )
{
	int return_code;
	uri_type dest_url;
	membuffer request;

	// parse url
	return_code = http_FixStrUrl(
		UpnpString_get_String(url),
		UpnpString_get_Length(url),
		&dest_url);
	if (return_code != 0) {
		return return_code;
	}

	// make request msg
	membuffer_init(&request);
	request.size_inc = 30;
	return_code = http_MakeMessage(
		&request, 1, 1,
		"q" "ssc" "Uc",
		HTTPMETHOD_UNSUBSCRIBE, &dest_url,
		"SID: ", UpnpString_get_String(sid));

	// Not able to make the message so destroy the existing buffer
	if (return_code != 0) {
		membuffer_destroy(&request);

		return return_code;
	}

	// send request and get reply
	return_code = http_RequestAndResponse(
		&dest_url, request.buf, request.length,
		HTTPMETHOD_UNSUBSCRIBE, HTTP_DEFAULT_TIMEOUT, response);
	membuffer_destroy(&request);
	if (return_code != 0) {
		httpmsg_destroy(&response->msg);
	}

	if (return_code == 0 && response->msg.status_code != HTTP_OK) {
		return_code = UPNP_E_UNSUBSCRIBE_UNACCEPTED;
		httpmsg_destroy(&response->msg);
	}

	return return_code;
}

/************************************************************************
 * Function: gena_subscribe
 *
 * Parameters:
 *	IN const UpnpString *url: url of service to subscribe
 *	INOUT int* timeout: subscription time desired (in secs)
 *	IN const UpnpString *renewal_sid: for renewal, this contains a currently h
 *		held subscription SID. For first time
 *		subscription, this must be NULL
 *	OUT UpnpString *sid: SID returned by the subscription or renew msg
 *
 * Description:
 *	This function subscribes or renew subscription
 *
 * Returns: int
 *	return 0 if successful else returns appropriate error
 ***************************************************************************/
static int gena_subscribe(
	IN const UpnpString *url,
	INOUT int *timeout,
	IN const UpnpString *renewal_sid,
	OUT UpnpString *sid)
{
	int return_code;
	int parse_ret = 0;
	int local_timeout = CP_MINIMUM_SUBSCRIPTION_TIME;
	memptr sid_hdr;
	memptr timeout_hdr;
	char timeout_str[25];
	membuffer request;
	uri_type dest_url;
	http_parser_t response;

	UpnpString_clear(sid);

	// request timeout to string
	if (timeout == NULL) {
		timeout = &local_timeout;
	}
	if (*timeout < 0) {
		strcpy(timeout_str, "infinite");
	} else if(*timeout < CP_MINIMUM_SUBSCRIPTION_TIME) {
		sprintf(timeout_str, "%d", CP_MINIMUM_SUBSCRIPTION_TIME);
	} else {
		sprintf(timeout_str, "%d", *timeout);
	}

	// parse url
	return_code = http_FixStrUrl(
		UpnpString_get_String(url),
		UpnpString_get_Length(url),
		&dest_url);
	if (return_code != 0) {
		return return_code;
	}

	// make request msg
	membuffer_init(&request);
	request.size_inc = 30;
	if (renewal_sid) {
		// renew subscription
		return_code = http_MakeMessage(
			&request, 1, 1,
			"q" "ssc" "sscc",
			HTTPMETHOD_SUBSCRIBE, &dest_url,
			"SID: ", renewal_sid,
			"TIMEOUT: Second-", timeout_str );
	} else {
		// subscribe
		if( dest_url.hostport.IPaddress.ss_family == AF_INET6 ) {
			return_code = http_MakeMessage(
				&request, 1, 1,
				"q" "sssdsc" "sc" "sscc",
				HTTPMETHOD_SUBSCRIBE, &dest_url,
				"CALLBACK: <http://[", gIF_IPV6, "]:", LOCAL_PORT_V6, "/>",
				"NT: upnp:event",
				"TIMEOUT: Second-", timeout_str );
		} else {
			return_code = http_MakeMessage(
				&request, 1, 1,
				"q" "sssdsc" "sc" "sscc",
				HTTPMETHOD_SUBSCRIBE, &dest_url,
				"CALLBACK: <http://", gIF_IPV4, ":", LOCAL_PORT_V4, "/>",
				"NT: upnp:event",
				"TIMEOUT: Second-", timeout_str);
		}
	}
	if (return_code != 0) {
		return return_code;
	}

	// send request and get reply
	return_code = http_RequestAndResponse(&dest_url, request.buf,
		request.length,
		HTTPMETHOD_SUBSCRIBE,
		HTTP_DEFAULT_TIMEOUT,
		&response);
	membuffer_destroy(&request);

	if (return_code != 0) {
		httpmsg_destroy(&response.msg);

		return return_code;
	}
	if (response.msg.status_code != HTTP_OK) {
		httpmsg_destroy(&response.msg);

		return UPNP_E_SUBSCRIBE_UNACCEPTED;
	}

	// get SID and TIMEOUT
	if (httpmsg_find_hdr(&response.msg, HDR_SID, &sid_hdr) == NULL ||
	    sid_hdr.length == 0 ||
	    httpmsg_find_hdr( &response.msg, HDR_TIMEOUT, &timeout_hdr ) == NULL ||
	    timeout_hdr.length == 0 ) {
		httpmsg_destroy( &response.msg );

		return UPNP_E_BAD_RESPONSE;
	}

	// save timeout
	parse_ret = matchstr(timeout_hdr.buf, timeout_hdr.length, "%iSecond-%d%0", timeout);
	if (parse_ret == PARSE_OK) {
		// nothing to do
	} else if (memptr_cmp_nocase(&timeout_hdr, "Second-infinite") == 0) {
		*timeout = -1;
	} else {
		httpmsg_destroy( &response.msg );

		return UPNP_E_BAD_RESPONSE;
	}

	// save SID
	UpnpString_set_StringN(sid, sid_hdr.buf, sid_hdr.length);
	if (UpnpString_get_String(sid) == NULL) {
		httpmsg_destroy(&response.msg);

		return UPNP_E_OUTOF_MEMORY;
	}
	httpmsg_destroy(&response.msg);

	return UPNP_E_SUCCESS;
}

/************************************************************************
 * Function: genaUnregisterClient
 *
 * Parameters:
 *	IN UpnpClient_Handle client_handle: 
 *		Handle containing all the control point related information
 *
 * Description:
 *	This function unsubcribes all the outstanding subscriptions and cleans
 *	the subscription list. This function is called when control point 
 *	unregisters.
 *
 * Returns: int
 *	return UPNP_E_SUCCESS if successful else returns appropriate error
 ***************************************************************************/
int genaUnregisterClient(IN UpnpClient_Handle client_handle)
{
	ClientSubscription *sub_copy = UpnpClientSubscription_new();
	int return_code = UPNP_E_SUCCESS;
	struct Handle_Info *handle_info = NULL;
	http_parser_t response;

	while (TRUE) {
		HandleLock();

		if (GetHandleInfo(client_handle, &handle_info) != HND_CLIENT) {
			HandleUnlock();
			return_code = GENA_E_BAD_HANDLE;
			goto exit_function;
		}
		if (handle_info->ClientSubList == NULL) {
			return_code = UPNP_E_SUCCESS;
			break;
		}
		UpnpClientSubscription_assign(sub_copy, handle_info->ClientSubList);
		RemoveClientSubClientSID(
			&handle_info->ClientSubList,
			UpnpClientSubscription_get_SID(sub_copy));

		HandleUnlock();

		return_code = gena_unsubscribe(
			UpnpClientSubscription_get_EventURL(sub_copy),
			UpnpClientSubscription_get_ActualSID(sub_copy),
			&response);
		if (return_code == 0) {
			httpmsg_destroy(&response.msg);
		}
		free_client_subscription(sub_copy);
	}

	freeClientSubList(handle_info->ClientSubList);
	HandleUnlock();

exit_function:
	UpnpClientSubscription_delete(sub_copy);
	return return_code;
}


/************************************************************************
 * Function: genaUnSubscribe
 *
 * Parameters:
 *	IN UpnpClient_Handle client_handle: UPnP client handle
 *	IN const UpnpString *in_sid: The subscription ID
 *
 * Description:
 *	This function unsubscribes a SID. It first validates the SID and 
 *	client_handle,copies the subscription, sends UNSUBSCRIBE http request 
 *	to service processes request and finally removes the subscription
 *
 * Returns: int
 *	return UPNP_E_SUCCESS if service response is OK else 
 *	returns appropriate error
 ***************************************************************************/
#ifdef INCLUDE_CLIENT_APIS
int genaUnSubscribe(
	IN UpnpClient_Handle client_handle,
	IN const UpnpString *in_sid)
{
	ClientSubscription *sub = NULL;
	int return_code = GENA_SUCCESS;
	struct Handle_Info *handle_info;
	ClientSubscription *sub_copy = UpnpClientSubscription_new();
	http_parser_t response;

	// validate handle and sid
	HandleLock();
	if (GetHandleInfo(client_handle, &handle_info) != HND_CLIENT) {
		HandleUnlock();
		return_code = GENA_E_BAD_HANDLE;
		goto exit_function;
	}
	sub = GetClientSubClientSID(handle_info->ClientSubList, in_sid);
	if (sub == NULL) {
		HandleUnlock();
		return_code = GENA_E_BAD_SID;
		goto exit_function;
	}
	UpnpClientSubscription_assign(sub_copy, sub);
	HandleUnlock();

	return_code = gena_unsubscribe(
		UpnpClientSubscription_get_EventURL(sub_copy),
		UpnpClientSubscription_get_ActualSID(sub_copy),
		&response);
	if (return_code == 0) {
		httpmsg_destroy(&response.msg);
	}
	free_client_subscription(sub_copy);

	HandleLock();
	if (GetHandleInfo(client_handle, &handle_info) != HND_CLIENT) {
		HandleUnlock();
		return_code = GENA_E_BAD_HANDLE;
		goto exit_function;
	}
	RemoveClientSubClientSID(&handle_info->ClientSubList, in_sid);
	HandleUnlock();

exit_function:
	UpnpClientSubscription_delete(sub_copy);
	return return_code;
	}
#endif /* INCLUDE_CLIENT_APIS */


/************************************************************************
 * Function: genaSubscribe
 *
 * Parameters:
 *	IN UpnpClient_Handle client_handle:
 *	IN const UpnpString *PublisherURL: Of the form:
 *		"http://134.134.156.80:4000/RedBulb/Event"
 *	INOUT int * TimeOut: requested Duration, if -1, then "infinite".
 *		in the OUT case: actual Duration granted 
 *		by Service, -1 for infinite
 *	OUT UpnpString *out_sid: sid of subscription, memory passed in by caller
 *
 * Description:
 *	This function subscribes to a PublisherURL ( also mentioned as EventURL
 *	some places). It sends SUBSCRIBE http request to service processes 
 *	request. Finally adds a Subscription to 
 *	the clients subscription list, if service responds with OK
 *
 * Returns: int
 *	return UPNP_E_SUCCESS if service response is OK else 
 *	returns appropriate error
 ***************************************************************************/
#ifdef INCLUDE_CLIENT_APIS
int genaSubscribe(
	IN UpnpClient_Handle client_handle,
	IN const UpnpString *PublisherURL,
	INOUT int *TimeOut,
	OUT UpnpString *out_sid)
{
	int return_code = GENA_SUCCESS;
	ClientSubscription *newSubscription = UpnpClientSubscription_new();
	uuid_upnp uid;
	Upnp_SID temp_sid;
	Upnp_SID temp_sid2;
	UpnpString *ActualSID = UpnpString_new();
	UpnpString *EventURL = UpnpString_new();
	struct Handle_Info *handle_info;

	UpnpPrintf(UPNP_INFO, GENA, __FILE__, __LINE__, "GENA SUBSCRIBE BEGIN");

	UpnpString_clear(out_sid);

	HandleReadLock();
	// validate handle
	if (GetHandleInfo(client_handle, &handle_info) != HND_CLIENT) {
		HandleUnlock();

		return GENA_E_BAD_HANDLE;
	}
	HandleUnlock();

	// subscribe
	SubscribeLock();
	return_code = gena_subscribe(PublisherURL, TimeOut, NULL, ActualSID);
	HandleLock();
	if (return_code != UPNP_E_SUCCESS) {
		UpnpPrintf( UPNP_CRITICAL, GENA, __FILE__, __LINE__,
			"SUBSCRIBE FAILED in transfer error code: %d returned\n",
			return_code );
		goto error_handler;
	}

	if(GetHandleInfo(client_handle, &handle_info) != HND_CLIENT) {
		return_code = GENA_E_BAD_HANDLE;
		goto error_handler;
	}

	// generate client SID
	uuid_create(&uid );
	uuid_unpack(&uid, temp_sid);
	sprintf(temp_sid2, "uuid:%s", temp_sid);
	UpnpString_set_String(out_sid, temp_sid2);

	// create event url
	UpnpString_assign(EventURL, PublisherURL);

	// fill subscription
	if (newSubscription == NULL) {
		return_code = UPNP_E_OUTOF_MEMORY;
		goto error_handler;
	}
	UpnpClientSubscription_set_RenewEventId(newSubscription, -1);
	UpnpClientSubscription_set_SID(newSubscription, out_sid);
	UpnpClientSubscription_set_ActualSID(newSubscription, ActualSID);
	UpnpClientSubscription_set_EventURL(newSubscription, EventURL);
	UpnpClientSubscription_set_Next(newSubscription, handle_info->ClientSubList);
	handle_info->ClientSubList = newSubscription;

	// schedule expiration event
	return_code = ScheduleGenaAutoRenew(client_handle, *TimeOut, newSubscription);

error_handler:
	if (return_code != UPNP_E_SUCCESS) {
		UpnpString_delete(ActualSID);
		UpnpString_delete(EventURL);
		UpnpClientSubscription_delete(newSubscription);
	}
	HandleUnlock();
	SubscribeUnlock();

	return return_code;
}
#endif /* INCLUDE_CLIENT_APIS */


/************************************************************************
 * Function : genaRenewSubscription
 *
 * Parameters:
 *	IN UpnpClient_Handle client_handle: Client handle
 *	IN const UpnpString *in_sid: subscription ID
 *	INOUT int * TimeOut: requested Duration, if -1, then "infinite".
 *		in the OUT case: actual Duration granted 
 *		by Service, -1 for infinite
 *
 * Description:
 *	This function renews a SID. It first validates the SID and 
 *	client_handle and copies the subscription. It sends RENEW 
 *	(modified SUBSCRIBE) http request to service and processes
 *	the response.
 *
 * Returns: int
 *	return UPNP_E_SUCCESS if service response is OK else 
 *	returns appropriate error
 ***************************************************************************/
int genaRenewSubscription(
	IN UpnpClient_Handle client_handle,
	IN const UpnpString *in_sid,
	INOUT int *TimeOut)
{
	int return_code = GENA_SUCCESS;
	ClientSubscription *sub = NULL;
	ClientSubscription *sub_copy = UpnpClientSubscription_new();
	struct Handle_Info *handle_info;
	UpnpString *ActualSID = UpnpString_new();
	ThreadPoolJob tempJob;

	HandleLock();

	// validate handle and sid
	if (GetHandleInfo(client_handle, &handle_info) != HND_CLIENT) {
		HandleUnlock();

		return_code = GENA_E_BAD_HANDLE;
		goto exit_function;
	}

	sub = GetClientSubClientSID(handle_info->ClientSubList, in_sid);
	if (sub == NULL) {
		HandleUnlock();

		return_code = GENA_E_BAD_SID;
		goto exit_function;
	}

	// remove old events
	if (TimerThreadRemove(
		&gTimerThread,
		UpnpClientSubscription_get_RenewEventId(sub),
		&tempJob) == 0 ) {
		free_upnp_timeout((upnp_timeout *)tempJob.arg);
	}

	UpnpPrintf(UPNP_INFO, GENA, __FILE__, __LINE__, "REMOVED AUTO RENEW  EVENT");

	UpnpClientSubscription_set_RenewEventId(sub, -1);
	UpnpClientSubscription_assign(sub_copy, sub);

	HandleUnlock();

	return_code = gena_subscribe(
		UpnpClientSubscription_get_EventURL(sub_copy),
		TimeOut,
		UpnpClientSubscription_get_ActualSID(sub_copy),
		ActualSID);

	HandleLock();

	if (GetHandleInfo(client_handle, &handle_info) != HND_CLIENT) {
		HandleUnlock();
		return_code = GENA_E_BAD_HANDLE;
		goto exit_function;
	}

	// we just called GetHandleInfo, so we don't check for return value
	//GetHandleInfo(client_handle, &handle_info);
	if (return_code != UPNP_E_SUCCESS) {
		// network failure (remove client sub)
		RemoveClientSubClientSID(&handle_info->ClientSubList, in_sid);
		free_client_subscription(sub_copy);
		HandleUnlock();
		goto exit_function;
	}

	// get subscription
	sub = GetClientSubClientSID(handle_info->ClientSubList, in_sid);
	if (sub == NULL) {
		free_client_subscription(sub_copy);
		HandleUnlock();
		return_code = GENA_E_BAD_SID;
		goto exit_function;
	}

	// store actual sid
	UpnpClientSubscription_set_ActualSID(sub, ActualSID);

	// start renew subscription timer
	return_code = ScheduleGenaAutoRenew(client_handle, *TimeOut, sub);
	if (return_code != GENA_SUCCESS) {
		RemoveClientSubClientSID(
			&handle_info->ClientSubList,
			UpnpClientSubscription_get_SID(sub));
	}
	free_client_subscription(sub_copy);
	HandleUnlock();

exit_function:
	UpnpString_delete(ActualSID);
	UpnpClientSubscription_delete(sub_copy);
	return return_code;
}


/************************************************************************
 * Function: gena_process_notification_event
 *
 * Parameters:
 *	IN SOCKINFO *info: Socket structure containing the device socket 
 *			information
 *	IN http_message_t *event: The http message contains the GENA 
 *			notification
 *
 * Description:
 *	This function processes NOTIFY events that are sent by devices. 
 *	called by genacallback()
 *
 * Returns: void
 *
 * Note : called by genacallback()
 ****************************************************************************/
void gena_process_notification_event(IN SOCKINFO *info, IN http_message_t *event)
{
	UpnpEvent *event_struct = UpnpEvent_new();
	IXML_Document *ChangedVars = NULL;
	int eventKey;
	token sid;
	ClientSubscription *subscription = NULL;
	struct Handle_Info *handle_info;
	void *cookie;
	Upnp_FunPtr callback;
	UpnpClient_Handle client_handle;

	memptr sid_hdr;
	memptr nt_hdr,
	nts_hdr;
	memptr seq_hdr;

	// get SID
	if (httpmsg_find_hdr(event, HDR_SID, &sid_hdr) == NULL) {
		error_respond(info, HTTP_PRECONDITION_FAILED, event);
		goto exit_function;
	}
	sid.buff = sid_hdr.buf;
	sid.size = sid_hdr.length;

	// get event key
	if (httpmsg_find_hdr(event, HDR_SEQ, &seq_hdr) == NULL ||
	    matchstr(seq_hdr.buf, seq_hdr.length, "%d%0", &eventKey) != PARSE_OK) {
		error_respond( info, HTTP_BAD_REQUEST, event );
		goto exit_function;
	}

	// get NT and NTS headers
	if (httpmsg_find_hdr(event, HDR_NT, &nt_hdr) == NULL ||
	    httpmsg_find_hdr(event, HDR_NTS, &nts_hdr) == NULL) {
		error_respond( info, HTTP_BAD_REQUEST, event );
		goto exit_function;
	}

	// verify NT and NTS headers
	if (memptr_cmp(&nt_hdr, "upnp:event") != 0 ||
	    memptr_cmp(&nts_hdr, "upnp:propchange") != 0) {
		error_respond(info, HTTP_PRECONDITION_FAILED, event);
		goto exit_function;
	}

	// parse the content (should be XML)
	if (!has_xml_content_type(event) ||
	    event->msg.length == 0 ||
	    ixmlParseBufferEx(event->entity.buf, &ChangedVars) != IXML_SUCCESS) {
		error_respond(info, HTTP_BAD_REQUEST, event);
		goto exit_function;
	}

	HandleLock();

	// get client info
	if (GetClientHandleInfo(&client_handle, &handle_info) != HND_CLIENT) {
		error_respond(info, HTTP_PRECONDITION_FAILED, event);
		HandleUnlock();
		goto exit_function;
	}

	// get subscription based on SID
	subscription = GetClientSubActualSID(handle_info->ClientSubList, &sid);
	if (subscription == NULL) {
		if (eventKey == 0) {
			// wait until we've finished processing a subscription 
			//   (if we are in the middle)
			// this is to avoid mistakenly rejecting the first event if we 
			//   receive it before the subscription response
			HandleUnlock();

			// try and get Subscription Lock 
			//   (in case we are in the process of subscribing)
			SubscribeLock();

			// get HandleLock again
			HandleLock();

			if (GetClientHandleInfo(&client_handle, &handle_info) != HND_CLIENT) {
				error_respond(info, HTTP_PRECONDITION_FAILED, event);
				SubscribeUnlock();
				HandleUnlock();
				goto exit_function;
			}

			subscription = GetClientSubActualSID(handle_info->ClientSubList, &sid);
			if (subscription == NULL) {
				error_respond( info, HTTP_PRECONDITION_FAILED, event );
				SubscribeUnlock();
				HandleUnlock();
				goto exit_function;
			}

			SubscribeUnlock();
		} else {
			error_respond( info, HTTP_PRECONDITION_FAILED, event );
			HandleUnlock();
			goto exit_function;
		}
	}

	// success
	error_respond(info, HTTP_OK, event);

	// fill event struct
	UpnpEvent_set_EventKey(event_struct, eventKey);
	UpnpEvent_set_ChangedVariables(event_struct, ChangedVars);
	UpnpEvent_set_SID(event_struct, UpnpClientSubscription_get_SID(subscription));

	// copy callback
	callback = handle_info->Callback;
	cookie = handle_info->Cookie;

	HandleUnlock();

	// make callback with event struct
	// In future, should find a way of mainting
	// that the handle is not unregistered in the middle of a
	// callback
	callback(UPNP_EVENT_RECEIVED, event_struct, cookie);

exit_function:
	ixmlDocument_free(ChangedVars);
	UpnpEvent_delete(event_struct);
}

#endif // INCLUDE_CLIENT_APIS
#endif // EXCLUDE_GENA

