diff -r 4dc88a4ac6f4 -r f6055a57ae18 obex/obexprotocol/obex/src/obexclient.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/obex/obexprotocol/obex/src/obexclient.cpp Tue Oct 19 11:00:12 2010 +0800 @@ -0,0 +1,1486 @@ +// Copyright (c) 1997-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: +// + +/** + @file + @internalComponent +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "logger.h" +#include "obexsetpathdata.h" +#include "OBEXUTIL.H" +#include "obexheaderutil.h" +#include "authentication.h" +#include "obexpacketsignaller.h" +#include "obexpackettimer.h" +#include "obexnotifyhandlerclient.h" +#include "obexerrorengine.h" + +#ifdef __FLOG_ACTIVE +_LIT8(KLogComponent, "OBEX"); +#endif + +// Constant used to identify if the last received response code field +// has been populated (i.e., if the first response has been received +// from the OBEX server). The response code field is only 7 bits (+ the +// 'final bit') so the use of this 16 bit number will ensure the value +// for KUnpopulatedResponseCode never overlaps with an actual code. +const static TUint16 KUnpopulatedResponseCode = 0xffff; + +// For debug builds check that when an underlying error is set +// it has not already been set since the start of the client operation +// and check that a last error has been set when the operation completes. +#define SET_LAST_ERROR(_error) __ASSERT_DEBUG(!iIsLastErrorSet, IrOBEXUtil::Fault(ELastErrorSetTwice)); iErrorEngine->SetLastError(CObexErrorEngine::_error); iIsLastErrorSet = ETrue +#define CHECK_LAST_ERROR_IS_SET __ASSERT_DEBUG(iIsLastErrorSet, IrOBEXUtil::Fault(ELastErrorNotSet)); iIsLastErrorSet = EFalse + + +// Constructor - set initial values +CObexClient::CObexClient() : CObex() + { + iCurrentOperation = EOpIdle; + iConnectionID = KConnIDInvalid; + iLastReceivedResponseOpcode = static_cast(KUnpopulatedResponseCode); + } + +void CObexClient::ConstructL(TObexTransportInfo& aObexTransportInfo) + { + CObex::ConstructL(aObexTransportInfo); + iNotifyHandler = new(ELeave)CObexNotifyHandlerClient(*this); + iTransportController->SetOwner(*iNotifyHandler); + + // iHeaderSet is used to store headers received in Put Response + iHeaderSet = CObexHeaderSet::NewL(); + iHeader = CObexHeader::NewL(); + iPacketProcessSignaller = CObexPacketSignaller::NewL(); + iErrorEngine = CObexErrorEngine::NewL(); + iPacketTimer = CObexPacketTimer::NewL(*this); + } + +/** Destructor. + +@publishedAll +@released +*/ +EXPORT_C CObexClient::~CObexClient() + { + LOG_LINE + LOG_FUNC + + Error(KErrCompletion); + delete iHeader; + delete iHeaderSet; + delete iPacketProcessSignaller; + delete iNotifyHandler; + delete iErrorEngine; + delete iPacketTimer; + } + +/** A call back from the the service with the password required for use with generating +the challenge response. + +@param aPassword Password + +@leave KErrNotReady if this function is not called from a MObexAuthChallengeHandler::GetUserPasswordL callback. + +@publishedAll +@released +*/ +EXPORT_C void CObexClient::UserPasswordL(const TDesC& aPassword) + { + LOG_LINE + LOG_FUNC + + //now have a password, get a nonce, and get it hashed then reply + FLOG(_L("CObexClient::UserPasswordL\n\r")); + if (GetConnectState() == EWaitForUserInput) + { + PrepareChallResponseL( aPassword); + + TObexInternalHeader hdr; + hdr.Set(TObexInternalHeader::EAuthResponse, CONST_CAST(TUint8*, iOutgoingChallResp.Ptr()), iOutgoingChallResp.Size()); + FLOG(_L("CObexClient::UserPasswordL EAuth heaqder added\n\r")); + if(iTransportController->SendPacket().InsertData(hdr)) + { + SetConnectState(EWaitForFinalResponse); + iTransportController->SendPacket().SetFinal(); + SendRequestPacket(); + FLOG(_L("CObexClient::UserPasswordL packet sent\n\r")); + + } + else + { + LEAVEIFERRORL(KErrGeneral); + } + } + else + { + LEAVEIFERRORL(KErrNotReady); + } + } + +/** Allocates and constructs a new OBEX client object. + +The received protocol information object, aObexProtocolInfoPtr, specifies the transport protocol to use: +For the standard transports the following are used, TObexIrProtocolInfo for IrDA, TObexBluetoothProtocolInfo +for Bluetooth, TObexUsbProtocolInfo for USB. + +@param aObexProtocolInfoPtr Protocol information object describing the transport to use +@return New OBEX client object + +@publishedAll +@released +*/ +EXPORT_C CObexClient* CObexClient::NewL(TObexProtocolInfo& aObexProtocolInfoPtr) + { + LOG_LINE + LOG_STATIC_FUNC_ENTRY + + TObexProtocolPolicy defaultProtocolPolicy; // no packet sizing policy specified, so use default + TObexTransportInfo* transportInfo = IrOBEXUtil::CreateTransportInfoL(aObexProtocolInfoPtr, defaultProtocolPolicy); + CleanupStack::PushL(transportInfo); + CObexClient* client = CObexClient::NewL(*transportInfo); + CleanupStack::PopAndDestroy(transportInfo); + return client; + } + +/** Allocates and constructs a new OBEX client object with packet sizing information. + +The received protocol information object, aObexProtocolInfoPtr, specifies the transport protocol to use: +For the standard transports the following are used, TObexIrProtocolInfo for IrDA, TObexBluetoothProtocolInfo +for Bluetooth, TObexUsbProtocolInfo for USB. + +The aObexProtocolPolicy parameter specifies the packet sizing policy for this OBEX object. + +@param aObexProtocolInfoPtr Protocol information object describing the transport to use +@param aObexProtocolPolicy Protocol policy object specifying the packet sizes to use +@return New OBEX client object + +@publishedAll +@released +*/ +EXPORT_C CObexClient* CObexClient::NewL(TObexProtocolInfo& aObexProtocolInfoPtr, TObexProtocolPolicy& aObexProtocolPolicy) + { + LOG_LINE + LOG_STATIC_FUNC_ENTRY + + TObexTransportInfo* transportInfo = IrOBEXUtil::CreateTransportInfoL(aObexProtocolInfoPtr, aObexProtocolPolicy); + CleanupStack::PushL(transportInfo); + CObexClient* client = CObexClient::NewL(*transportInfo); + CleanupStack::PopAndDestroy(transportInfo); + return client; + } + +/** Allocates and constructs a new OBEX client object with packet sizing information. + +The received transport information object, aObexTransportInfo, specifies the transport protocol packet sizes to use: +For the standard transports the following are used, TObexIrProtocolInfo for IrDA, TObexBluetoothProtocolInfo +for Bluetooth, TObexUsbProtocolInfo for USB. + +@param aObexTransportInfo Transport information object describing the transport and packet sizes to use +@return New OBEX client object + +@capability WriteDeviceData If the TObexIrV3TransportInfo is passed as the argument + and the associated name is valid. + +@publishedAll +@released +*/ +EXPORT_C CObexClient* CObexClient::NewL(TObexTransportInfo& aObexTransportInfo) + { + LOG_LINE + LOG_STATIC_FUNC_ENTRY + + CObexClient* self = new(ELeave) CObexClient(); + CleanupStack::PushL(self); + self->ConstructL(aObexTransportInfo); + CleanupStack::Pop(self); + return(self); + } + +/** OBEX CONNECT operation to any available remote machine, specifying an object +to pass. + +@param aObject OBEX object to pass to the remote machine +@param aStatus Asynchronous status word. On completion, KErrNone if it was +successful, or a system-wide error code + +@publishedAll +@released +*/ +EXPORT_C void CObexClient::Connect(CObexBaseObject& aObject, TRequestStatus& aStatus) + { + LOG_LINE + LOG_FUNC + + if(!AlreadyActive(aStatus)) + { + FLOG(_L("CObexClient::Connect no password but some header data\r\n")); + EmptyHeaderSet(); + iChallenge = EFalse; + OBEX_TRAP(Error, ClientCommandL(EOpConnect, static_cast(&aObject), aStatus)); + } + } + +/** OBEX CONNECT operation to any available remote machine, specifying an object +to pass and a password. + +@param aObject OBEX object to pass to the remote machine +@param aPassword Password to access remote machine +@param aStatus Asynchronous status word. On completion, KErrNone if it was +successful, or a system-wide error code + +@publishedAll +@released +*/ +EXPORT_C void CObexClient::ConnectL(CObexBaseObject& aObject, const TDesC& aPassword, + TRequestStatus& aStatus) + { + LOG_LINE + LOG_FUNC + + if(!AlreadyActive(aStatus)) + { + FLOG(_L("CObexClient::Connect with password and some header info\r\n")); + EmptyHeaderSet(); + delete iChallPassword; + iChallPassword = NULL; + iChallPassword = HBufC8::NewL(aPassword.Size()); + TPtr8 ptr = iChallPassword->Des(); + CnvUtfConverter::ConvertFromUnicodeToUtf8(ptr, aPassword); + iChallenge = ETrue; + OBEX_TRAP(Error, ClientCommandL(EOpConnect, static_cast(&aObject), aStatus)); + } + } + + +/** OBEX CONNECT operation to any available remote machine, specifying a password. + +@param aPassword Password to access remote machine +@param aStatus On completion, KErrNone if it was successful, or a system-wide +error code + +@publishedAll +@released +*/ +EXPORT_C void CObexClient::ConnectL(const TDesC& aPassword, TRequestStatus& aStatus) + { + LOG_LINE + LOG_FUNC + + if(!AlreadyActive(aStatus)) + { + FLOG(_L("CObexClient::Connect with password but no header info\r\n")); + EmptyHeaderSet(); + delete iChallPassword; + iChallPassword = NULL; + iChallPassword = HBufC8::NewL(aPassword.Length()); + TPtr8 ptr = iChallPassword->Des(); + CnvUtfConverter::ConvertFromUnicodeToUtf8(ptr, aPassword); + iChallenge = ETrue; + OBEX_TRAP(Error, ClientCommandL(EOpConnect, NULL, aStatus)); + } + } + +/** OBEX CONNECT operation to any available remote machine. + +@param aStatus Asynchronous status word. On completion, KErrNone if it was +successful, or a system-wide error code + +@publishedAll +@released +*/ +EXPORT_C void CObexClient::Connect(TRequestStatus& aStatus) + { + LOG_LINE + LOG_FUNC + + if(!AlreadyActive(aStatus)) + { + FLOG(_L("\tno password no header")); + EmptyHeaderSet(); + iChallenge = EFalse; + OBEX_TRAP(Error, ClientCommandL(EOpConnect, NULL, aStatus)); + } + } + +/** OBEX DISCONNECT operation. + +This terminates the OBEX connection, and closes the transport on receiving +any response from the server. + +@param aStatus Asynchronous status word. On completion, KErrNone on normal +disconnection, or KErrDisconnected if the server dropped the transport before +sending an OBEX response (which is valid behaviour). + +@publishedAll +@released +*/ +EXPORT_C void CObexClient::Disconnect(TRequestStatus& aStatus) + { + LOG_LINE + LOG_FUNC + + if(!AlreadyActive(aStatus)) + { + FLOG(_L("CObexClient::Disconnect\r\n")); + EmptyHeaderSet(); + OBEX_TRAP (Error, ClientCommandL(EOpDisconnect, NULL, aStatus)); + } + } + +/** OBEX PUT operation. + +Any valid header that is also present in aObject’s header mask will be sent +to the server, along with the object body specified by the implementation +of aObject. + +@param aObject OBEX object to put +@param aStatus Asynchronous status word. On completion, KErrNone if the server +accepted the object and received it fully, or the appropriate file error if +the data file could not be opened + +@publishedAll +@released +*/ +EXPORT_C void CObexClient::Put(CObexBaseObject& aObject, TRequestStatus& aStatus) + { + LOG_LINE + LOG_FUNC + + if(!AlreadyActive(aStatus)) + { + FLOG(_L("\tnot already active")); + EmptyHeaderSet(); + OBEX_TRAP(Error, ClientCommandL(EOpPut, static_cast(&aObject), aStatus)); + } + } + +/** OBEX GET operation. + +The caller specifies in aObject the headers to send to the server to specify +the object to get: normally just a name is expected. If the server can serve +the request, the object it returns will be loaded into aObject on completion. +All headers returned by the server that are also allowed by the object’s +header mask will be loaded into the relevant attributes of aObject. Any object +body is stored according to the implementation type of the CObexBaseObject +passed. + +@param aObject OBEX object to get; on completion, the retrieved object +@param aStatus Asynchronous status word. On completion, KErrNone if the server +passed back an OBEX object, or the appropriate file error if the data file +could not be opened + +@publishedAll +@released +*/ +EXPORT_C void CObexClient::Get(CObexBaseObject& aObject, TRequestStatus& aStatus) + { + LOG_LINE + LOG_FUNC + + if(!AlreadyActive(aStatus)) + { + FLOG(_L("CObexClient::Get\r\n")); + EmptyHeaderSet(); + OBEX_TRAP(Error, ClientCommandL(EOpGet, static_cast(&aObject), aStatus)); + } + } + +/** OBEX SETPATH operation. + +This changes the remote device's current path. + +@param aPathInfo Information to send in the SETPATH command. If you do not +which to send a name, make sure CObex::TSetPathInfo::iNamePresent is set to +false. +@param aStatus Asynchronous status word. On completion, KErrNone if successful, +or a system-wide error code + +@publishedAll +@released +*/ +EXPORT_C void CObexClient::SetPath(TSetPathInfo& aPathInfo, TRequestStatus& aStatus) + { + LOG_LINE + LOG_FUNC + + if(!AlreadyActive(aStatus)) + { + FLOG(_L("CObexClient::SetPath\r\n")); + EmptyHeaderSet(); + OBEX_TRAP(Error, ClientCommandL(EOpSetPath, static_cast(&aPathInfo), aStatus)); + } + } + +/** OBEX ABORT operation. + +The function sends the OBEX abort command to the remote machine if a multi-packet +operation (i.e. PUT or GET) is in progress. An operation in progress will +complete with KErrAbort. + +@publishedAll +@released +*/ +EXPORT_C void CObexClient::Abort() + { + LOG_LINE + LOG_FUNC + + if(iPendingRequest &&(iCurrentOperation == EOpPut + || iCurrentOperation == EOpGet || iCurrentOperation == EOpGetResponse)) + { + // We have two phase abort, async. abort request, followed by async. response + // To achive this without extra members we use extra operations in TOperation + // EOpAbortNoFBit for the asynchronous request + // EOpAbort for waiting for the response + iCurrentOperation = EOpAbortNoFBit; + } + } + +/** +This function can be called following the successful completion of a Put, +and will return a reference to a CObexHeaderSet containing all the headers +that were contained in the final Put response packet returned from the +peer Obex server. + +The headers in the header set will be deleted by any subsequent call to +CObexClient functions that trigger Obex commands (ie. Connect, Disconnect, +Put, Get, SetPath). + +The reference should not be retained beyond the end of the lifetime of the +CObexClient object. + +@publishedAll +@released + +@return const reference to a Headerset containing headers returned in final +Put response packet. +*/ +EXPORT_C const CObexHeaderSet& CObexClient::GetPutFinalResponseHeaders() + { + LOG_LINE + LOG_FUNC + + return *iHeaderSet; + } + +/** Sets a final packet observer. + +This replaces any previous observer. The observer will receive a callback +when a final packet for a put or get request begins to be sent and +another when the send completes. Although the start and finish callbacks +are guaranteed to be in order, no guarantees are made about the ordering +with respect to the completion of the put or get request. + +This does not transfer ownership. + +@publishedAll +@released +@param aObserver The observer to receive packet process events. This may + be NULL. +*/ +EXPORT_C void CObexClient::SetFinalPacketObserver(MObexFinalPacketObserver* aObserver) + { + iPacketProcessSignaller->SetFinalPacketObserver(aObserver); + } + +/** Get last server response code +This method returns the last received OBEX response code. +The method must not be called prior to a response notification being received by the +client application. If the method is called a panic will be raised. + +@return The last received OBEX response code. +@panic ENoResponseCodeToReturn Panics if the method is called prior to a response being received from the OBEX server. +@publishedAll +@released +*/ +EXPORT_C TObexResponse CObexClient::LastServerResponseCode() const + { + LOG_LINE + LOG_FUNC + // If the last received response code has not been set (i.e., no response has been received) then panic + // the client. + __ASSERT_ALWAYS(iLastReceivedResponseOpcode != KUnpopulatedResponseCode, IrOBEXUtil::Panic(ENoResponseCodeToReturn)); + return iLastReceivedResponseOpcode; + } + +/** Sets the Command Idle Time-out. + +This function sets the timer period to be observed during the progress of +all OBEX commands. If the OBEX server does not respond within the set period +the current OBEX command will be completed with an error of KErrIrObexRespTimedOut +and the transport will be disconnected. + +Setting a time-out value of 0, all OBEX commands will be allowed to continue +indefinitely. + +@param aTimeOut The time-out period in Microseconds. +@publishedAll +@released +*/ +EXPORT_C void CObexClient::SetCommandTimeOut(TTimeIntervalMicroSeconds32 aTimeOut) + { + LOG_LINE + LOG_FUNC + iCmdTimeOutDuration = aTimeOut; + } + +TBool CObexClient::AlreadyActive(TRequestStatus& aStatus) +//can't do more than one request at atime + { + if(iPendingRequest) + { + TRequestStatus* tempStatus = &aStatus; + User::RequestComplete(tempStatus,KErrAccessDenied); + return ETrue; + } + return EFalse; + } + +void CObexClient::ResetConnectionID() + { + iConnectionID = KConnIDInvalid; + iConnectionIdSet = EFalse; + } + +void CObexClient::SetConnectionID(TUint32 aConnectionID) + { + iConnectionID = aConnectionID; + iConnectionIdSet = ETrue; + } + +void CObexClient::ClientCommandL(TOperation aOp, TAny* aParam, TRequestStatus& aStatus) + { + LOG_LINE + LOG_FUNC + + // Note: Function Calls to this method must be enclosed with an if statement utilising + // CObexClient::AlreadyActive + + SetRequest(aStatus, aOp); + if(aOp != EOpConnect &&(GetConnectState() != EConnObex || iTransportController == NULL)) + { + SET_LAST_ERROR(EDisconnected); + LEAVEIFERRORL(KErrDisconnected); + } + + switch(aOp) + { + case EOpConnect: + { + switch(GetConnectState()) + { + case EConnIdle: + { + // Don t start the Packet timer, It will be done once the Transport is up(see :onTransportUp) + ResetConnectionID(); //set connection ID to invalid and flag to EFalse + iCurrentObject = static_cast(aParam); + TRAPD(err, iTransportController->ConnectL()); + if (err != KErrNone) + { + SET_LAST_ERROR(ETransportUpFailed); + LEAVEIFERRORL(err); + } + } + break; + case EConnTransport: + iCurrentObject = static_cast(aParam); + //Transport is already connected just do the connection + //Previously TransportUp was called. However the transport is already connected + //thus the only required actions are the following. + OnTransportUp(); + iTransportController->Receive(); + break; + default: + SET_LAST_ERROR(EAlreadyConnected); + CompleteRequest(KErrAlreadyExists); + } + } + break; + case EOpDisconnect: + { + iTransportController->SendPacket().Init(TUint8(EOpDisconnect)); + iTransportController->SendPacket().SetFinal(); + + // Insert the ConnectionID if necessary + if (iConnectionIdSet) + { + + TObexInternalHeader connectionID; + connectionID.Set(TObexInternalHeader::EConnectionID, iConnectionID); + + if(!iTransportController->SendPacket().InsertData(connectionID)) + { + LEAVEIFERRORL(KErrOverflow); + } + } + SendRequestPacket(); + } + break; + case EOpPut: + case EOpGet: + // The Put and Get "request" state machines are identical + // and live inside the object streamer. + { + // + // For 'Get' the params are what to 'Get' and where to put it + // For 'Put' the params are what to put + // Either way we need to send a request based on the params + iCurrentObject = static_cast(aParam); + + // Initialise the object ready for sending + TInt err = iCurrentObject->InitSend(TObexOpcode(aOp)); + if (err != KErrNone) + { + SET_LAST_ERROR(ECannotInitialiseObject); + LEAVEIFERRORL(err); + } + + //if a ConnectionID was added then it's important to add the Connection ID + //header here, allow it to be processed by PrepareNextSendPacket + if ( iConnectionIdSet ) + { + FLOG(_L("\tiConnectionIdSet is true")); + TRAPD(err, iCurrentObject->SetConnectionIdL(iConnectionID)); + if (err != KErrNone) + { + SET_LAST_ERROR(ECannotSetConnectionId); + LEAVEIFERRORL(err); + } + } + + // If we can... + if(iCurrentObject->PrepareNextSendPacket(iTransportController->SendPacket()) != CObexBaseObject::EError) + { + iTransportController->SendPacket().AddPacketProcessEvents(EObexFinalPacketStarted | EObexFinalPacketFinished); + + // ...send the first request packet + FLOG(_L("\tsending first request packet...")); + SendRequestPacket(); + } + else + { + FLOG(_L("\tleaving...")); + SET_LAST_ERROR(ECannotPreparePacket); + LEAVEIFERRORL(KErrGeneral); + } + } + break; + + case EOpSetPath: + {// Build up a setinfo packet and send it. + TSetPathInfo* info = static_cast(aParam); + + iTransportController->SendPacket().Init(TUint8(aOp)); + iTransportController->SendPacket().SetFinal(); + + TObexSetPathData data; + data.iFlags = info->iFlags; + data.iConstants = info->iConstants; + if(!iTransportController->SendPacket().InsertData(data)) + { + LEAVEIFERRORL(KErrOverflow); + } + + //insert the ConnectionID if necessary + if ( iConnectionIdSet ) + { + + TObexInternalHeader connectionID; + connectionID.Set(TObexInternalHeader::EConnectionID, iConnectionID); + + if(!iTransportController->SendPacket().InsertData(connectionID)) + { + LEAVEIFERRORL(KErrOverflow); + } + } + if(info->iNamePresent) + { + TObexInternalHeader name; + name.Set(TObexInternalHeader::EName, info->iName); + if(!iTransportController->SendPacket().InsertData(name)) + { + LEAVEIFERRORL(KErrOverflow); + } + } + SendRequestPacket(); + break; + } + default: + IrOBEXUtil::Fault(EClientCommandOpUnrecognised); + } + + } + +void CObexClient::OnPacketReceive(CObexPacket& aPacket) + { + LOG_LINE + LOG_FUNC + + //Cancel the timer + if (iPacketTimer->IsActive()) + { + iPacketTimer->Cancel(); + } + + // Store the response code including the 'final bit'. + iLastReceivedResponseOpcode = static_cast(aPacket.Opcode() | (aPacket.IsFinal() ? KObexPacketFinalBit : 0)); + + if(iTransportController->IsWriteActive()) + { + FLOG(_L("OnPacketReceive received request whilst writing... dropping connection\r\n")); + SET_LAST_ERROR(EResponseWhileWriting); + CompleteRequest(KErrNone); + ControlledTransportDown(); + return; + } + + // Initialise the send packet to ensure that we do not + // accidentally send the same packet as last time! + iTransportController->SendPacket().Init(0); + + switch(iCurrentOperation) + { + case EOpConnect: + { + FLOG(_L("CObexClient::OnPacketReceive Connect OpCode\r\n")); + if(ParseConnectPacket(aPacket) == KErrNone) + { + FLOG(_L("OnPacketReceive Connect Packet parsed \r\n")); + + //the request is only completed if now connected + if(GetConnectState() == EConnObex) + { + SET_LAST_ERROR(ENoError); + CompleteRequest(KErrNone); + } + else //otherwise still some outstanding issues + { + iTransportController->SendPacket().Init(EOpConnect); + TInt err = PrepareConnectPacket(iTransportController->SendPacket()); + if( err == KErrNone ) + { + FLOG(_L("OnPacketReceive PrepareConnectPacket SUCCESS\r\n")); + + iTransportController->SendPacket().SetFinal(); + SendRequestPacket(); + } + else if ( GetConnectState() != EWaitForUserInput ) + { + FLOG(_L("OnPacketReceive PrepareConnectPacket FAILED\r\n")); + // Last underlying error already set in PrepareConnectPacket(). + Error(err); + } + } + } + else + { + FLOG(_L("OnPacketReceive Connect Packet Parse FAILED\r\n")); + // Last underlying error already set in ParseConnectPacket(). + Error(KErrAbort); + } + } + break; + case EOpDisconnect: + { + FLOG(_L("CObexClient::OnPacketReceive DisConnect OpCode\r\n")); + switch (aPacket.Opcode()) + { + case ERespSuccess: + { + SET_LAST_ERROR(ENoError); + CompleteRequest(KErrNone); + } + break; + case ERespServiceUnavailable: + { + SET_LAST_ERROR(EBadConnectionId); + CompleteRequest(KErrDisconnected); + } + break; + default: + { + SET_LAST_ERROR(EErrorResponseFromServer); + CompleteRequest(KErrDisconnected); + } + } + + ControlledTransportDown(); // Bring down the transport anyway, otherwise we could end up in a situation where we can't + } + break; + case EOpPut: + { + FLOG(_L("CObexClient::OnPacketReceive Put OpCode\r\n")); + if(!aPacket.IsFinal()) + {// Multipacket responses not allowed. + SET_LAST_ERROR(EMultipacketResponse); + Error(KErrCommsOverrun); + break; + } + if(aPacket.Opcode() != ERespContinue + && aPacket.Opcode() != ERespSuccess + && aPacket.Opcode() != ERespPartialContent) + {// Server has returned an OBEX error response. Deal with it... + TInt err = IrOBEXUtil::EpocError(aPacket.Opcode()); + LOG1(_L8("Put error: %d"), err); + SET_LAST_ERROR(EErrorResponseFromServer); + CompleteRequest(err); + break; + } + + { + CObexBaseObject::TProgress progress = + iCurrentObject->PrepareNextSendPacket(iTransportController->SendPacket()); + iTransportController->SendPacket().AddPacketProcessEvents(EObexFinalPacketStarted | EObexFinalPacketFinished); + + // Work around for a problem both with some DoCoMo phones and Windows 2000: + // Their server sends "Success" when it should actually send "Continue", + // so we accept either here. + if (((progress == CObexBaseObject::EContinue || progress == CObexBaseObject::ELastPacket) + && (aPacket.Opcode () == ERespContinue || aPacket.Opcode () == ERespSuccess))|| + (progress == CObexBaseObject::EComplete + && (aPacket.Opcode() == ERespContinue ))) + {// More stuff to send. + SendRequestPacket(); + } + else if(progress == CObexBaseObject::EComplete + && (aPacket.Opcode() == ERespSuccess || aPacket.Opcode() == ERespPartialContent )) + {// We've completed okay. + // There may be headers to extract from this final put response + TObexInternalHeader header; + + while(aPacket.ExtractData(header)) + { + FLOG(_L("OnPacketReceive Extracting header from final Put Response")); + TInt err=IrOBEXHeaderUtil::ParseHeader(header, *iHeaderSet); + if(err != KErrNone) + { + SET_LAST_ERROR(ECannotExtractFinalPutHeader); + Error(err); + } + } + TInt err = IrOBEXUtil::EpocError(aPacket.Opcode()); + if (err == KErrNone) + { + SET_LAST_ERROR(ENoError); + } + else + { + SET_LAST_ERROR(EErrorResponseFromServer); + } + CompleteRequest(err); + } + else + {// We're out of sync with server. Give up. + SET_LAST_ERROR(EPutOutOfSync); + Error(KErrGeneral); + } + } + break; + } + case EOpGet: + { + FLOG(_L("CObexClient::OnPacketReceive Get OpCode\r\n")); + if(!aPacket.IsFinal()) + { + SET_LAST_ERROR(EMultipacketResponse); + Error(KErrCommsOverrun); //??? WTF? Comms overrun? + break; + } + + // There's only two valid responses to a 'Get' Request... + TUint8 respCode = aPacket.Opcode(); + if( respCode != ERespContinue && + respCode != ERespSuccess ) + // ... and if we didn't get one of them... + { + // ...there's not much else we can do + SET_LAST_ERROR(EErrorResponseFromServer); + CompleteRequest(IrOBEXUtil::EpocError(respCode)); + break; + } + + // Now we know the response was probably valid, see if we need + // to send another request packet + CObexBaseObject::TProgress progress; + progress = iCurrentObject->PrepareNextSendPacket(iTransportController->SendPacket()); + + if( progress != CObexBaseObject::EComplete && + progress != CObexBaseObject::ELastPacket) + // We're not done sending the request yet. + { + // So we'd better not have got a 'Success' from the remote end + if( progress == CObexBaseObject::EContinue && + respCode == ERespContinue) + { + iTransportController->SendPacket().AddPacketProcessEvents(EObexFinalPacketStarted | EObexFinalPacketFinished); + + // Not finished sending the request yet, so send the next packet + SendRequestPacket(); + } + else + { + // Something went wrong - can't continue. + SET_LAST_ERROR(EGetPrematureSuccess); + CompleteRequest(KErrGeneral); + } + + // We're done with this packet + break; + } + + // Still here? We MUST have got an EComplete from the sending state + // machine. That means that the response we're handling RIGHT NOW is + // the first packet of the thing we are 'Get'ting. + + // Must initialise the object to receive the data from the server + iCurrentObject->InitReceive(); + // Change state so we handle the next response properly + iCurrentOperation = EOpGetResponse; + } + // ** NB ** NO BREAK - DROP THROUGH TO HANDLE THE RESPONSE + case EOpGetResponse: + // Expecting a possibly multi-packet 'Get' response + { + FLOG(_L("CObexClient::OnPacketReceive GetResponse OpCode")); + + if(iCurrentObject->ParseNextReceivePacket(aPacket) == CObexBaseObject::EError) + { + SET_LAST_ERROR(EGetResponseParseError); + Error(KErrGeneral); + break; + } + if(aPacket.Opcode() == ERespContinue) + { + // Send a blank 'Get' request to solicit the next bit of the response + SendRequestPacket(EOpGet); + FTRACE(iTransportController->SendPacket().Dump()); + } + else + { + // Got all of the response, give it to the user + if (aPacket.Opcode() == ERespSuccess) + { + SET_LAST_ERROR(ENoError); + } + else + { + SET_LAST_ERROR(EErrorResponseFromServer); + } + CompleteRequest(IrOBEXUtil::EpocError(aPacket.Opcode())); + } + } + break; + case EOpSetPath: + FLOG(_L("CObexClient::OnPacketReceive Set Path OpCode\r\n")); + if (aPacket.Opcode() == ERespSuccess) + { + SET_LAST_ERROR(ENoError); + } + else + { + SET_LAST_ERROR(EErrorResponseFromServer); + } + CompleteRequest(IrOBEXUtil::EpocError(aPacket.Opcode())); + break; + case EOpAbortNoFBit: + FLOG(_L("CObexClient::OnPacketReceive Abort NoFBit OpCode\r\n")); + SendRequestPacket(EOpAbort); + iCurrentOperation = EOpAbort; + break; + case EOpAbort: + { + FLOG(_L("CObexClient::OnPacketReceive Abort OpCode\r\n")); + + SET_LAST_ERROR(EAborted); + if(aPacket.IsFinal() && aPacket.Opcode() == ERespSuccess) + {// Just complete the put/get with code aborted + CompleteRequest(KErrAbort); + } + else + {// Report an error while aborting -> causes a disconnect + Error(KErrAbort); + } + } + break; + default: +// FPrint(_L("CObexClient::OnPacketReceive unknown opcode 0x%X!"), +// iCurrentOperation); + break; + } + + // Client has finished with the read packet so queue the next read (if necessary) + if(iConnectState >= EConnTransport) + { + iTransportController->Receive(); + } + + } + +TInt CObexClient::ParseConnectPacket(CObexPacket& aPacket) + { + TInt retValue = KErrNone; + TConnectState nextState = EDropLink; + + if(!iTransportController || !iTransportController->ExtractRemoteConnectInfo(aPacket, iRemoteInfo.iVersion, iRemoteInfo.iFlags)) + { + FLOG(_L("CObexClient::ParseConnectPacket Extract Remote Info FAILED\r\n")); + SET_LAST_ERROR(ECannotExtractConnectInfo); + return KErrGeneral; + } + FLOG(_L("CObexClient::ParseConnectPacket Extract Remote Info Success\r\n")); + + TObexInternalHeader hdr; + + if ( aPacket.Opcode() == ERespSuccess ) + { + FLOG(_L("ParseConnectPacket ERespSuccess Opcode\r\n")); + + //if a simple connect was originally requested + //then a simple ERespSuccess without any headers is enough + if ( GetConnectState() == ESimpleConnRequest ) + nextState = EConnObex; + //if responding to a chall from the server + if (( GetConnectState() == EWaitForFinalResponse)&&(!iChallenge)) + nextState = EConnObex; + while(aPacket.ExtractData(hdr)) + { + switch(hdr.HI()) + { + case TObexInternalHeader::EWho: + { + FLOG(_L("ParseConnectPacket Extracting EWHO header\r\n")); + iRemoteInfo.iWho.Copy(hdr.HVByteSeq(), hdr.HVSize() > iRemoteInfo.iWho.MaxSize() ? iRemoteInfo.iWho.MaxSize() : hdr.HVSize()); + } + break; + case TObexInternalHeader::EConnectionID: + { + FLOG(_L("ParseConnectPacket Extracting EConnectionID header\r\n")); + + TUint32 newConnectionID = ((hdr.HVByteSeq()[0] << 24) + (hdr.HVByteSeq()[1] << 16) + (hdr.HVByteSeq()[2] << 8) + (hdr.HVByteSeq()[3])); + SetConnectionID(newConnectionID); + + if ( GetConnectState() == ESimpleConnRequest ) + { + nextState = EConnObex; + } + } + break; + case TObexInternalHeader::EAuthResponse: + { + if (iChallenge) + { + FLOG(_L("ParseConnectPacket Extracting EAuthResponse header\r\n")); + //extract the response into it's constituent parts + TRAPD(err, ProcessChallResponseL(hdr)); + if ( err == KErrNone ) + { + FLOG(_L("ParseConnectPacket ProcessChallResponse Success\r\n")); + + if ((GetConnectState() == EChallConnRequested) || ( GetConnectState() == EWaitForFinalResponse)) + { + nextState = EConnObex; + } + else + { + SET_LAST_ERROR(EUnexpectedChallengeResponse); + nextState = EDropLink; + retValue = KErrGeneral; + } + } + else + { + FLOG(_L("ParseConnectPacket ProcessChallResponse FAILED\r\n")); + SET_LAST_ERROR(ECannotProcessChallenge); + nextState = EDropLink; + retValue = KErrGeneral; + } + } + else + { + // if no challenge was issued, then receiving a challenge response means the peer is badly + // behaved. For this case we simply ignore the header, anything else would be too drastic. + FLOG(_L("ParseConnectPacket Chall Response received when no Chall issued\r\n")); + } + } + break; + default: + break; + }//end switch + }//end while + } + else if (aPacket.Opcode() == ERespUnauthorized ) + { + FLOG(_L("ParseConnectPacket ERespUnauthorized Opcode\r\n")); + // Only valid header here is AuthChallenge, if it's absent then authentication failed. + TBool challengeAbsent = ETrue; + while(aPacket.ExtractData(hdr)) + { + switch(hdr.HI()) + { + case TObexInternalHeader::EAuthChallenge: + { + FLOG(_L("ParseConnectPacket Extracting EAuthChallenge header\r\n")); + challengeAbsent = EFalse; + TRAPD(err, ProcessChallengeL(hdr)); + if ( !err ) + { + nextState = EConnChallRxed; + retValue = KErrNone; + } + else + { + SET_LAST_ERROR(ECannotProcessChallenge); + retValue = KErrGeneral; + } +#ifdef TEST_CLIENT_CHANGES_ITS_MIND_ABOUT_CHALLENGE + //this will force the client to challenge the server + //even if it didn't challenge initially + //this is not standard behaviour for our client + //but the server must be capable of handling this situation + iChallenge = ETrue; +#endif + } + break; + default: + break; + }//end switch + }//end while + + if (challengeAbsent) + { + SET_LAST_ERROR(EChallengeAbsent); + retValue = KErrGeneral; + } + } + else + { + FLOG(_L("ParseConnectPacket Unknown Opcode Opcode\r\n")); + SET_LAST_ERROR(EBadOpcodeInConnectPacket); + retValue = KErrGeneral; + } + + + SetConnectState(nextState); + return(retValue); + } + +void CObexClient::OnError(TInt aError) + { + LOG1(_L8("CObexClient::OnError(%d)"), aError); + CompleteRequest(aError); + + // Don't reset the Obex link for a normal disconnection, as + // technically there is no real error requiring such drastic + // action. In the case of USB, stalling the IN endpoint will + // cause another round of alternate interface changes which + // appears to confuse either Obex or the USB connector. + + // In other words the error KErrDisconnected occurs as a + // result of the transport coming down, so there's no need to + // signal a transport error in this case. + + // If this is called during construction, iTransportController could be NULL + if(aError!=KErrDisconnected && iTransportController) + { + iTransportController->SignalTransportError(); + } + } + + + +TInt CObexClient::PrepareConnectPacket(CObexPacket& aPacket) + { + TInt retValue = KErrNone; + TConnectState nextState = EDropLink; + + if (!iTransportController->InsertLocalConnectInfo(aPacket, iLocalInfo.iVersion, iLocalInfo.iFlags)) + { + FLOG(_L("CObexClient::PrepareConnectPacket local data insertion FAILED\r\n")); + SET_LAST_ERROR(ECannotInsertConnectInfo); + return(KErrGeneral); + } + FLOG(_L("CObexClient::PrepareConnectPacket local data inserted Succesfully\r\n")); + + //iCurrentObject could be NULL if Connect with no headers was requested + if ( iCurrentObject ) + iCurrentObject->PrepareConnectionHeader(iTransportController->SendPacket()); + + + if (( iChallenge )&&(!retValue)) //if a challenge is to be sent + { + FLOG(_L("PrepareConnectPacket Generating challenge\r\n")); + + retValue = GenerateChallenge(aPacket); + if ( retValue == KErrNone) + { + nextState = EChallConnRequested; + } + else + { + nextState = EDropLink; + } + } + + + //check the state of the connect instruction + if ( GetConnectState() == EConnTransport ) //first time round the loop + { + //it's going to be a simple challenge unless + //it's already been decides it's a EChallConnRequested + if ( nextState == EDropLink ) + nextState = ESimpleConnRequest; + } + else if (GetConnectState() == EConnChallRxed) + { + if (iCallBack ) + { + FLOG(_L("PrepareConnectPacket requesting password from user\r\n")); + + //ask the user for a password + //the callback does happens in the method OnPacketReceive() + nextState = EWaitForUserInput; + retValue = KErrGeneral; //mustn't send yet wait for reply from user + } + else + { + FLOG(_L("PrepareConnectPacket chall rxed but can't ask for password dropping link\r\n")); + SET_LAST_ERROR(EChallengeRejected); + retValue = KErrIrObexConnectChallRejected; + nextState = EDropLink; + } + } //or drop the link + else + { + FLOG(_L("PrepareConnectPacket unknown connect state\r\n")); + SET_LAST_ERROR(EPrepareConnectPacketIncorrectState); + retValue = KErrGeneral; + nextState = EDropLink; + } + SetConnectState(nextState); + + return(retValue); + } + +void CObexClient::OnTransportUp() + { + FLOG(_L("CObexClient::OnTransportUp\r\n")); + ResetConnectionID(); + + iTransportController->SendPacket().Init(EOpConnect); + if (PrepareConnectPacket(iTransportController->SendPacket()) == KErrNone) + { + FLOG(_L("OnTransportUp PrepareConnectPacket Succesfull\r\n")); + + iTransportController->SendPacket().SetFinal(); + FTRACE(iTransportController->SendPacket().Dump()); + SendRequestPacket(); + } + } + +void CObexClient::OnTransportDown() + { + LOG_LINE + LOG_FUNC + + // Cancel the timer + if (iPacketTimer->IsActive()) + { + iPacketTimer->Cancel(); + } + // If there's an outstanding request, an error has occured + // But don't do anything if an error has already been set (due to e.g. packet timer timeout) + if(iPendingRequest && !iIsLastErrorSet) + { + SET_LAST_ERROR(EOpOutstandingOnTransportDown); + Error(KErrIrObexClientPutPeerAborted); //extended error for IrObex,("Other IR device aborted the transfer") + } + } + +/** Signals an event has ocurred. + +@released +@internalComponent +@param aEvent The event that has ocurred (TObexPacketProcessEvent) +*/ +void CObexClient::SignalPacketProcessEvent(TInt aEvent) + { + if(aEvent & KObexPacketSignallerInterestingClientEvents) + { + // Currently all notifications are related to writes, so only need to + // clear events from the SendPacket. + iTransportController->SendPacket().RemovePacketProcessEvents(aEvent); + + iPacketProcessSignaller->Signal(static_cast(aEvent)); + } + } + +void CObexClient::SetRequest(TRequestStatus& aStatus, TOperation aOperation) + { + aStatus = KRequestPending; + iPendingRequest = &aStatus; + iCurrentOperation = aOperation; + } + +void CObexClient::CompleteRequest(const TInt aCompletion) + { + LOG_LINE + LOG_FUNC + + if(iPendingRequest) + { + // Some errors, particularly IR, only come up through OnError(). + // Thus the setting of underlying error happens here. + switch (aCompletion) + { + case KErrIrObexRespTimedOut: + { + SET_LAST_ERROR(EResponseTimeout); + } + break; + case KErrDisconnected: + { + // There are at least two ways of getting here. + // The first is disruption to the transport (e.g. IrDA beam blocked) + // and the second is attempting an operation other than connect when + // there is no connection (see CObexClient::ClientCommandL()). + // We don't want to set the last error twice in the second case, + // so check that the last error is not set before setting it here. + if (!iIsLastErrorSet) + { + SET_LAST_ERROR(EOpOutstandingOnTransportDown); + } + } + break; + case KErrCompletion: + { + // This is an error from the destructor. + // Operation is complete, successfully or otherwise. + // Since the operation is still outstanding and the interruption + // was initiated locally, the operation is effectively aborted. + SET_LAST_ERROR(EAborted); + } + break; + case KErrNotReady: + { + // This error occurs when the BT link times out. + SET_LAST_ERROR(EOpOutstandingOnTransportDown); + } + break; + case KErrCouldNotConnect: + { + // BT could not connect. + SET_LAST_ERROR(EOpOutstandingOnTransportDown); + } + break; + case KErrGeneral: + { + // Since Obex also uses this error elsewhere, only set a last error + // if none has been set previously. + if (!iIsLastErrorSet) + { + SET_LAST_ERROR(EOpOutstandingOnTransportDown); + } + } + break; + default: + { + // If an error has not yet been set, then set the last error as appropriate. + if (!iIsLastErrorSet) + { + // For all other errors. + SET_LAST_ERROR(EOpOutstandingOnTransportDown); + } + } + } + CHECK_LAST_ERROR_IS_SET; + User::RequestComplete(iPendingRequest, aCompletion);// Sets iPendingRequest = NULL + } + iPendingRequest=NULL; + iCurrentOperation = EOpIdle; + + if (iPacketTimer) + { + iPacketTimer->Cancel(); + } + } + +void CObexClient::EmptyHeaderSet() + { + LOG_LINE + LOG_FUNC + + iHeaderSet->First(); + while(iHeaderSet->This(iHeader) == KErrNone) + { + iHeaderSet->DeleteCurrentHeader(); + } + } + +void CObexClient::TimeOutCompletion() + { + LOG_LINE + LOG_FUNC + + CompleteRequest(KErrIrObexRespTimedOut); + // Disconnect transport to prevent the client entering an invalid state + ForcedTransportDown(); + } + +void CObexClient::SendRequestPacket() + { + //Send the request. + iTransportController->Send(); + + //Set the Timer. + iPacketTimer->SetTimer(iCmdTimeOutDuration); + } + +void CObexClient::SendRequestPacket(TObexOpcode aObexOpcode) + { + //Send the request. + iTransportController->Send(aObexOpcode); + + //Set the Timer. + iPacketTimer->SetTimer(iCmdTimeOutDuration); + } + +/** +Provides additional interfaces for CObexClient. + +@param aUid The UID of the interface that is required. +@return A pointer to an instance implementing the interface represented by aUid. +*/ +EXPORT_C TAny* CObexClient::ExtensionInterface(TUid aUid) + { + TAny* ret = NULL; + + if (aUid == KObexClientErrorResolverInterface) + { + ret = static_cast(iErrorEngine); + } + + return ret; + } + +