/*
 * oidParser.cpp - parse an Intel-style OID, with the assistance of dumpasn1.cfg
 */
 
#include <Security/cssmtype.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "oidParser.h"
#include "fileIo.h"
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

/* get config file from .. or from . */
#define 		CONFIG_FILE_NAME	"dumpasn1.cfg"
static char 	*CONFIG_FILE1 = 	"../"CONFIG_FILE_NAME;
static char 	*CONFIG_FILE2 = 	CONFIG_FILE_NAME;
/* or from here via getenv */
#define 		CONFIG_FILE_ENV 	"LOCAL_BUILD_DIR"

static char 	*OID_ENTRY_START = "OID = ";
static char 	*OID_DESCR_START = "Description = ";
/*
 * Read entire file with extra bytes left over in the mallocd buffer. 
 */
int readFileExtra(
	char			*fileName,
	unsigned		extraBytes,
	unsigned char	**bytes,		// mallocd and returned
	unsigned		*numBytes)		// returned
{
	int rtn;
	int fd;
	unsigned char *buf;
	struct stat	sb;
	unsigned size;
	
	*numBytes = 0;
	*bytes = NULL;
	fd = open(fileName, O_RDONLY, 0);
	if(fd <= 0) {
		return 1;
	}
	rtn = fstat(fd, &sb);
	if(rtn) {
		goto errOut;
	}
	size = sb.st_size;
	buf = (unsigned char *)malloc(size + extraBytes);
	if(buf == NULL) {
		rtn = ENOMEM;
		goto errOut;
	}
	rtn = lseek(fd, 0, SEEK_SET);
	if(rtn < 0) {
		goto errOut;
	}
	rtn = read(fd, buf, (size_t)size);
	if(rtn != (int)size) {
		if(rtn >= 0) {
			printf("readFile: short read\n");
		}
		rtn = EIO;
	}
	else {
		rtn = 0;
		*bytes = buf;
		*numBytes = size;
	}
errOut:
	close(fd);
	return rtn;
}

/*
 * Attempt to read dumpasn1.cfg from various places. If we can't find it, 
 * printOid() function will just print raw bytes as it
 * would if the .cfg file did not contain the desired OID.
 */
