// 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:
//

#include "hciloggerbtsnoop.h"

static const TInt KSnoopLogFileHeaderSize = 16; // bytes
static const TInt KSnoopLogRecordHeaderSize = 24; // bytes
static const TUint KSnoopLogVersion = 1;

static const TInt KHCILoggerFileCloseTime = 5000000; // 5 seconds

_LIT8(KSnoopLogBlankDescriptor, "");
_LIT8(KSnoopLogFileHeaderFormat, "btsnoop\0%M%M");
_LIT8(KSnoopLogRecordHeaderFormat, "%M%M%M%M%M%M");

#ifdef __WINS__
_LIT(KSnoopLogFileH1,		"\\shared\\hcilogger\\hcilog_H1.log");
_LIT(KSnoopLogFileH4,		"\\shared\\hcilogger\\hcilog_H4.log");
_LIT(KSnoopLogFileBCSP,		"\\shared\\hcilogger\\hcilog_Bcsp.log");
_LIT(KSnoopLogFileH5,		"\\shared\\hcilogger\\hcilog_H5.log");
_LIT(KSnoopLogFileDefault,	"\\shared\\hcilogger\\hcilog_unknown.log");
#else
_LIT(KSnoopLogFileH1,		"e:\\shared\\hcilogger\\hcilog_H1.log");
_LIT(KSnoopLogFileH4,		"e:\\shared\\hcilogger\\hcilog_H4.log");
_LIT(KSnoopLogFileBCSP,		"e:\\shared\\hcilogger\\hcilog_Bcsp.log");
_LIT(KSnoopLogFileH5,		"e:\\shared\\hcilogger\\hcilog_H5.log");
_LIT(KSnoopLogFileDefault,	"e:\\shared\\hcilogger\\hcilog_unknown.log");
#endif	//__WINS__

#ifndef EKA2
GLDEF_C TInt E32Dll(TDllReason)
#else
GLDEF_C TInt E32Dll()
#endif
	{
	return KErrNone;
	}

CHCILoggerBtSnoop* CHCILoggerBtSnoop::NewLC()
	{
	CHCILoggerBtSnoop* self = new (ELeave) CHCILoggerBtSnoop;
	CleanupStack::PushL(self);
	self->ConstructL();
	return self;
	}

CHCILoggerBtSnoop::CHCILoggerBtSnoop()
	{
	}

void CHCILoggerBtSnoop::ConstructL()
	{
	iFileCloseTimer = CHCILoggerFileCloseTimer::NewL(iLogFile);
	for (TInt i = 0; i <= TInt(KHCILoggerControllerToHost | KHCILoggerCommandOrEvent); i++)
		{
		CHCILoggerBufferedFrame* temp = new(ELeave) CHCILoggerBufferedFrame;
		iBuffers.Append(temp); // array now "owns" temp
		}
	}

void CHCILoggerBtSnoop::DoInitialise(TInt aType)
	{
	iDatalinkType = aType;
	iFs.Connect();
	OpenFile(); // if it fails, just ignore it - try later when writing
	}

CHCILoggerBtSnoop::~CHCILoggerBtSnoop()
	{
	iBuffers.ResetAndDestroy();
	iLogFile.Close();
	iFs.Close();
	delete iFileCloseTimer;
	}

void CHCILoggerBtSnoop::LogFrame(TUint aFrameFlags, const TDesC8& aDesc)
	{
	TInt packetFlags = aFrameFlags & (KHCILoggerControllerToHost | KHCILoggerCommandOrEvent);
	CHCILoggerBufferedFrame* buffer = iBuffers[packetFlags];
	if (aFrameFlags & KHCILoggerFrameFragmented)
		{
		buffer->BufferData(aDesc);
		return;
		}

	TBtSnoopPacketRecord record;
	record.iOriginalLength = aDesc.Length();
	record.iIncludedLength = record.iOriginalLength;
	record.iPacketFlags = aFrameFlags;
	record.iCumulativeDrops = iCumulativeDrops;
	iCurrentTime.HomeTime();
	record.iTimestampMicroseconds = iCurrentTime.Int64();

	// if the file is not open, try to open it
	if (!iLogFile.SubSessionHandle())
		{
		if (OpenFile() != KErrNone)
			{
			return;
			}
		}

	record.iOriginalLength += buffer->OriginalLength();
	TDesC8& des = buffer->Buffer();
	if (buffer->OriginalLength() > des.Length())
		{ // some data lost
		record.iIncludedLength = des.Length();
		FileWriteRecord(record, des); 
		}
	else
		{ // no data lost
		record.iIncludedLength = record.iOriginalLength;
		FileWriteRecord(record, des, aDesc);
		}
	buffer->Reset();
	}

