 /********************************************************************** 
 * Copyright (c) 2005, 2006 IBM Corporation and others. 
 * All rights reserved.   This program and the accompanying materials 
 * are made available under the terms of the Eclipse Public License v1.0 
 * which accompanies this distribution, and is available at 
 * http://www.eclipse.org/legal/epl-v10.html         
 * $Id: CBFilter.c.txt,v 1.2 2006/02/06 20:18:14 nmehrega Exp $ 
 * 
 * Contributors: 
 * IBM - Initial API and implementation 
 **********************************************************************/ 


/*
 * The pieces of code below implement the callback based filtering in the
 * Probe Agent Extension. This code was implemented and tested but then pulled 
 * out for the luck of per-probe filtering capabilities. However this is still 
 * a good functionally complete example of global filtering based on the 
 * instrumentation callback.
 */
   


/*******************************************************************************
 * Filters list
 *
 *
 */

typedef struct _MethodInfo
{
	const char* PackageName;
	const char* ClassName;
	const char* MethodName;
	const char* MethodSig;
} MethodInfo_t;

typedef enum {
	FT_INCLUDE, 
	FT_EXCLUDE
} FilterType_t; 

typedef struct _FilterElement
{
	FilterType_t FilterType; 
	struct _MethodInfo* pMethodInfo;
	struct _FilterElement* pNext;
} FilterElement_t;

typedef struct _FilterList
{
	FilterElement_t* pBegin;
	FilterElement_t* pEnd;
} FilterList_t;

static FilterList_t FilterList = {NULL, NULL};

/*******************************************************************************
 * Filter management functions
 *******************************************************************************/

/*******************************************************************************
 * Wildcard Matching Function
 *
 * Matches a string to a wildcard expression
 * Wildcard expressions are in the form
 * (?'*'?[A-Z,a-z,0-9]*?'*')*
 *
 */
static BOOL match_wildcard(const char* expression, const char* str)
{
	BOOL match = TRUE;

	char* wildcard = (char*)malloc(strlen(expression) + 1);
	char* next_tail = (char*)str;
	char* next_match;
	char* token;

	strcpy(wildcard, expression);
	token = strtok(wildcard, "*");
	if(*expression != '*') 
	{	// If there is no heading star we need exact match
		next_match = strstr(str, token);
		if(next_match != str)
		{
			token = NULL;
			match = FALSE;
		}
	}
	while(token != NULL)
	{
		next_match = strstr(next_tail, token);
		if(NULL == next_match)
		{
			match = FALSE;
			break;
		}
		next_tail = next_match + strlen(token);
		token = strtok(NULL, "*");
	}
	free(wildcard);
	return match;
}

/*******************************************************************************
 ** 
 */
static MethodInfo_t* new_method_info(
	const char* package_name,
	const char* class_name,
	const char* method_name,
	const char* method_sig)
{
	MethodInfo_t* pnew = malloc(sizeof(MethodInfo_t));
	if(NULL != pnew)
	{
		pnew->PackageName = strdup(package_name);
		pnew->ClassName = strdup(class_name);
		pnew->MethodName = strdup(method_name);
		pnew->MethodSig = strdup(method_sig);
	}
	return pnew;
}

/*******************************************************************************
 ** 
 */
static void delete_method_info(MethodInfo_t* method_info)
{
	free((void*)method_info->PackageName);
	free((void*)method_info->ClassName);
	free((void*)method_info->MethodName);
	free((void*)method_info->MethodSig);
	free(method_info);
}

/*******************************************************************************
 **
 */
static MethodInfo_t* new_method_info_from_rac(char* filter_string)
{
	enum token_type {
		TOK_UNKNOWN,
		TOK_RULE,
		TOK_PACKAGE,
		TOK_CLASS,
		TOK_METHOD,
		TOK_SIGNATURE,
		TOK_END
	} token_type = TOK_UNKNOWN;
	const char* filter_delim = "\t ";
	const char* token = NULL;
	MethodInfo_t* pInfo = NULL;

	pInfo = malloc(sizeof(MethodInfo_t));
	if(NULL == pInfo)
	{
		//TODO: report a serious problem here
		return NULL;
	}
	// Parse the input string and populate the method info;
	token = strtok(filter_string, filter_delim);
	while(NULL != token && token_type < TOK_END)
	{
		token_type++;
		switch(token_type)
		{
			case TOK_RULE:
				if(stricmp(token, "RULE") != 0)
				{
					// TODO: report error
					token_type = TOK_END;
				}
				break;
			case TOK_PACKAGE:
				pInfo->PackageName = strdup(token);
				break;
			case TOK_CLASS:
				pInfo->ClassName  = strdup(token);
				break;
			case TOK_METHOD:
				pInfo->MethodName = strdup(token);
				break;
			case TOK_SIGNATURE:
				pInfo->MethodSig = strdup(token);
				break;
			default:
				//TODO: Report filter error here
				break;
		}
		token = strtok(NULL, filter_delim);
	}
	if(TOK_END != token_type)
	{
		//TODO: Report filter error here
	}

	return pInfo;
}

