// Copyright (c) 2004-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 implements a loopback driver for use with 3GNIF test harness.
// 
//

/**
 @file
*/

#include <c32comm.h>
#include <c32comm_internal.h>

#include "Loopback.h"
#include "LoopbackConfig.h"
#include "LoopbackTimer.h"
#include "LoopbackQueue.h"

CHWPort::CHWPort(TUint aUnit) : iRole(ECommRoleDTE), iPortName(aUnit)
	{
	TName name;
	name.Format(_L("%d"),aUnit);
	SetName(&name);
	}

static void CloseObject(TAny* anObject)
/**
 * This method simply closes an object from the cleanup stack.  The object must contain
 * a Close method.
 *
 * @param	anObject - a TAny pointer to the object to close. 
 */
	{
	((CObject*)anObject)->Close();
	}

CHWPort* CHWPort::NewPacketLoopbackL(TUint aUnit, TUint aDelay, TUint aPacketLength, TUint aQueueLength)
/**
 * This method is used by the factory object to create the new CHWPort instances.
 * After newing the CHWPort object, the buffer is created, roles and signals are defaulted,
 * and names are initialized.
 *
 * @param	aUnit - the port number to create.
 * @param	aDelay - the delay for sending on the port
 * @param	aPacketLength - the size in bytes of a packet
 * @param	aQueueLength - the maximum number of packets to queue
 *
 * @return	A pointer to the created object
 */
	{
	LOGTEXT2(_L8("PKTLOOPBACK:NewL: Called for Unit %d"), aUnit);

	CHWPort *p = new(ELeave) CHWPort(aUnit);
	TCleanupItem closePort(CloseObject,p);
	CleanupStack::PushL(closePort);
	p->ConstructPacketLoopbackL(aDelay, aPacketLength, aQueueLength);
	CleanupStack::Pop(p);

	return p;
	}

CHWPort* CHWPort::NewSerialLoopbackL(TUint aUnit, TUint aDelay, TUint aBufferSize)
/**
 * This method is used by the factory object to create the new CHWPort instances.
 * After newing the CHWPort object, the buffer is created, roles and signals are defaulted,
 * and names are initialized.
 *
 * @param	aUnit - the port number to create.
 * @param	aDelay - the delay for sending on the port
 * @param	aBuffersize - the size of the buffer to allocate
 *
 * @return	A pointer to the created object
 */
	{
	LOGTEXT2(_L8("PKTLOOPBACK:NewL: Called for Unit %d"), aUnit);

	CHWPort *p = new(ELeave) CHWPort(aUnit);
	TCleanupItem closePort(CloseObject,p);
	CleanupStack::PushL(closePort);
	p->ConstructSerialLoopbackL(aDelay, aBufferSize);
	CleanupStack::Pop(p);

	return p;
	}

void CHWPort::ConstructSerialLoopbackL(TUint aDelay, TUint aBufferSize)
	{
	iWriteDelayTimer = CLoopbackTimer::NewL(aDelay, this);
	iFlowControlChange = CFlowControlChange::NewL(iPortName, this);
	
	iPortType = ESerialLoopbackPortType;
	TCommConfigV01 *config = &iConfig();
	iReadWriteQueue = CSerialBufferQueue::NewL(aBufferSize, config);
	}
	
void CHWPort::ConstructPacketLoopbackL(TUint aDelay, TUint aPacketLength, TUint aQueueLength)
	{
	iWriteDelayTimer = CLoopbackTimer::NewL(aDelay, this);
	iFlowControlChange = CFlowControlChange::NewL(iPortName, this);
	iReadWriteQueue = CPacketBufferQueue::NewL(aPacketLength, aQueueLength);
	}
	
TUint CHWPort::PortName() const
	{
	return iPortName;
	}

void CHWPort::SetLoopbackPort(CHWPort* aHWPort)
/**
 * This method sets up the loopback port member of the CHWPort.  It is used after
 * both ports have been created (NewL).  This allows each port to "know" to whom he is
 * connected.
 *
 * @param	aHWPort - the port which THIS port should be connected to.
 */
	{
	LOGTEXT2(_L8("PKTLOOPBACK:SetLoopbackPort:  Unit %d..."), iPortName);

	// Now set up the loopback
	iLoopbackPort = aHWPort;
	}

