/*
* Copyright (c) 2005-2009 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description:   
* This file was autogenerated by rpcgen, but should be modified by the developer.
* Make sure you don't use the -component_mod flag in future or this file will be overwritten.
* Fri Oct 17 09:37:12 2003
*
*/




/****************************************************************************************
 * 
 * System Includes
 * 
 ***************************************************************************************/
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifndef WIN32
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif


/****************************************************************************************
 * 
 * Local Includes
 * 
 ***************************************************************************************/
#include "CSvcPppcontroller.h"
#include "CSPppcontroller.h"
#include "../include/standard_unix.h"
#include "../include/strncpynt.h"
#include "../DynamicsConfigurationLibrary/file_utilities.h"


/****************************************************************************************
 * 
 * Definitions
 * 
 ***************************************************************************************/
#define MAXLOGNAME				64
#define MAXCOMMANDLINE			2048
#define MAXLOGLINELENGTH		1024
#define PPPD_BINARY				"./pppd"
#define GATEWAY_BINARY			"./pppdgateway"
#define MAXINTERFACENAMESIZE	64
#define MAXLISTLINES			32
#define CONNECTED_STRING		"Script /etc/ppp/ip-up finished"
#define DISCONNECTED_STRING		"Connection terminated"

#define IS_INLINE_WHITESPACE(c)	(((c) == ' ')||((c) == '\t'))

/****************************************************************************************
 * 
 * Implementation
 * 
 ***************************************************************************************/
CSPppcontroller::CSPppcontroller()
{
	iProcess = NULL;
	iSessionStatus = SS_NOT_STARTED;
}

CSPppcontroller::~CSPppcontroller()
{
	assert( iProcess == NULL );
}

int CSPppcontroller::GetKey()
{
	return iKey;
}

void CSPppcontroller::SetKey( int aKey )
{
	iKey = aKey;
}


/****************************************************************************************
 * 
 * PUBLIC FUNCTION: cstr_startpppsession
 * 
 ***************************************************************************************/
TResult CSPppcontroller::cstr_startpppsession( TPppSessionConfig aArgs )
{
	int err, errcode;
	TResult rv = { ERR_NONE, 0, 0 };
	char logfilename[MAXLOGNAME];
	char commandline[MAXCOMMANDLINE];
	TCAProcessError perr;

	// check that we are not already started
	assert( iProcess == NULL );

	// save the config
	iSessionConfig = aArgs;

	// construct the name of the logfile
	snprintf( logfilename, MAXLOGNAME, "pppd%04d.log", iKey );

	// remove the file if it exists -- if an error other than file not exists happens then exit
	err = unlink( logfilename );
	if( (err != 0) && (errno != ENOENT) ) {
		rv.iStandardResult = ERR_LOG_FILE_ERROR;
		rv.iExtendedCode = 0;
		rv.iSystemError = errno;
		return rv;
	}

	// construct the command line for the PPP session
#ifndef __UNIT_TEST_13
	snprintf( commandline, MAXCOMMANDLINE, "%s pty \'%s %s %s %d\' logfile %s %s", PPPD_BINARY, GATEWAY_BINARY, 
		  iSessionConfig.iMobsterAddress, iSessionConfig.iMobsterPort, iSessionConfig.iMTID, logfilename, iSessionConfig.iSessionConfiguration );
#else
	snprintf( commandline, MAXCOMMANDLINE, "%s notty logfile %s %s", PPPD_BINARY, logfilename, iSessionConfig.iSessionConfiguration );
#endif
	
	// now start the process
	iProcess = new CAProcess();
	assert( iProcess != NULL );
	perr = iProcess->StartProcess( commandline, &errcode, false, false, true );
	if( perr != CAE_NONE ) {
		rv.iStandardResult = ERR_START_PROCESS_ERROR;
		rv.iExtendedCode = (int)perr;
		rv.iSystemError = errcode;
		delete iProcess;
		iProcess = NULL;
		return rv;
	}

	// done - success
	return rv;
}