/*******************************************************************************
 **
 */
static MethodInfo_t* new_method_info_from_string(char* str)
{
	MethodInfo_t* pInfo = NULL;

	char* pBuf = malloc(strlen(str) + 1);
	char* pScan = NULL;

	pInfo = malloc(sizeof(MethodInfo_t));
	if(NULL == pInfo || NULL == pBuf)
	{
		//TODO: report a serious problem here
		return NULL;
	}
	strcpy(pBuf, str);
	pInfo->PackageName = NULL;
	pInfo->ClassName = NULL;
	pInfo->MethodName = NULL;
	pInfo->MethodSig = NULL;
	// Process signature
	pScan = strrchr(pBuf, '(');
	if(NULL != pScan)
	{
		pInfo->MethodSig = strdup(pScan);
		*pScan = 0;
	}
	// Process method name
	pScan = strrchr(pBuf, '.');
	if(NULL != pScan)
	{
		pInfo->MethodName = strdup(pScan + 1);
		*pScan = 0;
	}
	// Process class name
	pScan = strrchr(pBuf, '/');
	if(NULL != pScan)
	{
		pInfo->ClassName = strdup(pScan + 1);
		*pScan = 0;
	}
	else
	{
		pInfo->ClassName = strdup(pBuf);
		*pBuf = 0;
	}
	// Process package
	// By this time pBuf has just the package name or empty string
	if(*pBuf != 0)
	{
		pInfo->PackageName = strdup(pBuf);
	}
	else
	{	// Global package
		pInfo->PackageName = strdup(".");
	}
	
	free(pBuf);
	return pInfo;
}

/*******************************************************************************
 **
 */
static BOOL method_info_match(MethodInfo_t* method_template, MethodInfo_t* method_info)
{
	// Match the package
	if(NULL != method_template->PackageName && NULL != method_info->PackageName
	&& !match_wildcard(method_template->PackageName, method_info->PackageName))
		return FALSE;
	// Match the class
	if(NULL != method_template->ClassName && NULL != method_info->ClassName
	&& !match_wildcard(method_template->ClassName,method_info->ClassName))
		return FALSE;
	// Match the name
	if(NULL != method_template->MethodName && NULL != method_info->MethodName
	&& !match_wildcard(method_template->MethodName, method_info->MethodName))
		return FALSE;
	// Match the signature
	if(NULL != method_template->MethodSig && NULL != method_info->MethodSig
	&& !match_wildcard(method_template->MethodSig, method_info->MethodSig))
		return FALSE;
	return TRUE;
}

/*******************************************************************************
 ** 
 */
static FilterElement_t* new_filter_element(
	FilterType_t filter_type,
	MethodInfo_t* method_info)
{
	FilterElement_t* pnew = NULL;
	pnew = malloc(sizeof(FilterElement_t));
	if(NULL != pnew)
	{
		pnew->FilterType = filter_type;
		pnew->pMethodInfo = method_info;
		pnew->pNext = NULL;
	}
	else
	{
		// TODO: Report error
	}
	return pnew;
}

/*******************************************************************************
 ** 
 */
static void delete_filter_element(FilterElement_t* filter_element)
{
	free((void*)filter_element->FilterType);
	delete_method_info(filter_element->pMethodInfo);
	free(filter_element);
}

/*******************************************************************************
 ** 
 */
static FilterElement_t* new_filter_element_from_rac(const char* str)
{
	FilterElement_t* pnew = NULL;
	MethodInfo_t* pmethod = NULL;
	char* method_info = NULL;
	char* filter_type_str = NULL;
	char* last_delim = strrchr(str, ' ');
	FilterType_t filter_type = FT_INCLUDE;
	int   method_info_length = last_delim - str;

	// Split the string into method info and filter type
	method_info = malloc(method_info_length + 1);
	strncpy(method_info, str, method_info_length);
	method_info[method_info_length] = 0;
	filter_type_str = strdup(last_delim + 1);
	pmethod = new_method_info_from_rac(method_info);
	filter_type = stricmp(filter_type_str, "INCLUDE") == 0 
                ? FT_INCLUDE 
				: FT_EXCLUDE;
	pnew = new_filter_element(filter_type, pmethod);
	return pnew;
}