TBool CHWPort::GetFlowControl() const
/**
 * @return ETrue if flow control is on, EFalse if it is not
 */
	{
	return iFlowControlChange->FlowControlIsOn();
	}

void CHWPort::StartFlowControl()
/**
 * Performs any necessary actions to set flow control on
 */
	{
	iWriteDelayTimer->Cancel();
	}
	
void CHWPort::StopFlowControl()
/**
 * Performs any necessary actions to stop flow control
 */
	{
	if (!iLoopbackPort)
		{
		return;
		}
	
	if (iReadWriteQueue->IsWriteBufferEmpty())
		{
		return;
		}
	
	TInt nextWriteLen = iReadWriteQueue->PeekNextWriteBuffer().Length();
	if (!iLoopbackPort->iReadWriteQueue->IsReadBufferFull(nextWriteLen)
		&& !iWriteDelayTimer->IsActive())
		{
		iWriteDelayTimer->Start();
		}
	}
	
TInt CHWPort::GetReadRequestStatus()
/**
 * @return The status that Read() calls are to complete with
 */
	{
	TInt readRequestStatus = KErrNone;
	TInt ret = RProperty::Get(KUidPSCsyReadResultCategory, iPortName, readRequestStatus);
	if (ret == KErrNone)
		return readRequestStatus;
	// if we can't find this property, we make everything work
	return KErrNone;
	}
	
TInt CHWPort::GetWriteRequestStatus()
/**
 * @return The status that Write() calls are to complete with
 */
	{
	TInt writeRequestStatus = KErrNone;
	TInt ret = RProperty::Get(KUidPSCsyWriteResultCategory, iPortName, writeRequestStatus);
	if (ret == KErrNone)
		return writeRequestStatus;
	// if we can't find this property, we make everything work
	return KErrNone;
	}
	
void CHWPort::StartRead(const TAny* aClientBuffer, TInt aLength)
/**
 * This method queues a read operation to the driver.  If the read length is zero (which is a
 * special case used during initialization) the read completes immediately.
 * If the read buffer is insufficient in size, the read completes with error KErrOverflow.
 *
 * @param	aClientBuffer	- a TAny * to the buffer into which data is placed.
 * @param	aLength			- the length of the buffer supplied
 */
	{
	LOGTEXT2(_L8("PKTLOOPBACK:StartRead:  Unit %d..."), iPortName);

	// Because a length of zero is a special case (for initialization), we will complete this immediately
	if(aLength == 0)
		{
		ReadCompleted(KErrNone);
		return;
		}
	else if(aLength < 0)
		{
		// if the read is through the function ReadOneOrMore, the length will be negative
		// in this CSY, the behavior is always ReadOneOrMore
		aLength = -aLength;
		iReadOneOrMore = ETrue;
		}
	else // aLength > 0
		{
		iReadOneOrMore = EFalse;
		}
	
	// Save the client read buffer
	iClientReadBuffer = aClientBuffer;
	iClientReadBufferLength = aLength;
	iReadPending = ETrue;
	
	// if there is already an entry in the read buffer, we can complete the read now
	TryToCompleteRead();
	}