/****************************************************************************************
 * 
 * PUBLIC FUNCTION: dstr_removepppsession
 * 
 ***************************************************************************************/
TResult CSPppcontroller::dstr_removepppsession( int aArgs, int *aDeleteInstance )
{
	TResult rv = { ERR_NONE, 0, 0 };
	TCAProcessError perr;
	TProcessStatus pstatus;
	char logfilename[MAXLOGNAME];

	// make sure that we still have a process
	assert( iProcess != NULL );

	// get the status of the process to make sure we are ok
	perr = iProcess->GetProcessStatus( &pstatus );
	assert( perr == CAE_NONE );
	if( pstatus == PS_STARTED ) {
	  *aDeleteInstance = 0;
	  rv.iStandardResult = ERR_INVALIDSTATE;
	  return rv;
	}

	// remove the log file
	snprintf( logfilename, MAXLOGNAME, "pppd%04d.log", iKey );
	unlink( logfilename );

	// ok - remove the process
	delete iProcess;
	iProcess = NULL;

	// done - success
	*aDeleteInstance = 1; 
	return rv;
}


/****************************************************************************************
 * 
 * PUBLIC FUNCTION: killsession
 * 
 ***************************************************************************************/
TResult CSPppcontroller::killsession( int aArgs )
{
	return StopProcessWithSignal( SIGKILL );
}


/****************************************************************************************
 * 
 * PUBLIC FUNCTION: stopsession
 * 
 ***************************************************************************************/
TResult CSPppcontroller::stopsession( int aArgs )
{
	return StopProcessWithSignal( SIGTERM );
}


/****************************************************************************************
 * 
 * PUBLIC FUNCTION: getsessioninfo
 * 
 ***************************************************************************************/
TPppSessionDesc CSPppcontroller::getsessioninfo( int aArgs )
{
	TPppSessionDesc rv;
	TResult res;
	int exitcode = 0;
	TCAProcessError perr;
	TProcessStatus pstatus = PS_INVALID;
	TProcessExitReason preason = ER_INVALID;

	// check state
	assert( iProcess != NULL );
	memset( &rv, 0, sizeof(rv) );

	// interrogate the process	
	perr = iProcess->GetProcessStatus( &pstatus );
	assert( perr == CAE_NONE );
	if( (pstatus == PS_STOPPED) || (pstatus == PS_ABANDONNED) ) {
		perr = iProcess->GetExitReason( &preason );
		assert( perr == CAE_NONE );
	}
	if( (pstatus == PS_STOPPED) || (pstatus == PS_ABANDONNED) ) {
		perr = iProcess->GetExitCode( &exitcode );
		assert( perr == CAE_NONE );
	}

	// update the dynamic config
	res = GetInterfaceName();
	if( res.iStandardResult != ERR_NONE ) {
		rv.iErrorCode = res.iStandardResult;
		rv.iErrorDetail = res.iExtendedCode;
		return rv;
	}
	res = UpdateSessionStatus();
	if( res.iStandardResult != ERR_NONE ) {
		rv.iErrorCode = res.iStandardResult;
		rv.iErrorDetail = res.iExtendedCode;
		return rv;
	}
	res = GetIPAddresses();
	if( res.iStandardResult != ERR_NONE ) {
		rv.iErrorCode = res.iStandardResult;
		rv.iErrorDetail = res.iExtendedCode;
		return rv;
	}

	// now return the info
	rv.iErrorCode = ERR_NONE;
	rv.iErrorDetail = 0;
	rv.iConfig = iSessionConfig;
	rv.iProcessStatus = (int)pstatus;
	rv.iProcessExitReason = (int)preason;
	rv.iProcessExitCode = exitcode;
	rv.iSessionStatus = iSessionStatus;
	STRNCPY_NULL_TERMINATE( rv.iInterfaceName, iInterfaceName.c_str(), MAXSESSIONNAMELEN );
	rv.iLocalIPAddress = iLocalIPAddress;
	rv.iRemoteIPAddress = iRemoteIPAddress;
	return rv;
}


/****************************************************************************************
 * 
 * PUBLIC FUNCTION: getppplog
 * 
 ***************************************************************************************/
