/*++

Copyright (c) 1995  Microsoft Corporation

Module Name:

	net\routing\ipx\sap\sapmain.c

Abstract:

	 SAP DLL main module and thread container.

Author:

	Vadim Eydelman  05-15-1995

Revision History:

--*/
#include "sapp.h"

// Time limit for shutdown broadcast
ULONG	ShutdownTimeout=SAP_SHUTDOWN_TIMEOUT_DEF;
// Indeces of synchronization objects used to control asynchronous
// subsystems of SAP agent

	// Main thread signalling event
#define STOP_EVENT_IDX					0

#define RECV_COMPLETED_IDX				(STOP_EVENT_IDX+1)
	// Timer queue requires attention
#define TIMER_WAKEUP_IDX				(RECV_COMPLETED_IDX+1)

	// Server table aging queue requires processing
#define SERVER_TABLE_TIMER_IDX			(TIMER_WAKEUP_IDX+1)
	// Server table sorted list requires update
#define SERVER_TABLE_UPDATE_IDX			(SERVER_TABLE_TIMER_IDX+1)
	// Adapter change signalled by network driver (for standalone SAP only)
#define ADAPTER_CHG_IDX					(SERVER_TABLE_UPDATE_IDX+1)

	// Number of syncronization objects
#define ROUTING_NUM_OF_OBJECTS			(SERVER_TABLE_UPDATE_IDX+1)
#define STANDALONE_NUM_OF_OBJECTS		(ADAPTER_CHG_IDX+1)
	
#define MAX_OBJECTS						STANDALONE_NUM_OF_OBJECTS



/* Global Data */
// DLL module instance handle
HANDLE	hDLLInstance;

// Handle of main thread
HANDLE  MainThreadHdl;

// Operational state of the agent
ULONG	OperationalState=OPER_STATE_DOWN;
// Lock that protects changes in the state and state transitions
CRITICAL_SECTION OperationalStateLock;
// TRUE between start and stop service calls
volatile BOOLEAN ServiceIfActive=FALSE;
// TRUE between start and stop protocol calls
volatile BOOLEAN RouterIfActive=FALSE;


// TRUE if sap is part of the router, FALSE for standalone SAP agent
// It is computed based on two values above with RouterIfActive having
// precedence.  In stays where it was during transition periods and changes
// only when transition is completed (it can only be changed by the main 
// thread).
volatile BOOLEAN	Routing=FALSE;


/* Local static data */

// Async subsystem synchronization objects
HANDLE	WaitObjects[MAX_OBJECTS] = {NULL};


// Time we will die at when told to shutdown
ULONG	StopTime;


TCHAR   ModuleName[MAX_PATH+1];

// Local prototypes
BOOL WINAPI DllMain(
    HINSTANCE  	hinstDLL,	
    DWORD  		fdwReason,	
    LPVOID  	lpvReserved 
    );


DWORD WINAPI
MainThread (
	LPVOID param
	);

VOID
ReadRegistry (
	VOID
	);

/*++
*******************************************************************
		D l l M a i n
Routine Description:
	Dll entry point to be called from CRTstartup dll entry point (it
		will be actually an entry point for this dll)
Arguments:
	hinstDLL - handle of DLL module 
	fdwReason - reason for calling function 
	lpvReserved - reserved 
Return Value:
	TRUE - process initialization was performed OK
	FALSE - intialization failed
	
*******************************************************************
--*/
BOOL WINAPI DllMain(
    HINSTANCE  	hinstDLL,
    DWORD  		fdwReason,
    LPVOID  	lpvReserved 
    ) {
	STARTUPINFO		info;

	switch (fdwReason) {
		case DLL_PROCESS_ATTACH:	// We are being attached to a new process
			hDLLInstance = hinstDLL;
            GetModuleFileName (hinstDLL, ModuleName,
                        sizeof (ModuleName)/sizeof (ModuleName[0]));
			InitializeCriticalSection (&OperationalStateLock);
			return TRUE;

		case DLL_PROCESS_DETACH:	// The process is exiting
			ASSERT (OperationalState==OPER_STATE_DOWN);
			DeleteCriticalSection (&OperationalStateLock);
		default:					// Not interested in all other cases
			return TRUE;
		}

	}