void CHWPort::TryToCompleteRead()
/**
 * This method attempts to complete reads - it is either called as they are initially issued (and is
 * completed if there is data already in the read buffer), or is is called later, when data is supplied 
 * to the read buffer (and is completed if there is a read waiting)
 * Data is moved from the buffer associated with this port to a buffer that was supplied by the client 
 * when the read was issued.
 */

	{
	LOGTEXT2(_L8("PKTLOOPBACK:TryToCompleteRead: Unit %d ..."), iPortName);

	// If there is not read pending or no loopback port, we can't complete a read
	if (!iReadPending)
		return;
	
	if (!iLoopbackPort)
		return;
	
	// Determine if there an entry available to be read
	if (iReadWriteQueue->IsReadBufferEmpty())
		return;
	
	TInt res = KErrNone;
	TBool completeIfBufferNotFull;
	TPtrC8 nextReadBuffer = iReadWriteQueue->PeekNextReadBuffer(completeIfBufferNotFull);
	if ( iClientReadBufferLength > nextReadBuffer.Length() )
		{
		if (iPortType == ESerialLoopbackPortType) 
			{
		 	if (iReadOneOrMore == EFalse) 
				{
		 		if (!completeIfBufferNotFull)
		 			{
					return;
		 			}
				}
			}
		}
		
	TInt readRequestStatus = GetReadRequestStatus();
	if ( (iClientReadBufferLength < nextReadBuffer.Length()) && (iPortType == EPacketLoopbackPortType) )
		{
		// The client buffer is not large enough
		res = KErrOverflow;
		}
	else if (readRequestStatus != KErrNone)
		{
		// We are configured to complete with an error
		res = readRequestStatus;
		iReadWriteQueue->DiscardNextReadBuffer();
		}
	else
		{
		// Write the data to the client's supplied buffer
		res = IPCWrite(iClientReadBuffer, nextReadBuffer, 0);
		if(res == KErrNone)
			{
			// It is normally the writing buffer's responsibility to start the timer, but in the case where the 
			// reading port's buffer is full, now that the read buffer is no longer full, we have to signal to the 
			// writing port to start sending again
			if (!iLoopbackPort->iReadWriteQueue->IsWriteBufferEmpty())
				{
				TInt nextWriteLen = iLoopbackPort->iReadWriteQueue->PeekNextWriteBuffer().Length();
				if (iReadWriteQueue->IsReadBufferFull(nextWriteLen)
					&& !iLoopbackPort->iWriteDelayTimer->IsActive() )
					{
					iLoopbackPort->iWriteDelayTimer->Start();
					}
				}
			// Read has been completed successfully, so dequeue
			iReadWriteQueue->DiscardNextReadBuffer();
			}
		}
	// complete the read
	iReadPending = EFalse;
	LOGTEXT3(_L8("PKTLOOPBACK:TryToCompleteRead: Unit %d completing read with error %d"), iPortName, res);
	ReadCompleted(res);
	}

void CHWPort::ReadCancel()
/**
 * Cancel a pending read and complete it with KErrCancel. The handling of the CActive class
 * will by default complete any outstanding read with KErrCancel, but it is cleaner to handle
 * that processing here.
 */
	{
	LOGTEXT2(_L8("PKTLOOPBACK:ReadCancel:  Unit %d..."), iPortName);

	iReadPending = EFalse;

	ReadCompleted(KErrCancel);
	}

TInt CHWPort::WriteBuf(const TAny* aClientBuffer, TInt aLength)
/**
 * This method queues the client buffer to the loopback port.
 * A delay in the port is simulated, so the read corresponding to this buffer will not complete
 * until after this delay. The buffers are queued up and sent one per delay period.
 *
 * @param	aClientBuffer	- a TAny * to the buffer which contains the data to write
 * @param	aLength			- the length of the buffer.
 *
 * @return	KErrNone if everything is okay, KErrOverflow if the write queue is full and no more entries can be written until some have been sent, 
 * KErrNotReady	if the loopback port has not yet been opened or there is no loopback port configured for this port, or KErrArgument	if the supplied buffer is too large
 */
	{
	TInt res = KErrNone;

	LOGTEXT2(_L8("PKTLOOPBACK:WriteBuf:  Unit %d..."), iPortName);

	TInt writeRequestStatus = GetWriteRequestStatus();
	if (writeRequestStatus != KErrNone)
		return writeRequestStatus;
	
	// Fill the receiving buffer
	if (aLength != 0)
		{
		if (iReadWriteQueue->IsWriteBufferFull(aLength))
			{
			return KErrOverflow;
			}
		if (aLength > iReadWriteQueue->BufferSize())
			{
			return KErrArgument;
			}
		
		TPtr8 currentPacket = iReadWriteQueue->AppendToWriteBuffer(aLength);
		
		res = IPCRead(aClientBuffer, currentPacket);
		if(res != KErrNone)
			return res;
		}
	// Queue up the request and start the timer to simulate the delay in sending the data across the port
	if (!iWriteDelayTimer->IsActive())
		{
		iWriteDelayTimer->Start();
		}

	return KErrNone;
	}