TVarData CSPppcontroller::getppplog( int aArgs )
{
	TVarData rv;
	TResult res;
	char logname[MAXLOGNAME];

	// get the logfilename 
	snprintf( logname, MAXLOGNAME, "pppd%04d.log", iKey );

	// now copy the file into the vardata buffer
	res = CopyFileIntoBuffer( logname, &rv );
	if( res.iStandardResult != ERR_NONE ) {
		assert( rv.TVarData_len == 0 );
		assert( rv.TVarData_val == NULL );
	}

	// done
	return rv;
}


/****************************************************************************************
 * 
 * PRIVATE FUNCTION: StopProcessWithSignal
 * 
 ***************************************************************************************/
TResult CSPppcontroller::StopProcessWithSignal( int aSignal )
{
	TResult rv = { ERR_NONE, 0, 0 };
	TCAProcessError perr;
	TProcessStatus pstatus;

	// since we create in the constructor and stop in the destructor there must be a process
	assert( iProcess != NULL );

	// if the process isn't running then it has died outside the scope of this controller, 
	// clean up the state, return an error since this is noteworthy and should be either 
	// expected or not happen.
	perr = iProcess->GetProcessStatus( &pstatus );
	assert( perr == CAE_NONE );
	if( pstatus != PS_STARTED ) {
	  assert( (pstatus == PS_STOPPED) || (pstatus == PS_ABANDONNED) );
	  rv.iStandardResult = ERR_PROCESS_TERMINATED_OUTSIDE_SCOPE;
	  return rv;
	}

	// request the process to stop
	perr = iProcess->RequestStop( aSignal );
	if( perr != CAE_NONE ) {
	  rv.iStandardResult = ERR_STOP_PROCESS_ERROR;
	  rv.iExtendedCode = (int)perr;
	  return rv;
	}

	// wait for the process to stop
	perr = iProcess->WaitForProcessToTerminate( -1 );
	if( perr != CAE_NONE ) {
	  rv.iStandardResult = ERR_WAIT_PROCESS_ERROR;
	  rv.iExtendedCode = (int)perr;
	  return rv;
	}

	// done - success
	return rv;
}


/****************************************************************************************
 * 
 * PRIVATE FUNCTION: GetInterfaceName
 * 
 ***************************************************************************************/
TResult CSPppcontroller::GetInterfaceName()
{
	TResult rv;
	string line;
	char interface_name[MAXINTERFACENAMESIZE];

	// clear the interface (so if not found we don't have an old value)
	iInterfaceName = "";

	// we want to know the interface name, on line "Using interface pppX";
	rv = GetTokenFromFile( "Using", &line, MM_FIRST );
	if( rv.iStandardResult != ERR_NONE ) {
		if( rv.iStandardResult == ERR_INVALIDARG ) {
			rv.iStandardResult = ERR_NONE;
			rv.iExtendedCode = 0;
		}
		return rv;
	}

	// now grab the interface name
	sscanf( line.c_str(), "Using interface %s", interface_name );
	iInterfaceName = interface_name;	
	
	// done - success;	
	return rv;
}


/****************************************************************************************
 * 
 * PRIVATE FUNCTION: GetIPAddresses
 * 
 ***************************************************************************************/
TResult CSPppcontroller::GetIPAddresses()
{
	TResult rv;
	string line;
	char address[MAXINTERFACENAMESIZE];

	// clear old values
	iLocalIPAddress = iRemoteIPAddress = 0;

	// we want to know the local IP address
	rv = GetTokenFromFile( "local  IP address", &line, MM_LAST );
	if( rv.iStandardResult != ERR_NONE ) {
	  if( rv.iStandardResult == ERR_INVALIDARG ) {
	    rv.iStandardResult = ERR_NONE;
	    rv.iExtendedCode = 0;
	  }
	  return rv;
	}

	// grab address
	sscanf( line.c_str(), "local  IP address %s", address );
	iLocalIPAddress = inet_addr( address );

	// we want to know the remote IP address
	rv = GetTokenFromFile( "remote IP address", &line, MM_LAST );
	if( rv.iStandardResult != ERR_NONE ) {
	  if( rv.iStandardResult == ERR_INVALIDARG ) {
	    rv.iStandardResult = ERR_NONE;
	    rv.iExtendedCode = 0;
	  }
	  return rv;
	}

	// grab address
	sscanf( line.c_str(), "remote IP address %s", address );
	iRemoteIPAddress = inet_addr( address );

	// done - success;	
	return rv;
}


