bluetooth/btsdp/server/protocol/listener.cpp
changeset 0 29b1cd4cb562
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bluetooth/btsdp/server/protocol/listener.cpp	Fri Jan 15 08:13:17 2010 +0200
@@ -0,0 +1,563 @@
+// Copyright (c) 2000-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 <bluetooth/logger.h>
+#include "listener.h"
+#include "reqhandler.h"
+#include "sdpconsts.h"
+#include "epocsvr.h"
+#include <bt_sock.h>
+#include <es_sock.h>
+
+#ifdef __FLOG_ACTIVE
+_LIT8(KLogComponent, LOG_COMPONENT_SDP_SERVER);
+#endif
+
+#ifdef _DEBUG
+PANICCATEGORY("sdplist");
+#endif
+
+//The size of a the buffer that's used to read a fragment of the incoming data
+//Typical SDP requests are under 100 bytes. 512 is chosen because it is the 
+//closest power of 2 to the spec default MTU (672). We expect it is big enough
+//to handle most requests while not taking too much unnecessary space
+const TInt KRecvFragmentSize = 512;
+//The initial size of the buffer used to assemble the fragmented data from multiple 
+//continuation read. It is initilised to 4 times of the fragment size in the hope 
+//that on demand growing of the buffer will rarely be necessary.
+const TInt KRecvInitSize	 = 2048;
+
+CSdpConnection* CSdpConnection::NewL(RSocket& aSocket, CSdpDatabase& aDatabase)
+/**
+	Spawn of a new connection, using this pre-connected socekt.
+**/
+	{
+	LOG_STATIC_FUNC
+	CSdpConnection* self = new (ELeave)CSdpConnection(aSocket, aDatabase);
+	CleanupStack::PushL(self);
+	self->ConstructL();
+	CleanupStack::Pop();
+	return self;
+	}
+
+CSdpConnection::~CSdpConnection()
+	{
+	LOG_FUNC
+	LOG1(_L("CSdpConnection::~CSdpConnection() 0x%08x"), this);
+	Cancel();
+
+	// Delete the active objects that callback on the connection before
+	// closing the socket so that they don't try and use a bad handle.
+	delete iSDPClientTimer;
+	delete iReader;
+
+	iSocket.Close();
+	
+	// Delete the various buffers used for the connection
+	delete iReadFragmentHBuf;
+	delete iReadTotalHBuf;
+	delete iWriteHBuf;
+
+	iLink.Deque();	// Take ourselves off the connection Q.
+	}
+
+/**
+Handles the completion of asynchronous write and shutdown operations.
+(reads are handled by the separate CSdpConnectionReader active object).
+*/
+void CSdpConnection::RunL()
+	{
+	LOG_FUNC
+	LOG2(_L("CSdpConnection::RunL() 0x%08x state=%d"), this, iConnState);
+	if (iConnState == EShutting)
+		{
+		delete this;
+		}
+	else
+		{
+		// If not in the shutting state, then we must be in the writing state.
+		__ASSERT_DEBUG(iConnState == EWriting, PanicServer(ESdpBadState));
+		User::LeaveIfError(iStatus.Int()); // On error go to the error handling routine
+		// If here then a write successfully completed, so return to
+		// the reading state.
+		iConnState = EReading;
+		}
+	}
+
+void CSdpConnection::HandleReadL()
+	{
+	LOG_FUNC
+	iSDPClientTimer->Cancel();	// client is alive, cancel timer
+	LOG1(_L("Received %d bytes of request data [contents]"), iReadFragmentBuf.Length());
+	FTRACE(FHex(iReadFragmentBuf));
+
+	// Adjust the fragmented reading buffers as appropriate
+	TInt remainingDataLength = iRemainingDataLength();
+	TInt totalDataLength = remainingDataLength + iReadFragmentBuf.Length();
+	if(iReadTotalBuf.Length() + totalDataLength > iReadTotalBuf.MaxLength())
+		{
+		iReadTotalHBuf = iReadTotalHBuf->ReAllocL(iReadTotalBuf.MaxLength() + totalDataLength);
+		iReadTotalBuf.Set(iReadTotalHBuf->Des()); //in case the buffer has been moved in ReAlloc
+		}
+	iReadTotalBuf.Append(iReadFragmentBuf);
+
+	// Queue the next read up to always have a read outstanding.
+	// NOTE: This should be called before the ParseNextPacketL to
+	// ensure the connection state is correct.
+	QueRead();
+
+	// If we have a complete L2CAP datagram then parse it, otherwise
+	// continue the fragmented read.
+	if(remainingDataLength > 0)
+		{
+        LOG1(_L("Receive Continue...(%d bytes left to read)"), iRemainingDataLength());
+		}
+	else
+		{
+		ParseNextPacketL();
+		iReadTotalBuf.Zero();
+		}
+	}
+
+void CSdpConnection::DoCancel()
+	{
+	LOG_FUNC
+   	// There isn't async break in handling state, hence something is wrong if we are here
+	__ASSERT_DEBUG(iConnState != EHandling, PanicServer(ESdpBadState));
+
+	iReader->Cancel();
+	iSocket.CancelWrite();
+	// Can't cancel a shutdown request
+	}
+
+TInt CSdpConnection::RunError(TInt __DEBUG_ONLY(aError))
+	{
+	LOG_FUNC
+	// Not a recoverable error, disconnect
+	LOG1(_L("CSdpConnection::Error(%d). Aborting link!"), aError);
+	ShutdownConnection();
+
+	return KErrNone;
+	}
+
+void CSdpConnection::ClientTimeout()
+	{
+	LOG_FUNC
+	LOG(_L("CSdpListener::ClientTimeout()"));
+	LOG(_L("Disconnecting Inactive client"));
+	delete this;
+	}
+
+CSdpConnection::CSdpConnection(RSocket& aSocket, CSdpDatabase& aDatabase)
+	: CActive(CActive::EPriorityStandard),
+	iSocket(aSocket),
+	iDatabase(aDatabase),
+	iReadFragmentBuf(0,0),
+	iReadTotalBuf(0,0),
+	iWriteBuf(0,0)
+	{
+	LOG_FUNC
+	LOG1(_L("CSdpConnection::CSdpConnection 0x%08x"), this);
+	CActiveScheduler::Add(this);
+	}
+
+void CSdpConnection::ConstructL()
+	{
+	LOG_FUNC
+	TInt outMTU;
+	User::LeaveIfError(iSocket.GetOpt(KL2CAPOutboundMTUForBestPerformance, KSolBtL2CAP, outMTU));
+	TInt mru;
+	User::LeaveIfError(iSocket.GetOpt(KL2CAPInboundMTU, KSolBtL2CAP, mru));
+
+	iReadFragmentHBuf = HBufC8::NewL(KRecvFragmentSize);
+	iReadFragmentBuf.Set(iReadFragmentHBuf->Des());
+	iReadTotalHBuf = HBufC8::NewL(KRecvInitSize); //will grow on demand
+	iReadTotalBuf.Set(iReadTotalHBuf->Des());
+	
+	iWriteHBuf = HBufC8::NewL(outMTU);
+	iWriteBuf.Set(iWriteHBuf->Des());
+	iWriteBuf.SetMax();
+	iResponse.iParams.Set(&iWriteBuf[KSdpPduHeaderSize], 0, outMTU-KSdpPduHeaderSize);
+
+	iSDPClientTimer = CSdpClientTimer::NewL(*this);
+	iReader = CSdpConnectionReader::NewL(*this);
+	QueRead();
+	}
+
+void CSdpConnection::ParseNextPacketL()
+/**
+	Parse a single SDP PDU out of the L2CAP packet received.
+**/
+	{
+	LOG_FUNC
+	LOG(_L("CSdpConnection::ParseNextPacketL()"));
+	iConnState = EHandling;	// So that the RunError will handle leaves correctly
+	TInt rem = iReadTotalBuf.Length();
+	if (rem < KSdpPduHeaderSize)
+		{
+		// Incomplete header. Let request hadler cope with the error
+		User::Leave(KErrUnderflow); // Will cause "Invalid Pdu Size" error
+		}
+	iRequest.iPduId   = iReadTotalBuf[KSdpPduIdOffset];
+	iRequest.iTransId = BigEndian::Get16(&iReadTotalBuf[KSdpPduTransIdOffset]);
+	iResponse.iTransId = iRequest.iTransId; // Response has same TransID as request.
+
+	TUint16 paramlen= BigEndian::Get16(&iReadTotalBuf[KSdpPduParamLengthOffset]);
+	if ((rem != KSdpPduHeaderSize + paramlen) || (paramlen == 0))
+		{
+		// Not enough parameter data.
+		User::Leave(KErrUnderflow); // Will cause "Invalid Pdu Size" error
+		}
+	iRequest.iParams.Set(&iReadTotalBuf[KSdpPduHeaderSize], paramlen, paramlen);
+	iResponse.iParams.Zero();
+	LOG3(_L("Parsed SDP PDU ID %d, transaction ID %d, paramater length %d, [params]"), iRequest.iPduId, iRequest.iTransId, paramlen);
+	FTRACE(FHex(iRequest.iParams));
+
+	SdpReqHandler::HandleL(iDatabase, iRequest, iResponse);
+	// No leave occured, so response was formed okay.
+	// Now write it back to the client
+	WriteResponse();
+	}
+
+void CSdpConnection::QueRead()
+	{
+	LOG_FUNC
+	__ASSERT_DEBUG(!iReader->IsActive(), PanicServer(ESdpReadAlreadyOutstanding));
+	iConnState = EReading;
+	iSocket.Recv(iReadFragmentBuf, KSockReadContinuation, iReader->iStatus, iRemainingDataLength);
+	iReader->SetActive();
+	iSDPClientTimer->Start(); // gate the read with a timeout to guard against inactive clients
+	}
+
+void CSdpConnection::WriteResponse()
+	{
+	LOG_FUNC
+	LOG1(_L("Writing %d bytes of response data [contents]"), iWriteBuf.Length());
+	__ASSERT_DEBUG(!IsActive(), PanicServer(ESdpOutstandingOperation));
+	TInt paramLen = iResponse.iParams.Length();
+	ASSERT_DEBUG(paramLen <= static_cast<TInt>(KMaxTUint16));
+	
+	iWriteBuf.SetLength(paramLen + KSdpPduHeaderSize);
+	iWriteBuf[KSdpPduIdOffset] = iResponse.iPduId;
+	BigEndian::Put16(&iWriteBuf[KSdpPduTransIdOffset], iResponse.iTransId);
+	BigEndian::Put16(&iWriteBuf[KSdpPduParamLengthOffset], TUint16(paramLen));
+	
+	FTRACE(FHex(iWriteBuf));
+	iConnState = EWriting;
+	iSocket.Write(iWriteBuf, iStatus);
+	SetActive();
+	}
+
+void CSdpConnection::ShutdownConnection()
+	{
+	LOG_FUNC
+	__ASSERT_DEBUG(!IsActive(), PanicServer(ESdpOutstandingOperation));
+	iConnState = EShutting;
+	iSocket.Shutdown(RSocket::ENormal, iStatus);
+	SetActive();
+	}
+
+/**
+Handles the completion of a read operation.
+*/
+void CSdpConnection::ReadComplete(TInt aStatus)
+	{
+	LOG_FUNC
+	LOG1(_L("CSdpConnection::ReadComplete with %d"), aStatus);
+	if(aStatus == KErrNone)
+		{
+		if(iConnState == EReading)
+			{
+			TRAP(aStatus, HandleReadL());
+
+			if ((aStatus != KErrNone) && (iConnState == EHandling))
+				{
+				// Problem with received pdu, send an error response to the remote
+				aStatus = SdpReqHandler::RunError(aStatus, iRequest, iResponse);
+				if (aStatus == KErrNone)
+					{
+					LOG(_L("Handled bad SDP request. Sending response..."));
+					WriteResponse();
+			        iReadTotalBuf.Zero();
+					}
+				}
+			}
+		else
+			{
+			// If we complete a read while handling an existing request
+			// the other side is behaving badly.  We deal with this by
+			// ignoring them and disconnecting the connection.
+			LOG1(_L("ReadComplete during another operation(%d)!! Disconnecting..."), iConnState);
+			Cancel();
+			ShutdownConnection();
+			}
+		}
+
+	// deal with any unhandled errors by disconnecting
+	if(aStatus != KErrNone)
+		{
+		Cancel();
+		ShutdownConnection();
+		}
+	}
+
+void CSdpConnection::CancelRead()
+	{
+	LOG_FUNC
+	iSDPClientTimer->Cancel();
+	iSocket.CancelRecv();	
+	}
+
+CSdpConnectionReader* CSdpConnectionReader::NewL(CSdpConnection& aConnection)
+	{
+	LOG_STATIC_FUNC
+	CSdpConnectionReader* self = new(ELeave) CSdpConnectionReader(aConnection);
+	CleanupStack::PushL(self);
+	self->ConstructL();
+	CleanupStack::Pop(self);
+	return self;
+	}
+
+CSdpConnectionReader::CSdpConnectionReader(CSdpConnection& aConnection)
+	: CActive(EPriorityStandard)
+	, iConnection(aConnection)
+	{
+	LOG_FUNC
+	}
+
+void CSdpConnectionReader::ConstructL()
+	{
+	LOG_FUNC
+	CActiveScheduler::Add(this);
+	}
+
+CSdpConnectionReader::~CSdpConnectionReader()
+	{
+	LOG_FUNC
+	Cancel();
+	}
+
+void CSdpConnectionReader::RunL()
+	{
+	LOG_FUNC
+	iConnection.ReadComplete(iStatus.Int());
+	}
+
+void CSdpConnectionReader::DoCancel()
+	{
+	LOG_FUNC
+	iConnection.CancelRead();
+	}
+
+CSdpClientTimer* CSdpClientTimer::NewL(CSdpConnection& aConnection)
+	{
+	LOG_STATIC_FUNC
+	CSdpClientTimer* t = new (ELeave) CSdpClientTimer(aConnection);
+	CleanupStack::PushL(t);
+	t->ConstructL();
+	CleanupStack::Pop(t);
+	return t;
+	}
+
+
+CSdpClientTimer::CSdpClientTimer(CSdpConnection& aConnection)
+: CTimer(EPriorityStandard), iConnection(aConnection)
+	{
+	LOG_FUNC
+	}
+
+void CSdpClientTimer::Start()
+	{
+	LOG_FUNC
+	After(KSDPClientTimeout * 1000000);
+	}
+
+void CSdpClientTimer::ConstructL()
+	{
+	LOG_FUNC
+	CTimer::ConstructL();
+	CActiveScheduler::Add(this);
+	}
+
+void CSdpClientTimer::RunL()
+	{
+	LOG_FUNC
+	iConnection.ClientTimeout();
+	}
+
+
+CSdpListener* CSdpListener::NewL(RSocketServ& aSockServ, TInt aQueSize, CSdpDatabase& aDatabase)
+	{
+	LOG_STATIC_FUNC
+	CSdpListener* self = NewLC(aSockServ, aQueSize, aDatabase);
+	CleanupStack::Pop();
+	return self;
+	}
+
+CSdpListener* CSdpListener::NewLC(RSocketServ& aSockServ, TInt aQueSize, CSdpDatabase& aDatabase)
+	{
+	LOG_STATIC_FUNC
+	CSdpListener* self = new (ELeave) CSdpListener(aSockServ, aDatabase);
+	CleanupStack::PushL(self);
+	self->ConstructL(aQueSize);
+	return self;
+	}
+
+
+CSdpListener::~CSdpListener()
+	{
+	LOG_FUNC
+	while (!iConns.IsEmpty())
+		{
+		CSdpConnection* conn = iConns.First();
+		delete conn;
+		}
+	
+	Cancel(); //Ensure this active object is not still active.
+
+	iListener.Close();
+	iAccepter.Close();
+	iDelayAcceptTimer.Close();
+	}
+
+CSdpListener::CSdpListener(RSocketServ& aSockServ, CSdpDatabase& aDatabase)
+	: CActive(EPriorityLow), iSockServ(aSockServ), iDatabase(aDatabase),
+	iIsAcceptDelayed(ETrue), iAcceptDelay(KInitialAcceptDelay),
+	iConns(_FOFF(CSdpConnection, iLink))
+	{
+	LOG_FUNC
+	CActiveScheduler::Add(this);
+	}
+
+void CSdpListener::ConstructL(TInt aQueSize)
+	{
+	LOG_FUNC
+	User::LeaveIfError(iDelayAcceptTimer.CreateLocal());
+	iQueSize = aQueSize; //set before calling OpenListeningSocket
+	QueAcceptL();
+	}
+
+void CSdpListener::OpenListeningSocketL()
+	{
+	LOG_FUNC
+	User::LeaveIfError(iListener.Open(iSockServ, _L("L2CAP")));
+
+	TL2CAPSockAddr addr;
+	addr.SetPort(KSDPPSM);
+	TBTServiceSecurity sdpSecurity;
+	sdpSecurity.SetUid(KUidServiceSDP);
+	sdpSecurity.SetAuthentication(EMitmNotRequired);
+	sdpSecurity.SetAuthorisation(EFalse);
+	sdpSecurity.SetEncryption(EFalse);
+	sdpSecurity.SetDenied(EFalse);
+
+	addr.SetSecurity(sdpSecurity);
+
+	User::LeaveIfError(iListener.Bind(addr));
+	User::LeaveIfError(iListener.Listen(iQueSize));
+	}
+
+void CSdpListener::TryRestartL()
+	{
+	LOG_FUNC
+	if (iIsAcceptDelayed)
+		{
+		LOG(_L("SDP Server: New session open when accept delayed. Retrying right now"));
+		Cancel();
+		QueAcceptL();
+		}
+	}
+
+void CSdpListener::RunL()
+	{
+	LOG_FUNC
+	if(!iIsAcceptDelayed)
+		{
+		User::LeaveIfError(iStatus.Int());
+		CSdpConnection *conn = CSdpConnection::NewL(iAccepter, iDatabase);
+		iConns.AddFirst(*conn);
+		iAcceptDelay = KInitialAcceptDelay;
+		}
+	QueAcceptL();
+	}
+
+
+void CSdpListener::DoCancel()
+	{
+	LOG_FUNC
+	if (iListener.SubSessionHandle())
+		{
+		iListener.CancelAccept();
+		iListener.CancelRecv();
+		}
+	if(iIsAcceptDelayed)
+		{
+		iDelayAcceptTimer.Cancel();
+		}
+	}
+
+
+TInt CSdpListener::RunError(TInt aError)
+/**
+	Handle leave from RunL.
+	If this is called, the accept socket is assumed to be cleaned up already
+**/
+	{
+	LOG_FUNC
+	LOG1(_L("CSdpListener::Error(%d)"), aError);
+	// Just try to get an accpet going. If this fails, we'll keep trying using 
+	// an exponential timeout, as OOM could cause this to keep failing.
+	iListener.Close();
+	iAccepter.Close();
+	iIsAcceptDelayed = ETrue;
+
+	if(iAcceptDelay<=(KMaxAcceptDelay/2))
+		{
+		//alter ready for next attempt
+		iAcceptDelay*=2;
+		}
+	else if (aError == KErrHardwareNotAvailable)
+		{
+/*		the hardware has gone and we've met our retry limit
+		we'll wait until a new session is created
+		before attempting to get a connection
+		this makes sense as the services will have detected hardware off
+		and typically closed their sockets and handles to SDP.
+		At some point services will try to re-register and this will trigger
+		the SDP server to attempt to reaccept connections.
+*/
+		return KErrNone;
+		}
+
+	iDelayAcceptTimer.After(iStatus, iAcceptDelay); 
+	SetActive();
+	
+	return KErrNone;
+	}
+
+void CSdpListener::QueAcceptL()
+	{
+	LOG_FUNC
+	User::LeaveIfError(iAccepter.Open(iSockServ));
+	if(iIsAcceptDelayed)
+		{
+		OpenListeningSocketL();
+		iIsAcceptDelayed = EFalse;
+		}
+	iListener.Accept(iAccepter, iStatus);
+	SetActive();
+	}
+
+