void CHWPort::TimerCallBack()
/**
 * This method is called after the simulated delay for sending data across the port
 */
	{
	if (!iLoopbackPort)
		{
		return;
		}
	
	if (iReadWriteQueue->IsWriteBufferEmpty())
		{
		return;
		}
	
	TPtrC8 writeBuffer = iReadWriteQueue->PeekNextWriteBuffer();
	if (iLoopbackPort->iReadWriteQueue->IsReadBufferFull(writeBuffer.Length()))
		{
		return;
		}
	
	// Move the buffer from the write queue to the read queue
	iLoopbackPort->iReadWriteQueue->AppendToReadBuffer(writeBuffer);
	iReadWriteQueue->DiscardNextWriteBuffer();

	// If the loopback port has a read outstanding it can complete now
	iLoopbackPort->TryToCompleteRead();
	// If there is another write outstanding we have to simulate a delay for that entry
	if (!iReadWriteQueue->IsWriteBufferEmpty() && !iWriteDelayTimer->IsActive())
		{
		iWriteDelayTimer->Start();
		}
	}

TInt CHWPort::QueryReceiveBuffer(TInt& aLength) const
/**
 * This method returns the length of the buffer associated with this instance of the
 * port.
 *
 * @param	aLength			- a reference to return the length of the buffer.
 */
	{
	LOGTEXT2(_L8("PKTLOOPBACK:QueryReceiveBuffer:  Unit %d..."), iPortName);

	TBool dummy;
	const TPtrC8& iReadBuf = iReadWriteQueue->PeekNextReadBuffer(dummy);
	aLength = iReadBuf.Length();
	return KErrNone;
	}

void CHWPort::ResetBuffers(TUint)
/**
 * This method resets the buffer used by this loopback port 
 *
 * @note Note that most ResetBuffers methods derived from CPort allow a parameter for flags.
 * This ResetBuffers method does not.
 *
 * @param	Not Used
 */
	{
	LOGTEXT2(_L8("PKTLOOPBACK:ResetBuffers:  Unit %d..."), iPortName);
	
	iReadWriteQueue->Clear();
	}

void CHWPort::StartWrite(const TAny* aClientBuffer,TInt aLength)
/**
 * This method queues a write operation to the driver.
 *
 * @param	aClientBuffer	- a TAny * to the buffer into which data should be read.
 * @param	aLength			- the length of the data to be written.
 */
	{

	LOGTEXT2(_L8("PKTLOOPBACK:StartWrite:  Unit %d..."), iPortName);

	TInt res = WriteBuf(aClientBuffer, aLength);
	
	LOGTEXT3(_L8("PKTLOOPBACK:StartWrite:  Completing Write for Unit %d with error %d"), iPortName, res);
	WriteCompleted(res);
	}

void CHWPort::WriteCancel()
/**
 * This method cancels a pending write and issues a WriteCompleted with the result
 * KErrCancel.
 */

	{
	LOGTEXT2(_L8("PKTLOOPBACK:WriteCancel:  Unit %d..."), iPortName);

	WriteCompleted(KErrCancel);
	}

void CHWPort::Break(TInt /*aTime*/)
/**
 * This method is currently not implemented in the loopback driver as breaks are
 * not supported.
 */
	{
	LOGTEXT2(_L8("PKTLOOPBACK:Break is not supported:  Unit %d..."), iPortName);
	}

void CHWPort::BreakCancel()
/**
 * This method is currently not implemented in the loopback driver as breaks are
 * not supported.
 */
	{
	LOGTEXT2(_L8("PKTLOOPBACK:BreakCancel is not supported:  Unit %d..."), iPortName);
	}

TInt CHWPort::GetConfig(TDes8& /*aDes*/) const
/**
 * This gets the current configuration from the loopback driver.
 * 
 * @return KErrNotSupported
 */
	{
	LOGTEXT2(_L8("PKTLOOPBACK:GetConfig is not supported:  Unit %d..."), iPortName);

	return KErrNotSupported;
	}

TInt CHWPort::SetConfig(const TDesC8& aDes)
/**
 * This sets the current configuration for the loopback driver.  Note that
 * no error checking is done when setting the configuration.
 *
 * @return KErrNotSupported
 */
	{
	LOGTEXT2(_L8("PKTLOOPBACK:SetConfig is not supported:  Unit %d..."), iPortName);
	
	iConfig.Copy(aDes);

	return KErrNone;
	}