/****************************************************************************************
 * 
 * PRIVATE FUNCTION: UpdateSessionStatus
 * 
 ***************************************************************************************/
TResult CSPppcontroller::UpdateSessionStatus()
{
	TResult rv = { ERR_NONE, 0, 0 };
	TFUError ferr;
	int mline[MAXLISTLINES], match_count, errcode, once_connected, disconnected;
	char logfilename[MAXLOGNAME];
	struct stat fstat;

	// get the logfilename 
	snprintf( logfilename, MAXLOGNAME, "pppd%04d.log", iKey );

	// see if the file exists - if not then the status is SS_NOT_STARTED
	errcode = stat( logfilename, &fstat );
	if( (errcode == -1) && (errno == ENOENT) ) {
		iSessionStatus = SS_NOT_STARTED;
		return rv;
	}

	// look for the line with the CONNECTED_STRING prefix
	match_count = MAXLISTLINES;
	ferr = FindMatchingLinesByRawPrefix( logfilename, CONNECTED_STRING, mline, &match_count, &errcode );
	if( ferr != FUE_NONE ) {
		rv.iStandardResult = ERR_LOG_FILE_ERROR;
		rv.iExtendedCode = (int)ferr;
		rv.iSystemError = errcode;
		return rv;
	}
	once_connected = ((match_count > 0) ? 1 : 0);

	// look for the line with the DISCONNECTED_STRING prefix
	match_count = MAXLISTLINES;
	ferr = FindMatchingLinesByRawPrefix( logfilename, DISCONNECTED_STRING, mline, &match_count, &errcode );
	if( ferr != FUE_NONE ) {
		rv.iStandardResult = ERR_LOG_FILE_ERROR;
		rv.iExtendedCode = (int)ferr;
		rv.iSystemError = errcode;
		return rv;
	}
	disconnected  = ((match_count > 0) ? 1 : 0);

	// set the state based on the log
	if( (once_connected == 0) && (disconnected == 0) ) {
		iSessionStatus = SS_CONNECTING;
	} else if( (once_connected == 1) && (disconnected == 0) ) {
		iSessionStatus = SS_CONNECTED;
	} else if( (once_connected == 1) && (disconnected == 1) ) {
		iSessionStatus = SS_DISCONNECTED;
	} else if( (once_connected == 0) && (disconnected == 1) ) {
		iSessionStatus = SS_DISCONNECTED;
	}
			
	// done - success
	return rv;
}


/*******************************************************************************
 *
 * PRIVATE FUNCTION: CopyFileIntoBuffer
 *
 ******************************************************************************/
TResult CSPppcontroller::CopyFileIntoBuffer( char *aFilename, TVarData *aData )
{
	TResult rv = { ERR_NONE, 0, 0 };
	int err;
	int filesize;
	int i;
	char c;
	FILE *fp;

	// check params
	assert( aFilename != NULL );
	assert( aData != NULL );

	// initialise the data to empty
	aData->TVarData_val = NULL;
	aData->TVarData_len = 0;

	// open the logfile
	fp = fopen( aFilename, "rb" );
	if( fp == NULL ) {
		rv.iStandardResult = ERR_LOG_FILE_ERROR;
		rv.iExtendedCode = 0;
		rv.iSystemError = errno;
		return rv;
	}

	// get the file size
	fseek( fp, 0, SEEK_END );
	filesize = ftell( fp );
	fseek( fp, 0, SEEK_SET );

	// if the filesize is zero then just return
	if( filesize == 0 ) {
	  return rv;
	}

	// allocate space for the file data
	aData->TVarData_val = (char*)calloc( (filesize + 1), 1 );
	assert( aData->TVarData_val != NULL );
	aData->TVarData_len = (filesize + 1);

	// now copy the data
	for( i = 0; i < filesize; i++ ) {

		// read one byte
		err = fread( &c, 1, 1, fp );
		if( err != 1 ) {
			free( aData->TVarData_val );
			aData->TVarData_val = NULL;
			aData->TVarData_len = 0;
			rv.iStandardResult = ERR_LOG_FILE_ERROR;
			rv.iExtendedCode = 0;
			rv.iSystemError = errno;
			fclose( fp );
			return rv;
		}

		// save the byte in the buffer
		(aData->TVarData_val)[i] = c;
	}

	// cleanup and return 
	fclose( fp );
	return rv;
}