static CSSM_DATA_PTR readConfig()
{
	CSSM_DATA_PTR	configData = NULL;
	int				rtn;
	
	configData = (CSSM_DATA_PTR)malloc(sizeof(CSSM_DATA));
	if(configData == NULL) {
		return NULL;
	}
	/* malloc one extra byte, we'll null it later */
	rtn = readFileExtra(CONFIG_FILE1, 1, &configData->Data, 
		(unsigned *)&configData->Length);
	if(rtn) {
		rtn = readFileExtra(CONFIG_FILE2, 1, &configData->Data, 
				(unsigned *)&configData->Length);
	}
	if(rtn) {
		char fileName[100];
		char *localBuildDir  = getenv(CONFIG_FILE_ENV);
		if(localBuildDir == NULL) {
			rtn = 1;
		}
		else {
			sprintf(fileName,  "%s/%s", localBuildDir, CONFIG_FILE_NAME);
			rtn = readFileExtra(fileName, 1, &configData->Data, 
					(unsigned *)&configData->Length);
		}
	}
	if(rtn == 0) {
		/* make the whole shebang one long C string */
		configData->Data[configData->Length++] = '\0';
		return configData;
	}
	else {
		printf("""warning: no OID parser config file\n");
		free(configData);
		return NULL;
	}
}

/*
 * The heart of this module. 
 *
 * -- Convert Intel-style OID to a string which might be found 
 *    in the config file
 * -- search config file for that string
 * -- if found, use that entry in config file to output meaningful
 *    string and return CSSM_TRUE. Else return CSSM_FALSE.
 */
static CSSM_BOOL parseOidWithConfig(
	const CSSM_DATA_PTR configData, 
	const CSSM_OID_PTR	oid, 
	char				*strBuf)
{
	char				*fullOidStr = NULL;
	char				*ourEntry = NULL;
	char				*nextEntry = NULL;
	char				*descStart = NULL;
	char				*cp;
	unsigned			i;
	CSSM_BOOL			brtn;
	char				*nextCr;		// next CR if any
	char				*nextNl;		// next NL if any
	char				*eol;			// end of line
	int					len;
	
	if(configData == NULL) {
		return CSSM_FALSE;
	}
	
	/* cook up a full OID string, with tag and length */
	fullOidStr = (char *)malloc((3 * oid->Length) +		
												// 2 chars plus space per byte
		strlen(OID_ENTRY_START) +				// "OID = "
		6 +										// 06 xx - tag and length
		1);										// NULL
	if(fullOidStr == NULL) {
		return CSSM_FALSE;
	}
	/* subsequent errors to errOut: */
	
	sprintf(fullOidStr, "OID = 06 %02X", (unsigned)oid->Length);
	cp = fullOidStr + strlen(fullOidStr);
	for(i=0; i<oid->Length; i++) {
		/* move cp to current end of string */
		cp += strlen(cp);
		/* add one byte */
		sprintf(cp, " %02X", oid->Data[i]);
	}
	
	/* 
	 * Let's play it loose and assume that there are no embedded NULLs
	 * in the config file. Thus we can use the spiffy string functions
	 * in stdlib. 
	 */
	ourEntry = strstr((char *)configData->Data, fullOidStr);
	if(ourEntry == NULL) {
		brtn = CSSM_FALSE;
		goto errOut;
	}
	
	/* get position of NEXT full entry - may be NULL (end of file) */
	nextEntry = strstr(ourEntry+1, OID_ENTRY_START);
	
	/* get position of our entry's description line */
	descStart = strstr(ourEntry+1, OID_DESCR_START);
	
	/* handle not found/overflow */
	if( (descStart == NULL) ||			// no more description lines
	    ( (descStart > nextEntry) &&  	// no description in THIS entry
	      (nextEntry != NULL) ) ) {		// make sure this is valid
		brtn = CSSM_FALSE;
		goto errOut;
	}
	
	/* set descStart to after the leader */
	descStart += strlen(OID_DESCR_START);
	
	/* 
	 * descStart points to the text we're interested in.
	 * First find end of line, any style. 
	 */
	nextNl = strchr(descStart, '\n');
	nextCr = strchr(descStart, '\r');
	if((nextNl == NULL) && (nextCr == NULL)) {
		/* no line terminator, go to eof */
		eol = (char *)configData->Data + configData->Length;
	}
	else if(nextCr == NULL) {
		eol = nextNl;
	}
	else if(nextNl == NULL) {
		eol = nextCr;
	}
	else if(nextNl < nextCr) {
		/* both present, take first one */
		eol = nextNl;
	}
	else {
		eol = nextCr;
	}
	
	/* caller's string buf = remainder of description line */
	len = eol - descStart;
	if(len > (OID_PARSER_STRING_SIZE - 1)) {
		/* fixed-length output buf, avoid overflow */
		len = OID_PARSER_STRING_SIZE - 1;
	}
	memcpy(strBuf, descStart, len);
	strBuf[len] = '\0';
	brtn = CSSM_TRUE; 
errOut:
	if(fullOidStr != NULL) {
		free(fullOidStr);
	}
	return brtn;
}

/*** OidParser class ***/
OidParser::OidParser(bool noConfig)
{
	if(noConfig) {
		configData = NULL;
	}
	else {
		configData = readConfig();
	}
}

OidParser::~OidParser()
{
	if(configData == NULL) {
		return;
	}
	if(configData->Data != NULL) {
		free(configData->Data);
	}
	free(configData);
}

/*
 * Parse an Intel-style OID, generating a C string in caller-supplied buffer.
 */
void OidParser::oidParse(
	const unsigned char	*oidp,
	unsigned			oidLen,
	char 				*strBuf)
{
	unsigned i;
	CSSM_OID oid;
	
	oid.Data = (uint8  *)oidp;
	oid.Length = oidLen;
	
	if((oidLen == 0) || (oidp == NULL)) {
		strcpy(strBuf, "EMPTY");
		return;
	}
	if(parseOidWithConfig(configData, &oid, strBuf) == CSSM_FALSE) {
		/* no config file, just dump the bytes */
		char cbuf[8];
		
		sprintf(strBuf, "OID : < 06 %02X ", (unsigned)oid.Length);
		for(i=0; i<oid.Length; i++) {
			sprintf(cbuf, "%02X ", oid.Data[i]);
			strcat(strBuf, cbuf);
		}
		strcat(strBuf, ">");
	}
}