/*++
*******************************************************************
		C r e a t e A l l C o m p o n e n t s
Routine Description:
	Calls all sap componenets with initialization call and compiles an
	array of synchronization objects from objects returned from each
	individual component
Arguments:
	None
Return Value:
	NO_ERROR - component initialization was performed OK
	other - operation failed (windows error code)
	
*******************************************************************
--*/
DWORD
CreateAllComponents (
	HANDLE RMNotificationEvent
	) {
	DWORD		status;

	DbgInitialize (hDLLInstance);
	ReadRegistry ();
	status = CreateServerTable (
					&WaitObjects[SERVER_TABLE_UPDATE_IDX],
					&WaitObjects[SERVER_TABLE_TIMER_IDX]);
	if (status==NO_ERROR) {
		status = IpxSapCreateTimerQueue (&WaitObjects[TIMER_WAKEUP_IDX]);
		if (status==NO_ERROR) {
			status = CreateInterfaceTable ();
			if (status==NO_ERROR) {
				status = CreateIOQueue (&WaitObjects[RECV_COMPLETED_IDX]);
				if (status==NO_ERROR) {
					status = InitializeLPCStuff ();
					if (status==NO_ERROR) {
						status = CreateFilterTable ();
						if (status==NO_ERROR) {
							status = InitializeWorkers (WaitObjects[RECV_COMPLETED_IDX]);
							if (status==NO_ERROR) {
								WaitObjects[STOP_EVENT_IDX] = 
											CreateEvent (NULL,
															FALSE,	//Autoreset
															FALSE,	// non-signalled
															NULL);
								if (WaitObjects[STOP_EVENT_IDX]!=NULL) {

									if (RMNotificationEvent == NULL)
										status = CreateAdapterPort (&WaitObjects[ADAPTER_CHG_IDX]);
									else
										status = CreateResultQueue (RMNotificationEvent);
									if (status==NO_ERROR)
										return NO_ERROR;
									}
								else {
									status = GetLastError ();
									Trace (DEBUG_FAILURES, "File: %s, line %ld."
										" Could not create stop event (gle:%ld).",
													__FILE__, __LINE__, status);
									}
								DeleteWorkers ();
								}
							DeleteFilterTable ();
							}
						DeleteLPCStuff();
						}
					DeleteIOQueue ();
					}
				DeleteInterfaceTable ();
				}
			IpxSapDeleteTimerQueue ();
			}
		DeleteServerTable ();
		}
	return status;
	}



/*++
*******************************************************************
		D e l e t e A l l C o m p o n e n t s
Routine Description:
	Releases all resources allocated by SAP agent
Arguments:
	None
Return Value:
	NO_ERROR - SAP agent was unloaded OK
	other - operation failed (windows error code)
	
*******************************************************************
--*/
DWORD
DeleteAllComponents (
	void
	) {
	UINT			i;
	DWORD		status;

	EnterCriticalSection (&OperationalStateLock);
	OperationalState = OPER_STATE_DOWN;
	LeaveCriticalSection (&OperationalStateLock);
		// Stop now
	StopTime = GetTickCount ();

	CloseHandle (WaitObjects[STOP_EVENT_IDX]);
	DeleteFilterTable ();
	DeleteLPCStuff ();
	DeleteIOQueue ();
	DeleteInterfaceTable ();
	IpxSapDeleteTimerQueue ();
	DeleteServerTable ();
	DeleteWorkers ();
	DbgStop ();
	return NO_ERROR;
	}

/*++
*******************************************************************
		S t a r t S A P
Routine Description:
	Starts SAP threads
Arguments:
	None
Return Value:
	NO_ERROR - threads started OK
	other (windows error code) - start failed
*******************************************************************
--*/
DWORD
StartSAP (
	VOID
	) {
	DWORD	status;

	status = StartLPC ();
	if (status==NO_ERROR) {
		status = StartIO ();
		if (status==NO_ERROR) {
			DWORD	threadID;
			MainThreadHdl = CreateThread (NULL,
											0,
											&MainThread,
											NULL,
											0,
											&threadID);
			if (MainThreadHdl!=NULL) {
				OperationalState = OPER_STATE_UP;
				return NO_ERROR;
				}
			else {
				status = GetLastError ();
				Trace (DEBUG_FAILURES, "File: %s, line %ld."
					" Failed to launch IO thread (gle:%ld).",
							__FILE__, __LINE__, status);
				}
			StopIO ();
			}
		ShutdownLPC ();
		}

	OperationalState = OPER_STATE_DOWN;
	return status;
	}

/*++
*******************************************************************
		S t o p S A P
Routine Description:
	Signals SAP threads to stop
Arguments:
	No used
Return Value:
	None
*******************************************************************
--*/
VOID
StopSAP (
	void
	) {
	BOOL	res;

	OperationalState = OPER_STATE_STOPPING;
	StopTime = GetTickCount ()+ShutdownTimeout*1000;
	res = SetEvent (WaitObjects[STOP_EVENT_IDX]);
	ASSERTERRMSG ("Could not set stop event in StopSAP ", res);
	}