/*******************************************************************************
 * Add new filter to the list
 */
static void add_filter(FilterList_t* filter_list, FilterElement_t* filter_element)
{
	if(filter_list->pBegin == NULL)
	{
		filter_list->pBegin = filter_element;
		filter_list->pEnd = filter_list->pBegin;
	}
	else
	{
		filter_list->pEnd->pNext = filter_element;
		filter_list->pEnd = filter_element;
	}
	filter_element->pNext = NULL; // just in case assure the list termination
}

/*******************************************************************************
 * Process filters received from RAC
 */
static void process_filters_from_rac(char* data, int num)
{
	int processed = 0;
	char* pNext = data;
	FilterElement_t* pfe;

	while(processed < num)
	{
		// Process the next filter string
		pfe = new_filter_element_from_rac(pNext);
		add_filter(&FilterList, pfe);
		// Advance
		processed ++;
		pNext = pNext + strlen(pNext) + 1;
	}
}

/*******************************************************************************
 * Apply Filters to a Method Spec.
 * 
 * Returns TRUE if a method did go through and should be instrumented
 * Returns FALSE if a method was rejected by filters
 */
static BOOL apply_filters(FilterList_t* filter_list, MethodInfo_t* method_info)
{
	BOOL match = FALSE;
	BOOL accept = FALSE;
	FilterElement_t* pFilter = filter_list->pBegin;
	while(NULL != pFilter)
	{
		match = method_info_match(pFilter->pMethodInfo, method_info);
		if(match)
		{
			if(pFilter->FilterType == FT_INCLUDE)
			{
				accept = TRUE;
			}
			else if(pFilter->FilterType == FT_EXCLUDE)
			{
				accept = FALSE;
			}
			break;
		}
		pFilter = pFilter->pNext;
	}
	
	return accept;
}

/*******************************************************************************
 * Cleanup a list of filters
 */
static void cleanup_filters(FilterList_t* filter_list)
{
	FilterElement_t* pFilter = NULL;
	FilterElement_t* pNext = filter_list->pBegin;
	while(pNext != NULL)
	{
		pFilter = pNext;
		pNext = pFilter->pNext;
		delete_filter_element(pFilter);
	}
}



/*------------------------------------------------------------------------------
 * This function is passed to the BCI engine, and the engine calls it
 * in order to ask, "are you *sure* you want me to instrument this?"
 * We should say no if we haven't seen JVM_INIT_DONE yet, and also
 * if the class name matches our (harsh, hard-coded) class list
 * specified above in szFilters.
 */
static unsigned int name_buffer_size = 256;	// initial buffer size
static int class_name_size = 0;		// last recorded class name		
static char* name_buffer = NULL;

static int BCICallback(const char* i_pInfo, size_t i_cbInfo, unsigned i_wFlags)
{
	int nRet = 1;
	int i = 0;

	if(name_buffer == NULL)
	{	// Preallocate the name buffer
		name_buffer = malloc(name_buffer_size);
	}
	if(!is_init_done)
	{	// Make sure the JVM initialization is done. 
		// If not return 0 (not ok to instrument)
		nRet = 0;
	}
	else if(i_wFlags == BCIENGINTERFACE_CALLBACK_MODULE)
	{
		struct _DenyInstrList* deny_item;

		if(i_cbInfo >= name_buffer_size)
		{
			name_buffer_size = i_cbInfo + 1;
			name_buffer = realloc(name_buffer, name_buffer_size);
		}
		class_name_size = i_cbInfo;
		memcpy(name_buffer, i_pInfo, i_cbInfo);
		name_buffer[i_cbInfo] = 0;

		/* look on the list */
		deny_item = DenyInstrList;
		while (deny_item != NULL) {
			if(strcmp(deny_item->className, name_buffer) == 0)
			{
				nRet = 0;
				break;
			}
			deny_item = deny_item->next;
		}
	}
	else if(i_wFlags == BCIENGINTERFACE_CALLBACK_METHOD)
	{
		/*
		 * Apply RAC filters to the method about to be instrumented
		 */
		if(FilterList.pBegin != NULL)
		{
			MethodInfo_t* pmti;
			
			if(class_name_size + i_cbInfo >= name_buffer_size + 1)
			{
				name_buffer_size = i_cbInfo + 2;	// save place for the delimiter
													// and the trailing zero
				name_buffer = realloc(name_buffer, name_buffer_size);
			}
			name_buffer[class_name_size] = '.';
			memcpy(name_buffer + class_name_size  + 1, i_pInfo, i_cbInfo);
			name_buffer[class_name_size  + 1 + i_cbInfo] = '\0';
			pmti = new_method_info_from_string(name_buffer);
			nRet = apply_filters(&FilterList, pmti);
			delete_method_info(pmti);
		}
	}
	else if (i_wFlags == BCIENGINTERFACE_CALLBACK_MODULE_INSTR) {
		/*
		 * This is a report that this class was actually instrumented.
		 * If the logging level is high enough, write a message saying so.
		 * (Because of the way the API is defined, we must make a null-terminated string by hand.)
		 */
		if (logLevel <= LOG_LEVEL_FINEST) {
			char* nameBuf = (char*)malloc(i_cbInfo + 1);
			memcpy(nameBuf, i_pInfo, i_cbInfo);
			nameBuf[i_cbInfo] = '\0';
			logMessage(LOG_LEVEL_FINEST, "IWAC0101I: Class got some instrumentation", nameBuf);
			free(nameBuf);
		}
	}
	return nRet;
}