TInt CHWPort::GetCaps(TDes8& aDes)
/**
 * This gets the supported capabilities from the loopback driver.  The actual capabilities of
 * the driver will vary based on the role the port is playing (DCE or DTE).  The loopback driver
 * supports capabilities via TCommCapsV01 and TCommCapsV02.
 *
 * @param	aDes				- a TDes8 reference to copy the capabilities into.
 *
 * @return	KErrNone			- Everything is okay, KErrNotSupported it the length of the descriptor passed to this method indicates a
 *								  capabilities structure which we don't support.
 */
	{
	LOGTEXT2(_L8("PKTLOOPBACK:GetCaps:  Unit %d..."), iPortName);

	if(aDes.Length()==sizeof(TCommCapsV01))
		{
		TCommCapsV01* commcaps=(TCommCapsV01*)(aDes.Ptr());
		// We've got all of these
		commcaps->iRate=0x3fffff;
		commcaps->iDataBits=0xf;
		commcaps->iStopBits=0x3;
		commcaps->iParity=0x1f;
		commcaps->iFifo=0x1;
		commcaps->iHandshake=0;
		commcaps->iSignals=0x3f;
		commcaps->iSIR=0x0;
		return KErrNone;
		}
	else if(aDes.Length()==sizeof(TCommCapsV02))
		{
		TCommCapsV02* commcaps=(TCommCapsV02*)(aDes.Ptr());
		commcaps->iRate=0x3fffff;
		commcaps->iDataBits=0xf;
		commcaps->iStopBits=0x3;
		commcaps->iParity=0x1f;
		commcaps->iFifo=0x1;
		commcaps->iHandshake=0;
		commcaps->iSignals=0x3f;
		commcaps->iSIR=0x0;
		commcaps->iNotificationCaps=0;
		commcaps->iRoleCaps=0x0;
		return KErrNone;
		}
	else
		return KErrNotSupported;
	}

TInt CHWPort::SetServerConfig(const TDesC8& /*aDes*/)
/**
 * This sets the current server configuration for the loopback driver.  The loopback driver
 * stores this information but does nothing with it.
 *
 * @param	aDes	- a TDes8 reference to copy the configuration from.
 *
 * @return	KErrNotSupported
 */
	{
	LOGTEXT2(_L8("PKTLOOPBACK:SetServerConfig is not supported:  Unit %d..."), iPortName);

	return KErrNotSupported;
	}

TInt CHWPort::GetServerConfig(TDes8& /*aDes*/)
/**
 * This gets the current server configuration for the loopback driver.  The loopback driver
 * stores this information but does nothing with it other than return it here.
 *
 * @param	aDes	- a TDes8 reference to copy the configuration to.
 *
 * @return	KErrNotSupported
 */
	{
	LOGTEXT2(_L8("PKTLOOPBACK:GetServerConfig is not supported:  Unit %d..."), iPortName);

	return KErrNotSupported;
	}

TInt CHWPort::GetSignals(TUint& aSignals)
/**
 * This method retrieves the current setting of the signals for THIS port.
 *
 * @param	aSignals	- A reference to a TUint to return the signal settings.
 *
 * @return	KErrNotSupported
 */
	{
	LOGTEXT2(_L8("PKTLOOPBACK:GetSignals is not supported:  Unit %d..."), iPortName);

	aSignals = 0;
	return KErrNotSupported;
	}

TInt CHWPort::SetSignalsToMark(TUint /*aSignals*/)
/**
 * This method asserts the signals specified by the parameter aSignals.
 *
 * @param	aSignals	- a bitmask specifying which signals to assert (See definition
 *                        of KSignalDCD, KSignalCTS, etc. for bit values).
 *
 * @return	KErrNotSupported
 */
	{
	LOGTEXT2(_L8("PKTLOOPBACK:SetSignalsToMark is not supported:  Unit %d..."), iPortName);

	return KErrNotSupported;
	}



TInt CHWPort::SetSignalsToSpace(TUint /*aSignals*/)
/**
 * This method de-asserts the signals specified by the parameter aSignals.
 *
 * @return KErrNotSupported
 */
	{
	LOGTEXT2(_L8("PKTLOOPBACK:SetSignalsToSpace is not supported:  Unit %d..."), iPortName);

	return KErrNotSupported;
	}

TInt CHWPort::GetReceiveBufferLength(TInt& /*aLength*/) const
/**
 * This method is currently not implemented in the loopback driver.  Calling this
 * method will return an error
 *
 * @return	KErrNotSupported
 */
	{
	LOGTEXT2(_L8("PKTLOOPBACK:GetReceiveBufferLength is not supported:  Unit %d..."), iPortName);

	return KErrNotSupported;
	}