/*++
*******************************************************************
		R e s u l t R e t r e i v e d C B
Routine Description:
	Async result manager call back routine that signals IO thread when
	stop message is retreived by router manager
Arguments:
	No used
Return Value:
	None
*******************************************************************
--*/
VOID
ResultRetreivedCB (
	PAR_PARAM_BLOCK rslt
	) {
	BOOL res;
	UNREFERENCED_PARAMETER(rslt);
	res = SetEvent (WaitObjects[STOP_EVENT_IDX]);
	ASSERTERRMSG ("Could not set stop event in result retreived CB", res);
	}

/*++
*******************************************************************
		M a i n T h r e a d
Routine Description:
	Thread in which context we'll perform async IO and maintain timer
	queues.
	It is also used to launch and control other threads of SAP agent
Arguments:
	None
Return Value:
	None
*******************************************************************
--*/
DWORD WINAPI
MainThread (
	LPVOID param
	) {
	DWORD	    status;
	UINT	    i;
	DWORD	    nObjects;
	HANDLE	    enumHdl;
    HINSTANCE   hModule;

    hModule = LoadLibrary (ModuleName);

Restart:
    Routing = RouterIfActive;
	if (Routing) {
		nObjects = ROUTING_NUM_OF_OBJECTS;
	}
	else {
		nObjects = STANDALONE_NUM_OF_OBJECTS;
	}

	while ((status = WaitForMultipleObjectsEx (
						nObjects,
						WaitObjects,
						FALSE,				// Wait any
						INFINITE,
						TRUE))!=WAIT_OBJECT_0+STOP_EVENT_IDX) {

		switch (status) {
			case WAIT_OBJECT_0+RECV_COMPLETED_IDX:
				InitReqItem ();
				break;
			case WAIT_OBJECT_0+TIMER_WAKEUP_IDX:
				ProcessTimerQueue ();
				break;
			case WAIT_OBJECT_0+SERVER_TABLE_TIMER_IDX:
				ProcessExpirationQueue ();
				break;
			case WAIT_OBJECT_0+SERVER_TABLE_UPDATE_IDX:
				UpdateSortedList ();
				break;
			case WAIT_OBJECT_0+ADAPTER_CHG_IDX:
				if (!RouterIfActive)
					ProcessAdapterEvents ();
				break;
			case WAIT_IO_COMPLETION:
				break;
			default:
				ASSERTMSG ("Unexpected return code from WaitFromObjects"
							" in IO thread ", FALSE);
				break;
			}
		}

	
	enumHdl = CreateListEnumerator (SDB_HASH_TABLE_LINK,
								0xFFFF,
								NULL,
								INVALID_INTERFACE_INDEX,
								0xFFFFFFFF,
								SDB_DISABLED_NODE_FLAG);

	if (ServiceIfActive || RouterIfActive) {
		if (enumHdl!=NULL)
			EnumerateServers (enumHdl, DeleteNonLocalServersCB, enumHdl);
		}
	else {
		ShutdownLPC ();
		if (enumHdl!=NULL)
			EnumerateServers (enumHdl, DeleteAllServersCB, enumHdl);

		}
    if (enumHdl)
    {
	DeleteListEnumerator (enumHdl);
	}

	if (!RouterIfActive) {
		ShutdownInterfaces (WaitObjects[STOP_EVENT_IDX]);

		ExpireTimerQueue ();
		while ((status = WaitForMultipleObjectsEx (
							ROUTING_NUM_OF_OBJECTS,
							WaitObjects,
							FALSE,				// Wait any
							INFINITE,
							TRUE))!=WAIT_OBJECT_0+STOP_EVENT_IDX) {
			switch (status) {
				case WAIT_OBJECT_0+RECV_COMPLETED_IDX:
						// No more recv requests
					break;
				case WAIT_OBJECT_0+TIMER_WAKEUP_IDX:
					ProcessTimerQueue ();
					break;
				case WAIT_OBJECT_0+SERVER_TABLE_TIMER_IDX:
					ProcessExpirationQueue ();
					break;
				case WAIT_OBJECT_0+SERVER_TABLE_UPDATE_IDX:
					UpdateSortedList ();
					break;
				case WAIT_IO_COMPLETION:
					break;
				default:
					ASSERTMSG ("Unexpected return code from WaitForObjects"
							" in IO thread", FALSE);
				}
			}

		if (!ServiceIfActive) {
			StopIO ();
			StopInterfaces ();
			ExpireTimerQueue ();
			ShutdownWorkers (WaitObjects[STOP_EVENT_IDX]);
			while ((status=WaitForSingleObjectEx (
								WaitObjects[STOP_EVENT_IDX],
								INFINITE,
								TRUE))!=WAIT_OBJECT_0) {
				switch (status) {
					case WAIT_IO_COMPLETION:
						break;
					default:
						ASSERTMSG (
							"Unexpected status when waiting for worker shutdown ",
							FALSE);
						break;
					}
				}
			}
		}


	if (Routing) {
			// Signal completion of stop operation to
			// router manager
		static AR_PARAM_BLOCK	ar;
		ar.event = ROUTER_STOPPED;
		ar.freeRsltCB = ResultRetreivedCB;
		EnqueueResult (&ar);
		while ((status = WaitForSingleObjectEx (
							WaitObjects[STOP_EVENT_IDX],
							INFINITE,
							TRUE))!=WAIT_OBJECT_0) {
			switch (status) {
				case WAIT_IO_COMPLETION:
					break;
				default:
					ASSERTMSG (
						"Unexpected status when waiting for router callback ",
						FALSE);
					break;
				}
			}
		DeleteResultQueue ();
		if (ServiceIfActive) {
			status = CreateAdapterPort (&WaitObjects[ADAPTER_CHG_IDX]);
			if (status==NO_ERROR) {
				EnterCriticalSection (&OperationalStateLock);
				OperationalState = OPER_STATE_UP;
				LeaveCriticalSection (&OperationalStateLock);
				goto Restart;
				}
			else
				ServiceIfActive = FALSE;
			}

    	EnterCriticalSection (&OperationalStateLock);
        CloseHandle (MainThreadHdl);
        MainThreadHdl = NULL;
		LeaveCriticalSection (&OperationalStateLock);
		}
	else {
		DeleteAdapterPort ();
		WaitObjects [ADAPTER_CHG_IDX] = NULL;
		if (RouterIfActive) {
			EnterCriticalSection (&OperationalStateLock);
			OperationalState = OPER_STATE_UP;
			LeaveCriticalSection (&OperationalStateLock);
			goto Restart;
			}
		}

    // Make sure all threads get a chance to complete
    Sleep (1000);
	DeleteAllComponents ();
    FreeLibraryAndExitThread (hModule, 0);
	return 0;
	}