TInt CHCILoggerBtSnoop::OpenFile()
	{
	TPtrC fileName;
	switch (iDatalinkType)
		{
	case KHCILoggerDatalinkTypeH1:
		fileName.Set(KSnoopLogFileH1);
		break;
	case KHCILoggerDatalinkTypeH4:
		fileName.Set(KSnoopLogFileH4);
		break;
	case KHCILoggerDatalinkTypeBCSP:
		fileName.Set(KSnoopLogFileBCSP);
		break;
	case KHCILoggerDatalinkTypeH5:
		fileName.Set(KSnoopLogFileH5);
		break;
	default:
		fileName.Set(KSnoopLogFileDefault);
		break;
		};

	TInt err = iLogFile.Open(iFs, fileName, EFileWrite | EFileShareAny);
	if (err == KErrNotFound)
		{ // if it doesn't already exist, create it
		//Note that the potential KErrPathNotFound error is deliberately not handled, 
		//since the shared\hcilogger directory not being present is used to indicate 
		//that logging is not desired.
		err = iLogFile.Create(iFs, fileName, EFileWrite | EFileShareAny);
		}
	TInt size = 0;
	if (err == KErrNone)
		{
		err = iLogFile.Size(size);
		}
	if (err == KErrNone)
		{
		if (size < KSnoopLogFileHeaderSize)
			{
			err = FileWriteHeader(); // write will automatically start at pos 0,
			}						 // hence overwriting the file
		else
			{ // want to append the data to the current file
			TInt writePos = KSnoopLogFileHeaderSize; // KSnoopLogFileHeaderSize is const, so can't pass in Seek()
			iLogFile.Seek(ESeekEnd, writePos);
			}
		}
	if (err)
		{
		iLogFile.Close(); // just in case
		}
	return err;
	}

TInt CHCILoggerBtSnoop::FileWriteHeader()
	{
	TBuf8<KSnoopLogFileHeaderSize> buf;
	buf.Format(KSnoopLogFileHeaderFormat, KSnoopLogVersion, iDatalinkType);
	TInt err = iLogFile.Write(buf);
	iFileCloseTimer->ResetTimer();
	return err;
	}

void CHCILoggerBtSnoop::FileWriteRecord(const TBtSnoopPacketRecord& aRecord, const TDesC8& aBufferedData, const TDesC8& aNewData)
	{
	TBuf8<KSnoopLogRecordHeaderSize> buf;
	buf.Format(KSnoopLogRecordHeaderFormat, aRecord.iOriginalLength,
											aRecord.iIncludedLength,
											aRecord.iPacketFlags,
											aRecord.iCumulativeDrops,
											I64HIGH(aRecord.iTimestampMicroseconds),
											I64LOW(aRecord.iTimestampMicroseconds));

	// if these fail, just ignore them
	iLogFile.Write(buf);	// header
	iLogFile.Write(aBufferedData);	// data
	iLogFile.Write(aNewData);	// data
	iFileCloseTimer->ResetTimer();
	}

void CHCILoggerBtSnoop::FileWriteRecord(const TBtSnoopPacketRecord& aRecord, const TDesC8& aNewData)
	{
	FileWriteRecord(aRecord, KSnoopLogBlankDescriptor, aNewData);
	}

// CHCILoggerFileCloseTimer functions //

CHCILoggerFileCloseTimer* CHCILoggerFileCloseTimer::NewL(RFile& aLogFile)
	{
	CHCILoggerFileCloseTimer* self = new(ELeave) CHCILoggerFileCloseTimer(aLogFile);
	CleanupStack::PushL(self);
	self->ConstructL();
	CleanupStack::Pop(self);
	return self;
	}

CHCILoggerFileCloseTimer::CHCILoggerFileCloseTimer(RFile& aLogFile)
: CTimer(EPriorityStandard), iLogFile(aLogFile)
	{
	}

void CHCILoggerFileCloseTimer::ConstructL()
	{
	CTimer::ConstructL();
	CActiveScheduler::Add(this);
	}

void CHCILoggerFileCloseTimer::ResetTimer()
	{
	if (IsActive())
		{
		Cancel();
		}
	After(TTimeIntervalMicroSeconds32(KHCILoggerFileCloseTime));
	}

void CHCILoggerFileCloseTimer::RunL()
	{
	iLogFile.Close();
	}

void CHCILoggerFileCloseTimer::DoCancel()
	{
	CTimer::DoCancel();
	}

CHCILoggerFileCloseTimer::~CHCILoggerFileCloseTimer()
	{
	Cancel();
	}

CHCILoggerBufferedFrame::CHCILoggerBufferedFrame()
	{
	iBuffer = HBufC8::New(0);
	}

CHCILoggerBufferedFrame::~CHCILoggerBufferedFrame()
	{
	delete iBuffer;
	}

void CHCILoggerBufferedFrame::BufferData(const TDesC8& aDesc)
	{
	if (iOriginalLength == iBuffer->Length())
		{ // then no allocations have failed so far
		TInt newLength = iOriginalLength + aDesc.Length();
		// add aDesc to buffer
		HBufC8* buf = iBuffer;
		if(buf != NULL)
			{
			if(newLength > buf->Des().MaxLength())
				{
				buf = buf->ReAlloc(newLength);
				}
			if(buf != NULL)
				{ // successful allocation
				buf->Des().Append(aDesc);
				iBuffer = buf;
				}
			// else if it failed, iBuffer stays the same
			}
		}
	iOriginalLength += aDesc.Length(); // add this on anyway
	}

void CHCILoggerBufferedFrame::Reset()
	{
	iBuffer->Des().Zero();
	iOriginalLength = 0;
	}

TInt CHCILoggerBufferedFrame::OriginalLength() const
	{
	return iOriginalLength;
	}

TDesC8& CHCILoggerBufferedFrame::Buffer() const
	{
	return *iBuffer;
	}