TInt CHWPort::SetReceiveBufferLength(TInt /*aLength*/)
/**
 * This method is currently not implemented in the loopback driver.  Calling this
 * method will return an error
 *
 * @return	KErrNotSupported
 */
	{
	LOGTEXT2(_L8("PKTLOOPBACK:SetReceiveBufferLength is not supported:  Unit %d..."), iPortName);

	return KErrNotSupported;
	}

void CHWPort::Destruct()
/**
 * This method is simply deletes this instance of the port, comitting sucide.
 */
	{
	delete this;
	}



void CHWPort::NotifySignalChange(TUint /*aSignalMask*/)
/**
 * This method sets up a request to be notified when a signal change occurs on the specified
 * signals.  Later operations will send a message to the requestor indicating the signal
 * change.
 *
 * @param	aSignalMask		-	the signals that the caller is interested in monitoring.
 */
	{
	LOGTEXT2(_L8("PKTLOOPBACK:NotifySignalChange is not supported:  Unit %d..."), iPortName);
	}


void CHWPort::NotifySignalChangeCancel()
/**
 * This method cancels an outstanding request to be notified when a signal change occurs.  Any
 * outstanding signal change request will be completed with KErrCancel.
 */
	{
	LOGTEXT2(_L8("PKTLOOPBACK:NotifySignalChangeCancel is not supported:  Unit %d..."), iPortName);
	}


void CHWPort::NotifyConfigChange()
/**
 * This method is currently not implemented in the loopback driver.
 */
	{
	LOGTEXT2(_L8("PKTLOOPBACK:NotifyConfigChange is not supported:  Unit %d..."), iPortName);
	}

void CHWPort::NotifyConfigChangeCancel()
/**
 * This method is currently not implemented in the loopback driver.
 */
	{
	LOGTEXT2(_L8("PKTLOOPBACK:NotifyConfigChangeCancel is not supported:  Unit %d..."), iPortName);
	}

void CHWPort::NotifyFlowControlChange()
/**
 * This method is currently not implemented in the loopback driver.
 */
	{
	LOGTEXT2(_L8("PKTLOOPBACK:NotifyFlowControlChange is not supported:  Unit %d..."), iPortName);
	}

void CHWPort::NotifyFlowControlChangeCancel()
/**
 * This method is currently not implemented in the loopback driver.
 */
	{
	LOGTEXT2(_L8("PKTLOOPBACK:NotifyFlowControlChangeCancel is not supported:  Unit %d..."), iPortName);
	}


void CHWPort::NotifyBreak()
/**
 * This method is currently not implemented in the loopback driver.
 */
	{
	LOGTEXT2(_L8("PKTLOOPBACK:NotifyBreak is not supported:  Unit %d..."), iPortName);
	}

void CHWPort::NotifyBreakCancel()
/**
 * This method is currently not implemented in the loopback driver.
 */
	{
	LOGTEXT2(_L8("PKTLOOPBACK:NotifyBreakCancel is not supported:  Unit %d..."), iPortName);
	}

void CHWPort::NotifyDataAvailable()
/**
 * This method is currently not implemented in the loopback driver.
 */
	{
	LOGTEXT2(_L8("PKTLOOPBACK:NotifyDataAvailable is not supported:  Unit %d..."), iPortName);
	}

void CHWPort::NotifyDataAvailableCancel()
/**
 * This method is currently not implemented in the loopback driver.
 */
	{
	LOGTEXT2(_L8("PKTLOOPBACK:NotifyDataAvailableCancel is not supported:  Unit %d..."), iPortName);
	}

void CHWPort::NotifyOutputEmpty()
/**
 * This method is currently not implemented in the loopback driver.
 */
	{
	LOGTEXT2(_L8("PKTLOOPBACK:NotifyOutputEmpty is not supported:  Unit %d..."), iPortName);
	}

void CHWPort::NotifyOutputEmptyCancel()
/**
 * This method is currently not implemented in the loopback driver.
 */
	{
	LOGTEXT2(_L8("PKTLOOPBACK:NotifyOutputEmptyCancel is not supported:  Unit %d..."), iPortName);
	}