#define MYTEXTW1(str) L##str
#define MYTEXTW2(str) MYTEXTW1(str)

#define REGISTRY_PARAM_ENTRY(name,val) {		\
		NULL,									\
			RTL_QUERY_REGISTRY_DIRECT,			\
			MYTEXTW2(name##_STR),				\
			&val,								\
			REG_DWORD,							\
			&val,								\
			sizeof (DWORD)						\
	}

#define REGISTRY_CHECK(name,val)	{									\
	if (val<name##_MIN) {												\
		Trace (DEBUG_FAILURES, name##_STR " is to small %ld!", val);	\
		val = name##_MIN;												\
		}																\
	else if (val>name##_MAX) {											\
		Trace (DEBUG_FAILURES, name##_STR " is to big %ld!", val);		\
		val = name##_MAX;												\
		}																\
	if (val!=name##_DEF)												\
		Trace (DEBUG_FAILURES, name##_STR" is set to %ld.", val);		\
	}

#define REGISTRY_CHECK_DEF(name,val)	{								\
	if (val<name##_MIN) {												\
		Trace (DEBUG_FAILURES, name##_STR " is to small %ld!", val);	\
		val = name##_DEF;												\
		}																\
	else if (val>name##_MAX) {											\
		Trace (DEBUG_FAILURES, name##_STR " is to big %ld!", val);		\
		val = name##_DEF;												\
		}																\
	if (val!=name##_DEF)												\
		Trace (DEBUG_FAILURES, name##_STR " is set to %ld.", val);		\
	}


VOID
ReadRegistry (
	VOID
	) {
	DWORD		rc;
	HKEY		hKey;
static RTL_QUERY_REGISTRY_TABLE	ParamTable[] = {
	{	NULL,
			RTL_QUERY_REGISTRY_SUBKEY,
			L"Parameters" },
	REGISTRY_PARAM_ENTRY (SAP_UPDATE_INTERVAL,	UpdateInterval),
	REGISTRY_PARAM_ENTRY (SAP_AGING_TIMEOUT,	ServerAgingTimeout),
	REGISTRY_PARAM_ENTRY (SAP_WAN_UPDATE_MODE,	WanUpdateMode),
	REGISTRY_PARAM_ENTRY (SAP_WAN_UPDATE_INTERVAL,WanUpdateInterval),
	REGISTRY_PARAM_ENTRY (SAP_MAX_UNPROCESSED_REQUESTS,
												MaxUnprocessedRequests),
	REGISTRY_PARAM_ENTRY (SAP_RESPOND_FOR_INTERNAL,
												RespondForInternalServers),
	REGISTRY_PARAM_ENTRY (SAP_DELAY_RESPONSE_TO_GENERAL,
												DelayResponseToGeneral),
	REGISTRY_PARAM_ENTRY (SAP_DELAY_CHANGE_BROADCAST,
												DelayChangeBroadcast),
	REGISTRY_PARAM_ENTRY (SAP_SDB_MAX_HEAP_SIZE,SDBMaxHeapSize),
	REGISTRY_PARAM_ENTRY (SAP_SDB_SORT_LATENCY,	SDBSortLatency),
	REGISTRY_PARAM_ENTRY (SAP_SDB_MAX_UNSORTED,	SDBMaxUnsortedServers),
	REGISTRY_PARAM_ENTRY (SAP_TRIGGERED_UPDATE_CHECK_INTERVAL,
												TriggeredUpdateCheckInterval),
	REGISTRY_PARAM_ENTRY (SAP_MAX_TRIGGERED_UPDATE_REQUESTS,
												MaxTriggeredUpdateRequests),
	REGISTRY_PARAM_ENTRY (SAP_SHUTDOWN_TIMEOUT,	ShutdownTimeout),
	REGISTRY_PARAM_ENTRY (SAP_REQUESTS_PER_INTF,NewRequestsPerInterface),
	REGISTRY_PARAM_ENTRY (SAP_MIN_REQUESTS,		MinPendingRequests),
	{
		NULL
	}
};

	rc = RegOpenKeyEx (HKEY_LOCAL_MACHINE,
			TEXT (SAP_ROUTER_REGISTRY_KEY_STR),
			0,
			KEY_READ,
			&hKey
			);
	if ((rc!=NO_ERROR) && !Routing) {
		rc = RegOpenKeyEx (HKEY_LOCAL_MACHINE,
			TEXT (SAP_SERVICE_REGISTRY_KEY_STR),
			0,
			KEY_READ,
			&hKey
			);
		}

	if (rc==NO_ERROR) {
		NTSTATUS	status;
		status = RtlQueryRegistryValues(
			 RTL_REGISTRY_HANDLE,
			 (PWSTR)hKey,
			 ParamTable,
			 NULL,
			 NULL);
		if (NT_SUCCESS (status)) {
			REGISTRY_CHECK (SAP_UPDATE_INTERVAL,	UpdateInterval);
			REGISTRY_CHECK (SAP_AGING_TIMEOUT,		ServerAgingTimeout);
			REGISTRY_CHECK_DEF (SAP_WAN_UPDATE_MODE, (int)WanUpdateMode);
			REGISTRY_CHECK (SAP_WAN_UPDATE_INTERVAL,WanUpdateInterval);
			REGISTRY_CHECK (SAP_MAX_UNPROCESSED_REQUESTS,
													MaxUnprocessedRequests);
			REGISTRY_CHECK_DEF (SAP_RESPOND_FOR_INTERNAL,
													(int) RespondForInternalServers);
			REGISTRY_CHECK (SAP_DELAY_RESPONSE_TO_GENERAL,
													(int) DelayResponseToGeneral);
			REGISTRY_CHECK (SAP_DELAY_CHANGE_BROADCAST,
												(int) DelayChangeBroadcast);
			REGISTRY_CHECK (SAP_SDB_MAX_HEAP_SIZE,	SDBMaxHeapSize);
			REGISTRY_CHECK (SAP_SDB_SORT_LATENCY,	SDBSortLatency);
			REGISTRY_CHECK (SAP_SDB_MAX_UNSORTED,	SDBMaxUnsortedServers);
			REGISTRY_CHECK (SAP_TRIGGERED_UPDATE_CHECK_INTERVAL,
													TriggeredUpdateCheckInterval);
			REGISTRY_CHECK (SAP_MAX_TRIGGERED_UPDATE_REQUESTS,
													MaxTriggeredUpdateRequests);
			REGISTRY_CHECK (SAP_SHUTDOWN_TIMEOUT,	ShutdownTimeout);
			REGISTRY_CHECK (SAP_REQUESTS_PER_INTF,	NewRequestsPerInterface);
			REGISTRY_CHECK (SAP_MIN_REQUESTS,		MinPendingRequests);
			}
		RegCloseKey (hKey);
		}
	}