/****************************************************************************************
 * 
 * PRIVATE FUNCTION: GetTokenFromFile
 * 
 ***************************************************************************************/
TResult CSPppcontroller::GetTokenFromFile( string aPrefix, string *aLine, TMultiMatchSemantics aMultiMatchSemantics )
{
	TResult rv = { ERR_NONE, 0, 0 };
	TFUError ferr;
	char logfilename[MAXLOGNAME];
	char cline[MAXLOGLINELENGTH];
	int mline[MAXLISTLINES], match_count, errcode, entry;
	struct stat fstat;

	// check params
	assert( aLine != NULL );

	// get the logfilename 
	snprintf( logfilename, MAXLOGNAME, "pppd%04d.log", iKey );

	//	fprintf( stderr, "DEBUG: request for lines starting with '%s'\n", aPrefix.c_str() );

	// see if the file exists - if not then just return - this is NOT an error
	errcode = stat( logfilename, &fstat );
	if( (errcode == -1) && (errno == ENOENT) ) {
		rv.iStandardResult = ERR_INVALIDARG;
		rv.iExtendedCode = 1;
		//		fprintf( stderr, "DEBUG: file doesn't exist.\n" );
		return rv;
	}

	// look for the lines that match the prefix
	match_count = MAXLISTLINES;
	ferr = FindMatchingLinesByRawPrefix( logfilename, (char*)aPrefix.c_str(), mline, &match_count, &errcode );
	if( ferr != FUE_NONE ) {
		rv.iStandardResult = ERR_LOG_FILE_ERROR;
		rv.iExtendedCode = (int)ferr;
		rv.iSystemError = errcode;
		//		fprintf( stderr, "DEBUG: error parsing logfile.\n" );
		return rv;
	}

	// if there is no match then nothing to do
	if( match_count == 0 ) {
		rv.iStandardResult = ERR_INVALIDARG;
		rv.iExtendedCode = 2;
		//		fprintf( stderr, "DEBUG: no matches.\n" );
		return rv;
	}

	// if there are multiple matches then print warning (just for the admin)
	if( match_count > 1 ) {
	  //	  fprintf( stderr, "WARNING: multiple matches for token \"%s\" in '%s'.\n", aPrefix.c_str(), logfilename );  
	}

	// handle multiple matches
	if( (aMultiMatchSemantics == MM_ERROR) && (match_count > 1) ) {
		rv.iStandardResult = ERR_MULTIPLE_MATCHES_IN_LOG_FILE;
		return rv;
	}
	entry = ((aMultiMatchSemantics == MM_FIRST) ? 0 : (match_count-1));

	// now get the matching line 
	ferr = GetLine( logfilename, mline[entry], cline, MAXLOGLINELENGTH, &errcode );
	if( ferr != FUE_NONE ) {
		rv.iStandardResult = ERR_LOG_FILE_ERROR;
		rv.iExtendedCode = (int)ferr;
		rv.iSystemError = errcode;
		//		fprintf( stderr, "DEBUG: error getting the matching line.\n" );
		return rv;
	}

	// copy the line into the passed buffer
	(*aLine) = cline;
	//	fprintf( stderr, "DEBUG: successfully retrieved line '%s'\n", aLine->c_str() );

	// done - success;	
	return rv;
}