TInt CHWPort::GetFlowControlStatus(TFlowControl& /* aFlowControl */)
/**
 * This method is currently not implemented in the loopback driver.
 *
 * @return	KErrNotSupported
 */
	{
	LOGTEXT2(_L8("PKTLOOPBACK:GetFlowControlStatus is not supported:  Unit %d..."), iPortName);

	return KErrNotSupported;
	}

TInt CHWPort::GetRole(TCommRole& aRole)
/**
 * This method returns the current Role that this port is playing (ECommRoleDCE or ECommRoleDTE)
 *
 * @param	aRole	-	a reference to a TCommRole to return the role value in.
 *
 * @return	KErrNone
 */
	{
	LOGTEXT3(_L8("PKTLOOPBACK:GetRole:  Unit %d, aRole %d..."), iPortName, aRole);

	aRole = iRole;
	return KErrNone;
	}

TInt CHWPort::SetRole(TCommRole aRole)
/**
 * This method sets the role of the port.  Additionally, it sets the default state of the
 * signals for this type of port.  This is the first place where signals are set as a port
 * is opening.  The ports will assert their output signals with the exception of DCD (which
 * is an output signal from the DCE).  DCD is left to be driven by the test harness.
 *
 * A test could be put into place to insure that each port is in a different role.  This was
 * not done at this time for backwards compatibility reasons.
 *
 * @param	aRole	-	the TCommRole value that this port should be set to.
 *
 * @return	KErrNone
 */
	{
	LOGTEXT3(_L8("PKTLOOPBACK:SetRole:  Unit %d, aRole %d..."), iPortName, aRole);

	if (ECommRoleDTE == aRole)
		{
		SetSignalsToMark(KSignalDTR|KSignalRTS);
		}
	else // DCE
		{
		SetSignalsToMark(KSignalDSR|KSignalCTS);
		}


	// Informational test only.  This will produce output to the log file if both sides have the
	// same role set.  With both sides having the same role, none of the signal handling will
	// function properly.
#if defined (_DEBUG)
	if (iLoopbackPort)
		{
		TCommRole otherSide;
		iLoopbackPort->GetRole(otherSide);
		if (otherSide == aRole)
			LOGTEXT3(_L8("PKTLOOPBACK:SetRole:  Unit %d...Both sides same role %d"), iPortName, aRole);
		}
#endif

	iRole = aRole;
	return KErrNone;
	}

CHWPort::~CHWPort()
/**
 * This method is the standard destructor for the port.  It deletes the buffer
 * which was allocated to the port and resets the loopback port pointer to NULL.
 */
	{
	delete iFlowControlChange;
	iFlowControlChange = NULL;
	
	delete iReadWriteQueue;
	iReadWriteQueue = NULL;
	
	delete iWriteDelayTimer;
	iWriteDelayTimer = NULL;

	if(iLoopbackPort)
		{
		iLoopbackPort->SetLoopbackPort(NULL);
		iLoopbackPort->iWriteDelayTimer->Cancel();
		}
	
	((CHWPortFactory*)Owner())->Remove(this);
	}

void CHWPort::FreeMemory()
/**
 * This method is currently not implemented in the loopback driver.
 */
	{}

CHWPort* CHWPortFactory::FindPort(TUint aUnit)
/**
 * Returns the port corresponding to the unit aUnit
 *
 * @param aUnit The port number for the port object to find
 *
 * @return The port object corresponding to unit aUnit
 */
	{
	for (TInt i = 0; i < iPorts.Count(); i++)
		{
		if (iPorts[i]->PortName() == aUnit)
			return iPorts[i];
		}
	return NULL;
	}
	