/*******************************************************************************
 * Please note that the filtering implementad above depends on the custom
 * RAC command PROBE_FILTERS.
 * The structure of this command is defined by implementation of the function
 * named process_filters_from_rac. 
 *
 */

void command_handler(ra_command_t *command)
{
	switch (command->tag) {
		case RA_BINARY_CUSTOM_COMMAND:
		{
			unsigned int	cbHeader = 0;
			unsigned int	cbData = 0;
			unsigned char*	pbHeader;
			unsigned char*	pbData;
			char*			szClassName = NULL;

			unsigned char* pScan = command->info.custom_command.message.data;

			/* Process command header */
			pbHeader = pScan;
			cbHeader = strlen(pScan);
			pScan += cbHeader;
			pScan++;

			/* Process command data
			 * Workbench always sends four bytes big-endian
			 * (because it's a Java operation on a ByteBuffer).
			 * Combine them into an int, even if this machine is different.
			 * (Waste of time if this machine is also big-endian. Sue me.)
			 */
			cbData = *pScan++;
			cbData = (cbData << 8) | *pScan++;
			cbData = (cbData << 8) | *pScan++;
			cbData = (cbData << 8) | *pScan++;

			pbData = pScan;
			/* Execute custom command (so far only BCI engine is using this feature). */
			if(strcmp((char*)pbHeader, "PROBE_SCRIPT") == 0)
			{
				jint nRet = JNI_OK;
				
				logMessage(LOG_LEVEL_FINE, "IWAC0118I: PROBE_SCRIPT command received", NULL);
				if (logLevel <= LOG_LEVEL_FINER) {
					/* If we are logging at "finer" level then show contents of engine scripts */
					/* Make a copy of the engine script and turn nulls into newlines for printing */
					/* ASCII/EBCDIC note: because the script came through in a binary custom command, */
					/* the script is ASCII, not EBCDIC. That's what logMessage expects. */
					unsigned char* strbuf = malloc(cbData + 1);
					unsigned char* p;
					memcpy(strbuf, pbData, cbData);
					strbuf[cbData] = '\0';
					for (p = strbuf; p < strbuf + cbData; p++) {
						if (*p == '\0') *p = '\n';
					}
					logMessage(LOG_LEVEL_FINER, "IWAC0119I: PROBE_SCRIPT contents (multiple lines)", strbuf);
					free(strbuf);
				}
				if(!BCIEngineActive)
				{
					nRet = initializeProbeEngine();
				}
				if(nRet == JNI_OK)
				{
					processRemoteProbeScript(cbData, pbData);
				}
			}
			else if(strcmp((char*)pbHeader, "REMOTE_CLASS") == 0)
			{
				pScan += cbData;
				szClassName = pScan;
				logMessage(LOG_LEVEL_FINE, "IWAC0120I: REMOTE_CLASS command received:", szClassName);
				instantiateRemoteClass(cbData, pbData, szClassName);
			}
			else if(strcmp((char*)pbHeader, "PROBE_FILTERS") == 0)
			{
				logMessage(LOG_LEVEL_FINE, "IWAC0128I: PROBE_FILTERS command received", ".");
				// TODO: dump filters for the 'Finest' log level
				process_filters_from_rac(pbData, cbData);
			}
			break;
		}
	}
}