CPort* CHWPortFactory::NewPortL(const TUint aUnit)
/**
 * This method creates a new port object.  It identifies the new object with the unit number that
 * is supplied.  If both ports that are supported by the CHWPortFactory object have been created,
 * then the loopback ports are initialized.
 *
 * @param	aUnit		-	The unit number to create.
 *
 * @return	CPort *		-	A pointer to the newly created object.
 */
	{
	LOGTEXT2(_L8("PKTLOOPBACK:NewPortL:  Unit %d"), aUnit);
	
	// Get the settings for the given port. The iLoopbackConfig will look at the test's ini file for this information
	TLoopbackConfigItem portSettings;
	if (iLoopbackConfig)
		{
		if (iLoopbackConfig->GetPortSettings(aUnit, portSettings) != KErrNone)
			User::Leave(KErrNotFound);
		}
	else
		{
		User::Leave(KErrNotFound);
		}
	
	// If the port already exists, just return it
	CHWPort *existingPort = FindPort(aUnit);
	if (existingPort)
		{
		LOGTEXT2(_L8("Loopback:NewPortL:  Unit %d already exists! @%x"), aUnit);
		return existingPort;
		}
	// Create the port and add it to the list of ports
	CHWPort *newPort;
	if (iLoopbackConfig->PortType() == ESerialLoopbackPortType)
		{
		newPort = CHWPort::NewSerialLoopbackL(aUnit, portSettings.iDelay, portSettings.iBufferSize);
		}
	else
		{
		newPort = CHWPort::NewPacketLoopbackL(aUnit, portSettings.iDelay, portSettings.iPacketLength, portSettings.iQueueLength);
		}
	CleanupStack::PushL(newPort);
	iPorts.AppendL(newPort);
	CleanupStack::Pop(newPort);
	
	// Get the name of the opposite loopback port
	TUint oppositePortName;
	if (portSettings.iPortA == aUnit)
		oppositePortName = portSettings.iPortB;
	else
		oppositePortName = portSettings.iPortA;
	
	// If the opposite loopback port is open, set it as the current port's loopback
	CHWPort *oppositePort = FindPort(oppositePortName);
	if (oppositePort)
		{
		oppositePort->SetLoopbackPort(newPort);
		newPort->SetLoopbackPort(oppositePort);
		}
	return newPort;
	}

void CHWPortFactory::Info(TSerialInfo &aSerialInfo)
/**
 * This method fills information into the passed structure.  It is required for factory objects.
 *
 * @param	aSerialInfo		-	a reference to the structure to fill in.
 */
	{
	aSerialInfo.iDescription = KSerialDescription;
	aSerialInfo.iName = KSerialName;
	aSerialInfo.iLowUnit = KCommLowUnit;
	aSerialInfo.iHighUnit = KCommHighUnit;
	}

CHWPortFactory::CHWPortFactory()
/**
 * This method is the constructor for the factory object.
 * We initialize iPorts to contain the number of ports from KMinLoopBackPort to KMaxLoopBackPort
 */
	{
	TName name(KSerialName);
	SetName(&name);
	iVersion = TVersion(KEC32MajorVersionNumber,KEC32MinorVersionNumber,KEC32BuildVersionNumber);
	
	// Create the object to read the loopback port settings for this test
	TRAP_IGNORE(iLoopbackConfig = CLoopbackConfig::NewL());
	}

void CHWPortFactory::Remove(CHWPort* aPort)
/**
 * This method removes an instance of the CHWPort from the factory package CHWPortFactory.
 * The object is not deleted.
 *
 * @param	aPort	-	The pointer to the CHWPort pointer to be removed from the factory object.
 *
 * @note	If the passed in value does not match a current port, this method will panic.
 */
	{
	LOGTEXT2(_L8("PKTLOOPBACK:Remove:  Port %d"), aPort->PortName());
	for (TInt i = 0; i < iPorts.Count(); i++)
		{
		if(iPorts[i] == aPort)
			{
			iPorts.Remove(i);
			return;
			}
		}
	User::Panic(_L("CHWPortFactory: Port not found"),0);
	}

CHWPortFactory::~CHWPortFactory()
/**
 * This method is the destructor for the factory object.
 */
	{
    ASSERT(iPorts.Count()==0);
    iPorts.Close();
    
	delete iLoopbackConfig;
	iLoopbackConfig = NULL;
	LOGDESTROY();
	}
	
/**
Returns capabilities for requested port
*/
TSecurityPolicy CHWPortFactory::PortPlatSecCapability(TUint /*aPort*/) const
	{
	return TSecurityPolicy(TSecurityPolicy::EAlwaysPass);	
	}

extern "C"
	{
	IMPORT_C CSerial * LibEntry(void);	// Force export 

	EXPORT_C CSerial * LibEntry(void)
	/**
	 * This method is the library's main entry point.  It simply new's the factory object.
	 */
		{
		return new CHWPortFactory;
		}

	} // extern "C"


