--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/natfw/natfwstunturnclient/src/cstunrelaybindingimplementation.cpp Tue Feb 02 01:04:58 2010 +0200
@@ -0,0 +1,1316 @@
+/*
+* Copyright (c) 2007 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 "stunassert.h"
+#include "natfwunsaflog.h"
+#include "natfwunsafmessagefactory.h"
+#include "natfwunsafbindingrequest.h"
+#include "natfwunsafallocaterequest.h"
+#include "natfwunsafconnectrequest.h"
+#include "natfwunsafsetactivedestinationrequest.h"
+#include "natfwunsafsendindication.h"
+#include "cstunrelaybindingimplementation.h"
+#include "natfwstunbinding.h"
+#include "cstunbindinginit.h"
+#include "cstunbindinggetsharedsecret.h"
+#include "cstunbindinggetaddress.h"
+#include "cstunbindingactive.h"
+#include "cstunbindingwaittoretry.h"
+#include "mstunbindingobserver.h"
+#include "cstuntransaction.h"
+#include "stunutils.h"
+#include "natfwstunclientdefs.h"
+#include "cstunindicationtransmitter.h"
+#include "natfwunsaftcprelaypacketfactory.h"
+
+// Attributes
+#include "natfwunsafusernameattribute.h"
+#include "natfwunsafxoronlyattribute.h"
+#include "natfwunsafunknownattributesattribute.h"
+#include "natfwunsafmagiccookieattribute.h"
+#include "natfwunsafdataattribute.h"
+#include "natfwunsafremoteaddressattribute.h"
+#include "natfwunsafusecandidateattribute.h"
+#include "natfwunsafpriorityattribute.h"
+#include "natfwunsaficecontrollingattribute.h"
+#include "natfwunsaficecontrolledattribute.h"
+#include "natfwunsafalternateserverattribute.h"
+#include "natfwunsafrealmattribute.h"
+#include "natfwunsafnonceattribute.h"
+
+
+
+// ======== MEMBER FUNCTIONS ========
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::NewL
+// ---------------------------------------------------------------------------
+//
+CSTUNRelayBindingImplementation*
+ CSTUNRelayBindingImplementation::NewL( CBinding& aBinding,
+ MSTUNBindingObserver& aClient,
+ RSocket& aSocket )
+ {
+
+ CSTUNRelayBindingImplementation* self =
+ new ( ELeave ) CSTUNRelayBindingImplementation( aBinding, aClient, aSocket );
+ CleanupStack::PushL( self );
+ self->ConstructL();
+ CleanupStack::Pop( self );
+ return self;
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::NewL - overloaded
+// ---------------------------------------------------------------------------
+//
+CSTUNRelayBindingImplementation*
+ CSTUNRelayBindingImplementation::NewL( CBinding& aBinding,
+ MSTUNBindingObserver& aClient,
+ TUint aStreamId,
+ TUint aConnectionId,
+ MNcmConnectionMultiplexer* aMux )
+ {
+
+ CSTUNRelayBindingImplementation* self =
+ new ( ELeave ) CSTUNRelayBindingImplementation( aBinding, aClient, aStreamId,
+ aConnectionId, aMux );
+ CleanupStack::PushL( self );
+ self->ConstructL( );
+ CleanupStack::Pop( self );
+ return self;
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::CSTUNRelayBindingImplementation
+// ---------------------------------------------------------------------------
+//
+CSTUNRelayBindingImplementation::CSTUNRelayBindingImplementation(
+ CBinding& aBinding,
+ MSTUNBindingObserver& aClient,
+ RSocket& aSocket ) :
+ CBindingImplementation( aClient, aBinding, aSocket )
+ {
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::CSTUNRelayBindingImplementation
+// ---------------------------------------------------------------------------
+//
+CSTUNRelayBindingImplementation::CSTUNRelayBindingImplementation(
+ CBinding& aBinding,
+ MSTUNBindingObserver& aClient,
+ TUint aStreamId,
+ TUint aConnectionId,
+ MNcmConnectionMultiplexer* aMux ) :
+ CBindingImplementation( aClient, aBinding, aStreamId, aConnectionId, aMux )
+ {
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::ConstructL
+// ---------------------------------------------------------------------------
+//
+void CSTUNRelayBindingImplementation::ConstructL()
+ {
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::ConstructL" )
+ ClientL().AttachBindingL( iBinding );
+
+ iGetAddress = new ( ELeave ) CSTUNBindingGetAddress();
+ iGetSharedSecret =
+ new ( ELeave ) CSTUNBindingGetSharedSecret( *iGetAddress );
+ iInit = new ( ELeave ) CSTUNBindingInit( *iGetSharedSecret );
+ iActive = new ( ELeave ) CSTUNBindingActive( *iGetSharedSecret );
+ iWaitToRetry = new ( ELeave ) CSTUNBindingWaitToRetry( *iGetSharedSecret );
+ iGetAddress->SetNeighbourStates( *iGetSharedSecret,
+ *iWaitToRetry,
+ *iActive );
+
+ iIndicationTx = CStunIndicationTransmitter::NewL( *iMux,
+ iStreamId,
+ iConnectionId );
+ ChangeState( *iInit );
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::~CSTUNRelayBindingImplementation
+// ---------------------------------------------------------------------------
+//
+CSTUNRelayBindingImplementation::~CSTUNRelayBindingImplementation()
+ {
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::~CSTUNRelayBindingImplementation" )
+ if ( iClient )
+ {
+ iClient->DetachBinding( iBinding );
+ }
+
+ this->FreeRequestData();
+
+ delete iInit;
+ delete iGetSharedSecret;
+ delete iGetAddress;
+ delete iWaitToRetry;
+ delete iActive;
+ delete iIndicationTx;
+
+ iSocket = NULL;
+ iMux = NULL;
+
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::~CSTUNRelayBindingImplementation exit" )
+ }
+
+// -----------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::TimerExpiredL
+// -----------------------------------------------------------------------------
+//
+void CSTUNRelayBindingImplementation::TimerExpiredL()
+ {
+ iState->TimerExpiredL( *this );
+ }
+
+// -----------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::LeaveFromTimerExpired
+// -----------------------------------------------------------------------------
+//
+void CSTUNRelayBindingImplementation::LeaveFromTimerExpired( TInt aError )
+ {
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::LeaveFromTimerExpired" )
+ __STUN_ASSERT_RETURN( aError != KErrNone, KErrArgument );
+
+ Terminate( aError );
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::PublicAddressObtainedL
+// ---------------------------------------------------------------------------
+//
+void CSTUNRelayBindingImplementation::PublicAddressObtainedL(
+ const TInetAddr& aAddress )
+ {
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::PublicAddressObtainedL 1" )
+ iState->PublicAddressReceivedL( *this, aAddress );
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::PublicAddressObtainedL
+// ---------------------------------------------------------------------------
+//
+void CSTUNRelayBindingImplementation::PublicAddressObtainedL(
+ const TInetAddr& aReflexiveAddr, const TInetAddr& aRelayAddr )
+ {
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::PublicAddressObtainedL 2" )
+ iState->PublicAddressReceivedL( *this, aReflexiveAddr, aRelayAddr );
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::TransactionError
+// ---------------------------------------------------------------------------
+//
+void CSTUNRelayBindingImplementation::TransactionError( TInt aError,
+ CNATFWUNSAFUnknownAttributesAttribute* aUnknownAttr )
+ {
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::TransactionError" )
+ delete iUnknownAttr;
+ iUnknownAttr = aUnknownAttr;
+
+ __STUN_ASSERT_RETURN( aError != KErrNone, KErrArgument );
+ iState->TransactionError( *this, aError );
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::TransactionEventOccurredL
+// ---------------------------------------------------------------------------
+//
+void CSTUNRelayBindingImplementation::TransactionEventOccurredL(
+ TSTUNCallbackInfo::TFunction aEvent )
+ {
+ __STUNTURNCLIENT_INT1(
+ "CSTUNRelayBindingImplementation::TransactionEventOccurredL event:",
+ aEvent )
+ if ( ESetActiveDestinationRequest == iRequestType )
+ {
+ *iTimerValue = iTransaction->TimerValue();
+ }
+
+ FreeRequestData();
+ ChangeState( *iActive );
+ ClientL().BindingEventOccurred( iBinding, aEvent );
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::HandleTransactionError
+// ---------------------------------------------------------------------------
+//
+TBool CSTUNRelayBindingImplementation::HandleTransactionError( TInt aError )
+ {
+ __STUNTURNCLIENT_INT1( "HandleTransactionError, reason", aError )
+
+ const TInt KMaxErrorResponseCount = 3;
+
+ __STUN_ASSERT_RETURN_VALUE( iRequest && aError != KErrNone,
+ EFalse );
+
+ delete iTransaction;
+ iTransaction = NULL;
+ iTransactionError = aError;
+
+ if ( ( aError > 0 ) && ( ++iErrorResponseCount >= KMaxErrorResponseCount ) )
+ {
+ iErrorResponseCount = 0;
+ return EFalse;
+ }
+
+ CNATFWUNSAFAlternateServerAttribute* alternateServer = NULL;
+ HBufC8* realmValue = NULL;
+
+ switch ( aError )
+ {
+ case KErrTimedOut:
+ __STUNTURNCLIENT(
+ "CSTUNRelayBindingImplementation - KErrTimedOut" )
+ //Clear iAddXorOnly as another address is tried
+ iAddXorOnly = EFalse;
+ return iClient && iClient->ObtainServerAddress( iServerAddress );
+
+ case E300TryAlternate:
+ // The client SHOULD attempt a new transaction to the server
+ // indicated in the ALTERNATE-SERVER attribute.
+ alternateServer = static_cast<CNATFWUNSAFAlternateServerAttribute*>
+ ( iRequest->Attribute( CNATFWUNSAFAttribute::EAlternateServer ) );
+ if ( alternateServer )
+ {
+ iServerAddress = alternateServer->Address();
+ return ETrue;
+ }
+ return EFalse;
+
+ case E4XX:
+ return EFalse;
+
+ case E401Unauthorized:
+ if ( iSharedSecret )
+ {
+ CNATFWUNSAFRealmAttribute* realm =
+ static_cast<CNATFWUNSAFRealmAttribute*>
+ ( iRequest->Attribute( CNATFWUNSAFAttribute::ERealm ) );
+ if ( realm || !iRealmFromResponse )
+ {
+ // indicates that this was retry and it did not work.
+ // notify client -> unrecoverable error
+ return EFalse;
+ }
+ }
+ return ETrue;
+
+ case E420UnknownAttributes:
+ //Remove the unknown attributes and retry
+ return iUnknownAttr != NULL;
+
+ case E430StaleCredentials:
+ // Client used a short term credential that has expired so
+ // generate a new Shared Secret request
+ return iSharedSecret != NULL;
+
+ case E431IntegrityCheckFailure:
+ {
+ TBool retry = EFalse;
+ TRAPD( err, retry =
+ ClientL().SharedSecretRejectedL( iBinding,
+ Username(),
+ *iSharedSecret ) );
+ return err == KErrNone && retry;
+ }
+
+ case E432MissingUsername:
+ //If missing USERNAME or MESSAGE-INTEGRITY, add them and retry
+ return !iRequest->Attribute( CNATFWUNSAFAttribute::EUsername ) ||
+ !iSharedSecret;
+
+ case E433UseTLS:
+ // If request was a Shared Secret request and wasn't sent over
+ // TLS, the client SHOULD retry the request with TLS.
+ return iSharedSecret != NULL;
+
+ case E434MissingRealm:
+ if ( !iRequest->Attribute( CNATFWUNSAFAttribute::ERealm ) &&
+ iSharedSecret )
+ {
+ // use a long term credential and retry the request using the username
+ // and password associated with the REALM
+ return ETrue;
+ }
+
+ TRAPD( err, realmValue = ( static_cast<CNATFWUNSAFRealmAttribute*>
+ ( iRequest->Attribute( CNATFWUNSAFAttribute::ERealm ) ) )->
+ Value().AllocL() );
+ if ( err || !realmValue )
+ {
+ delete realmValue;
+ return EFalse;
+ }
+
+ if ( KErrNone != realmValue->Compare( *iRealmFromResponse ) )
+ {
+ // the client SHOULD retry using the username and
+ // password associated with the REALM in the response
+ iUseRealmFromResponse = ETrue;
+ delete realmValue;
+ return ETrue;
+ }
+ delete realmValue;
+ return EFalse;
+
+
+ case E435MissingNonce:
+ if ( iRequest->Attribute( CNATFWUNSAFAttribute::ENonce ) )
+ {
+ return EFalse;
+ }
+ // retry using a nonce from error response
+ return ETrue;
+
+ case E436UnknownUsername:
+ if ( iSharedSecret )
+ {
+ return ETrue;
+ }
+ // If the username was collected from the user, alert the user.
+ return EFalse;
+
+ case E437NoBindind:
+ // There is none yet in place
+ return EFalse;
+
+ case E438StaleNonce:
+ if ( iRequest->Attribute( CNATFWUNSAFAttribute::ENonce ) )
+ {
+ // retry using a nonce from error response
+ return ETrue;
+ }
+ return EFalse;
+
+ case E439Transitioning:
+ // The client should reset the active destination, wait for
+ // 5 seconds and set the active destination to the new value.
+ return EFalse;
+
+ case E442UnsupportedTransportProtocol:
+
+ return EFalse;
+
+ case E443InvalidIPAddress:
+
+ return EFalse;
+
+ case E444InvalidPort:
+
+ return EFalse;
+
+ case E445OperationForTCPOnly:
+
+ return EFalse;
+
+ case E446ConnectionAlreadyExists:
+
+ return EFalse;
+
+ case E486AllocationQuotaReached:
+ // The user or client is not authorized to request additional
+ // allocations.
+ return EFalse;
+
+ case E500ServerError:
+ return ETrue;
+
+ case E507InsufficientCapacity:
+ // The server cannot allocate a new port for this client as it has
+ // exhausted its relay capacity.
+ return EFalse;
+
+ case E600GlobalFailure:
+ return EFalse;
+
+ case ERetryAfterAddingXorOnly:
+ iAddXorOnly = ETrue;
+ return ETrue;
+
+ default:
+ return STUNUtils::Is5xxResponse( aError );
+ }
+
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::IcmpError
+// ---------------------------------------------------------------------------
+//
+void CSTUNRelayBindingImplementation::IcmpError( const TInetAddr& aAddress )
+ {
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::IcmpError" )
+ if ( iTransaction && iServerAddress.CmpAddr( aAddress ) )
+ {
+ //Try next address, or terminate with KErrTimedOut.
+ iTransaction->Terminate( KErrTimedOut );
+ }
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::AllocateRequestL
+// ---------------------------------------------------------------------------
+//
+void CSTUNRelayBindingImplementation::AllocateRequestL( TUint /*aRtoValue*/ )
+ {
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::AllocateRequestL" )
+ iRequestType = EAllocateRequest;
+ iState->SendRequestL( *this );
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::SetActiveDestinationRequestL
+// ---------------------------------------------------------------------------
+//
+void CSTUNRelayBindingImplementation::SetActiveDestinationRequestL(
+ const TInetAddr& aRemoteAddr, TUint32& aTimerValue )
+ {
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::SetActiveDestinationRequestL" )
+
+ iRequestType = ESetActiveDestinationRequest;
+ iRemoteAddr = aRemoteAddr;
+ iTimerValue = NULL;
+ iTimerValue = &aTimerValue;
+ iState->SendRequestL( *this );
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::SendIndicationL
+// ---------------------------------------------------------------------------
+//
+void CSTUNRelayBindingImplementation::SendIndicationL(
+ const TInetAddr& aRemoteAddr, const TDesC8& aData,
+ TBool /*aAddFingerprint*/ )
+ {
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::SendIndicationL" )
+
+ TNATFWUNSAFTransactionID transactionID;
+ ClientL().ObtainTransactionIDL( transactionID );
+
+ CNATFWUNSAFMessage*
+ indication = CNATFWUNSAFSendIndication::NewL( transactionID );
+ CleanupStack::PushL( indication );
+
+ indication->AddAttributeL( CNATFWUNSAFDataAttribute::NewLC( aData ) );
+ CleanupStack::Pop(); // CNATFWUNSAFDataAttribute
+
+ indication->AddAttributeL( CNATFWUNSAFRemoteAddressAttribute::NewLC(
+ aRemoteAddr ) );
+ CleanupStack::Pop(); // CNATFWUNSAFRemoteAddressAttribute
+
+
+ if ( iUnknownAttr )
+ {
+ STUNUtils::RemoveUnknownAttributes( *indication, *iUnknownAttr );
+ delete iUnknownAttr;
+ iUnknownAttr = NULL;
+ }
+
+ if ( ETcpProtocol == ClientL().TransportProtocol() )
+ {
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::SendIndicationL TCP" )
+
+ CBufBase* message = indication->EncodeL();
+ CleanupStack::PushL( message );
+ CNATFWUNSAFTcpRelayPacket* packet =
+ CNATFWUNSAFTcpRelayPacket::NewLC( message->Ptr( 0 ),
+ CNATFWUNSAFTcpRelayPacket::EFrameTypeStun );
+
+ iIndicationTx->TransmitL( *packet );
+ CleanupStack::PopAndDestroy( packet );
+ CleanupStack::PopAndDestroy( message );
+ }
+ else
+ {
+ iIndicationTx->TransmitL( *indication );
+ }
+
+ CleanupStack::PopAndDestroy( indication );
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::SendIndicationL exit" )
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::ConnectRequestL
+// ---------------------------------------------------------------------------
+//
+void CSTUNRelayBindingImplementation::ConnectRequestL(
+ const TInetAddr& aRemoteAddr )
+ {
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::ConnectRequestL" )
+ iRemoteAddr = aRemoteAddr;
+ iRequestType = ETCPConnectRequest;
+ iState->SendRequestL( *this );
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::ConnectRequestL end" )
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::StreamId
+// ---------------------------------------------------------------------------
+//
+TUint CSTUNRelayBindingImplementation::StreamId() const
+ {
+ return iStreamId;
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::ConnectionId
+// ---------------------------------------------------------------------------
+//
+TUint CSTUNRelayBindingImplementation::ConnectionId() const
+ {
+ return iConnectionId;
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::CancelRequest
+// If transaction exists, delete it to stop using the socket.
+// ---------------------------------------------------------------------------
+//
+void CSTUNRelayBindingImplementation::CancelRequest()
+ {
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::CancelRequest" )
+ if ( AddressResolved() )
+ {
+ ChangeState( *iActive );
+ }
+ else
+ {
+ //The initial request is canceled
+ ChangeState( *iInit );
+ }
+
+ delete iTransaction;
+ iTransaction = NULL;
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::CancelRequest end" )
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::Socket
+// ---------------------------------------------------------------------------
+//
+const RSocket& CSTUNRelayBindingImplementation::Socket() const
+ {
+ return *iSocket;
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::AddressResolved
+// The most recent public address is kept also during a refresh.
+// ---------------------------------------------------------------------------
+//
+TBool CSTUNRelayBindingImplementation::AddressResolved() const
+ {
+ return !iPublicAddr.IsUnspecified();
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::PublicAddr
+// ---------------------------------------------------------------------------
+//
+const TInetAddr& CSTUNRelayBindingImplementation::PublicAddr() const
+ {
+ return iPublicAddr;
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::AlternateServerAddr
+// ---------------------------------------------------------------------------
+//
+const TInetAddr& CSTUNRelayBindingImplementation::AlternateServerAddr() const
+ {
+ return iServerAddress;
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::RealmFromResponse
+// ---------------------------------------------------------------------------
+//
+const HBufC8* CSTUNRelayBindingImplementation::RealmFromResponse() const
+ {
+ return iRealmFromResponse;
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::HandleDataL
+// Must NOT leave, if aData is not a STUN message!
+// ---------------------------------------------------------------------------
+//
+HBufC8* CSTUNRelayBindingImplementation::HandleDataL(
+ const TDesC8& aData, TBool& aConsumed, TInetAddr& aRemoteAddr )
+ {
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::HandleDataL" )
+ aConsumed = EFalse;
+ TInt err( KErrNone );
+ HBufC8* dataPointer = NULL;
+ CNATFWUNSAFMessage* msg = NULL;
+ CNATFWUNSAFTcpRelayPacket* relayPacket = NULL;
+
+ if ( ETcpProtocol == ClientL().TransportProtocol() )
+ {
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::HandleDataL - TCP" )
+
+ TRAP( err, relayPacket = DecodeTcpRelayMessageL( aData ) );
+ CleanupStack::PushL( relayPacket );
+
+ if ( KErrNone == err && relayPacket &&
+ CNATFWUNSAFTcpRelayPacket::EFrameTypeStun == relayPacket->Type() )
+ {
+ TRAP( err, msg = DecodeMessageL( relayPacket->Data() ) );
+ if ( KErrNoMemory == err )
+ {
+ User::Leave( err );
+ }
+ CleanupStack::PopAndDestroy( relayPacket );
+ }
+ else if ( relayPacket &&
+ CNATFWUNSAFTcpRelayPacket::EFrameTypeData == relayPacket->Type() )
+ {
+ HBufC8* ptr = relayPacket->Data().AllocL();
+ CleanupStack::PopAndDestroy( relayPacket );
+ return ptr;
+ }
+ else if ( KErrNoMemory == err )
+ {
+ User::Leave( err );
+ }
+ else
+ {
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::HandleDataL -\
+ Tcp framing invalid!" )
+
+ CleanupStack::PopAndDestroy( relayPacket );
+ return NULL;
+ }
+ }
+ else
+ {
+ TRAP( err, msg = DecodeMessageL( aData ) );
+ if ( KErrNoMemory == err )
+ {
+ User::Leave( err );
+ }
+ }
+ if ( msg )
+ {
+ CleanupStack::PushL( msg );
+ CNATFWUNSAFRealmAttribute* realm = static_cast<CNATFWUNSAFRealmAttribute*>(
+ msg->Attribute( CNATFWUNSAFAttribute::ERealm ) );
+ if ( realm )
+ {
+ delete iRealmFromResponse;
+ iRealmFromResponse = NULL;
+ const TDesC8& realmValue = ( static_cast<CNATFWUNSAFRealmAttribute*>(
+ msg->Attribute( CNATFWUNSAFAttribute::ERealm ) )->Value() );
+ iRealmFromResponse = realmValue.AllocL();
+ __STUNTURNCLIENT_STR8( "realm: ", *iRealmFromResponse )
+ }
+
+ CNATFWUNSAFNonceAttribute* nonce = static_cast<CNATFWUNSAFNonceAttribute*>(
+ msg->Attribute( CNATFWUNSAFAttribute::ENonce ) );
+ if ( nonce )
+ {
+ delete iNonce;
+ iNonce = NULL;
+ const TDesC8& nonceValue = ( static_cast<CNATFWUNSAFNonceAttribute*>(
+ msg->Attribute( CNATFWUNSAFAttribute::ENonce ) )->Value() );
+ iNonce = nonceValue.AllocL();
+ }
+
+ // if aData contains data indication, decode it
+ if ( msg->Type() == CNATFWUNSAFMessage::EDataIndication &&
+ KErrNone == err )
+ {
+ const TDesC8& data = ( static_cast<CNATFWUNSAFDataAttribute*>(
+ msg->Attribute( CNATFWUNSAFAttribute::EData ) )->Value() );
+
+ // Remote address is returned through reference
+ CNATFWUNSAFRemoteAddressAttribute* remoteAddress =
+ static_cast<CNATFWUNSAFRemoteAddressAttribute*>
+ ( msg->Attribute( CNATFWUNSAFAttribute::ERemoteAddress ) );
+
+ if( remoteAddress )
+ {
+ aRemoteAddr = remoteAddress->Address();
+ }
+ else
+ {
+ remoteAddress = NULL;
+ }
+
+ CNATFWUNSAFMessage* msg_unsaf = NULL;
+
+ dataPointer = data.AllocLC(); // to cleanupstack
+
+ TRAP( err, msg_unsaf = DecodeMessageL( *dataPointer ) );
+ CleanupStack::PushL( msg_unsaf );
+ if ( KErrNoMemory == err )
+ {
+ User::Leave( err );
+ }
+ // if we have outstanding transaction, offer data to it
+ if ( iTransaction && iRequest && !err && msg_unsaf )
+ {
+ if ( ValidateMsgType( msg_unsaf ) &&
+ iRequest->TransactionID() == msg_unsaf->TransactionID() )
+ {
+ // Data was encapsulated in data indication.
+ // Data is consumed. dataPointer is not needed.
+ aConsumed = ETrue;
+ iTransaction->ReceiveL( *msg_unsaf, *dataPointer );
+ CleanupStack::PopAndDestroy( msg_unsaf );
+ CleanupStack::PopAndDestroy( dataPointer );
+ dataPointer = NULL;
+ }
+ }
+
+ if ( dataPointer )
+ {
+ // dataPointer is still valid. This means that message was not
+ // consumed, and data indication is ripped off from it.
+ // Message can also contain media data.
+ // Data indication is decapsulated out of that message.
+ CleanupStack::PopAndDestroy( msg_unsaf );
+ CleanupStack::Pop( dataPointer );
+ }
+ }
+
+ // Data was NOT in encapsulated in data indication, so offer
+ // message to this client.
+ else if ( KErrNone == err && iTransaction && iRequest )
+ {
+ if ( ValidateMsgType( msg ) &&
+ iRequest->TransactionID() == msg->TransactionID() )
+ {
+ aConsumed = ETrue;
+ iTransaction->ReceiveL( *msg, aData );
+ }
+ }
+ else
+ {
+ // for PCLint approval
+ }
+ CleanupStack::PopAndDestroy( msg );
+ }
+
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::HandleDataL end" )
+
+ // if there is not any outstanding transaction, data does not belong
+ // to this client. Pointer to decoded data IS NOT returned.
+ return dataPointer;
+ }
+
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::STUNClient
+// ---------------------------------------------------------------------------
+//
+const CSTUNClient* CSTUNRelayBindingImplementation::STUNClient() const
+ {
+ return iClient ? &iClient->STUNClient() : NULL;
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::GetServerAddressL
+// ---------------------------------------------------------------------------
+//
+void CSTUNRelayBindingImplementation::GetServerAddressL()
+ {
+ __ASSERT_ALWAYS( iServerAddress.IsUnspecified(),
+ User::Leave( KErrAlreadyExists ) );
+ __ASSERT_ALWAYS( ClientL().ObtainServerAddress( iServerAddress ),
+ User::Leave( KErrNotFound ) );
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::GetSharedSecretL
+// ---------------------------------------------------------------------------
+//
+void CSTUNRelayBindingImplementation::GetSharedSecretL()
+ {
+ ClientL().ObtainSharedSecretL( iBinding );
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::Username
+// ---------------------------------------------------------------------------
+//
+const TDesC8& CSTUNRelayBindingImplementation::Username() const
+ {
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::Username" )
+ if ( iRequest && iRequest->HasAttribute( CNATFWUNSAFAttribute::EUsername ) )
+ {
+ return static_cast<CNATFWUNSAFUsernameAttribute*>(
+ iRequest->Attribute( CNATFWUNSAFAttribute::EUsername ) )->Value();
+ }
+
+ return KNullDesC8;
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::DetachClient
+// ---------------------------------------------------------------------------
+//
+void CSTUNRelayBindingImplementation::DetachClient()
+ {
+ iClient = NULL;
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::StoreAddressL
+// Transaction no longer needed.
+// ---------------------------------------------------------------------------
+//
+void CSTUNRelayBindingImplementation::StoreAddressL(
+ const TInetAddr& aReflexiveAddr,
+ const TInetAddr& aRelayAddr )
+ {
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::StoreAddressL" )
+
+ iErrorResponseCount = 0;
+ iPublicAddr = aReflexiveAddr;
+ iRelayAddr = aRelayAddr;
+ ClientL().AddressResolvedL( iBinding );
+
+ FreeRequestData();
+
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::StoreAddressL end" )
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::StoreAddressL
+// Transaction no longer needed.
+// This is kind of wrong functionality if this method get called.
+// ---------------------------------------------------------------------------
+//
+void CSTUNRelayBindingImplementation::StoreAddressL(
+ const TInetAddr& aPublicAddress )
+ {
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::StoreAddressL" )
+
+ iErrorResponseCount = 0;
+ iPublicAddr = aPublicAddress;
+ ClientL().AddressResolvedL( iBinding );
+
+ FreeRequestData();
+
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::StoreAddressL end" )
+ }
+
+// -----------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::ChangeState
+// -----------------------------------------------------------------------------
+//
+void CSTUNRelayBindingImplementation::ChangeState(
+ CSTUNBindingState& aNewState )
+ {
+ iState = &aNewState;
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::Terminate
+// Binding can be re-started with SendRequestL.
+// ---------------------------------------------------------------------------
+//
+void CSTUNRelayBindingImplementation::Terminate( TInt aError )
+ {
+ __STUNTURNCLIENT_INT1( "CSTUNRelayBindingImplementation::Terminate, error:", aError )
+ __STUN_ASSERT_RETURN( aError != KErrNone, KErrArgument );
+
+ NATFWUNSAF_INTLOG( "STUNRelayBinding terminated, reason", aError )
+
+ TInetAddr emptyAddr;
+ iPublicAddr = emptyAddr;
+ iServerAddress = emptyAddr;
+ iAddXorOnly = EFalse;
+ iErrorResponseCount = 0;
+ FreeRequestData();
+
+ if ( iState != iInit )
+ {
+ __STUNTURNCLIENT( "Binding terminated" )
+
+ ChangeState( *iInit );
+ TRAP_IGNORE( ClientL().BindingErrorL( Binding(), aError, ETrue ) )
+ }
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::FreeRequestData
+// ---------------------------------------------------------------------------
+//
+void CSTUNRelayBindingImplementation::FreeRequestData()
+ {
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::FreeRequestData" )
+ delete iTransaction;
+ iTransaction = NULL;
+ delete iUnknownAttr;
+ iUnknownAttr = NULL;
+ delete iRequest;
+ iRequest = NULL;
+ delete iSharedSecret;
+ iSharedSecret = NULL;
+ delete iRealmFromResponse;
+ iRealmFromResponse = NULL;
+ delete iNonce;
+ iNonce = NULL;
+ iRequestType = EUnknown;
+ iRemoteAddr.SetAddress( KAFUnspec );
+ iICEAttributes = TICEAttributes();
+
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::FreeRequestData end" )
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::IsWaitingSharedSecret
+// ---------------------------------------------------------------------------
+//
+TBool CSTUNRelayBindingImplementation::IsWaitingSharedSecret() const
+ {
+ return iState == iGetSharedSecret;
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::SharedSecretObtainedL
+// ---------------------------------------------------------------------------
+//
+void CSTUNRelayBindingImplementation::SharedSecretObtainedL(
+ const TDesC8& aUsername, const TDesC8& aPassword )
+ {
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::SharedSecretObtainedL" )
+ __STUN_ASSERT_L( ( aUsername.Length() > 0 && aPassword.Length() > 0 ) ||
+ ( aUsername.Length() == 0 && aPassword.Length() == 0 ), KErrArgument );
+
+
+ iState->SharedSecretObtainedL( *this, aUsername, aPassword );
+
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::SharedSecretObtainedL end" )
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::CreateBindingRequestL
+// ---------------------------------------------------------------------------
+//
+void CSTUNRelayBindingImplementation::CreateBindingRequestL(
+ const TDesC8& aUsername, const TDesC8& aPassword )
+ {
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::CreateBindingRequestL" )
+
+ delete iRequest;
+ iRequest = NULL;
+
+ if ( IsSharedSecretRequired() &&
+ ( aUsername.Length() == 0 || aPassword.Length() == 0 ) )
+ {
+ Terminate( KErrAccessDenied );
+ User::Leave( KErrAccessDenied );
+ }
+ iTransactionError = KErrNone;
+
+ HBufC8* newSharedSecret = aPassword.AllocL();
+ delete iSharedSecret;
+ iSharedSecret = newSharedSecret;
+
+ TNATFWUNSAFTransactionID transactionID;
+ ClientL().ObtainTransactionIDL( transactionID );
+
+ // Check the request type
+ switch( iRequestType )
+ {
+ case ESendRequest:
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::\
+ CreateBindingRequestL - BindingRequest" )
+ iRequest = CNATFWUNSAFBindingRequest::NewL( transactionID );
+ break;
+
+ case EAllocateRequest:
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::\
+ CreateBindingRequestL - AllocateRequest" )
+ iRequest = CNATFWUNSAFAllocateRequest::NewL( transactionID );
+ break;
+
+ case ETCPConnectRequest:
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::\
+ CreateBindingRequestL - ConnectRequest" )
+ iRequest = CNATFWUNSAFConnectRequest::NewL( transactionID );
+ break;
+
+ case ESetActiveDestinationRequest:
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::\
+ CreateBindingRequestL - SetActiveDestinationRequest" )
+ iRequest = CNATFWUNSAFSetActiveDestinationRequest::NewL(
+ transactionID );
+ break;
+
+ // request not recognised
+ default:
+ User::Leave( KErrArgument );
+ }
+
+
+ if ( aUsername.Length() > 0 )
+ {
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::\
+ CreateBindingRequestL - Add USERNAME attribute" )
+ iRequest->AddAttributeL( CNATFWUNSAFUsernameAttribute::NewLC(
+ aUsername ) );
+ CleanupStack::Pop(); // CNATFWUNSAFUsernameAttribute
+ }
+
+ if ( iICEAttributes.iPriority > 0 )
+ {
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::\
+ CreateBindingRequestL - Add PRIORITY attribute" )
+ iRequest->AddAttributeL( CNATFWUNSAFPriorityAttribute::NewLC(
+ iICEAttributes.iPriority ) );
+ CleanupStack::Pop(); // CNATFWUNSAFPriorityAttribute
+ }
+
+ if ( iICEAttributes.iUseCandidate )
+ {
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::\
+ CreateBindingRequestL - Add USE_CANDIDATE attribute" )
+ iRequest->AddAttributeL( CNATFWUNSAFUseCandidateAttribute::NewLC() );
+ CleanupStack::Pop(); // CNATFWUNSAFUseCandidateAttribute
+ }
+
+ if ( iICEAttributes.iControlled > 0 )
+ {
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::\
+ CreateBindingRequestL - Add CONTROLLED attribute" )
+ iRequest->AddAttributeL( CNATFWUNSAFIceControlledAttribute::NewLC(
+ iICEAttributes.iControlled ) );
+ CleanupStack::Pop(); // CNATFWUNSAFIceControlledAttribute
+ }
+
+ if ( iICEAttributes.iControlling > 0 )
+ {
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::\
+ CreateBindingRequestL - Add CONTROLLING attribute" )
+ iRequest->AddAttributeL( CNATFWUNSAFIceControllingAttribute::NewLC(
+ iICEAttributes.iControlling ) );
+ CleanupStack::Pop(); // CNATFWUNSAFIceControllingAttribute
+ }
+
+ if ( !iRemoteAddr.IsUnspecified() )
+ {
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::\
+ CreateBindingRequestL - Add REMOTEADDRESS attribute" )
+ iRequest->AddAttributeL( CNATFWUNSAFRemoteAddressAttribute::NewLC(
+ iRemoteAddr ) );
+ CleanupStack::Pop(); // CNATFWUNSAFRemoteAddressAttribute
+ }
+
+ if ( iUseRealmFromResponse )
+ {
+ iRequest->AddAttributeL( CNATFWUNSAFRealmAttribute::NewLC(
+ *iRealmFromResponse ) );
+
+ CleanupStack::Pop(); // CNATFWUNSAFRealmAttribute
+ }
+
+ if ( iUnknownAttr )
+ {
+ STUNUtils::RemoveUnknownAttributes( *iRequest, *iUnknownAttr );
+ delete iUnknownAttr;
+ iUnknownAttr = NULL;
+ }
+
+ if ( iNonce )
+ {
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::\
+ CreateBindingRequestL - Add NONCE attribute" )
+
+ iRequest->AddAttributeL( CNATFWUNSAFNonceAttribute::NewLC(
+ *iNonce ) );
+ CleanupStack::Pop(); // CNATFWUNSAFNonceAttribute
+ }
+
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::\
+ CreateBindingRequestL, exit" )
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::SendBindingRequestL
+// ---------------------------------------------------------------------------
+//
+void CSTUNRelayBindingImplementation::SendBindingRequestL()
+ {
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::SendBindingRequestL" )
+
+ __STUN_ASSERT_L( !iTransaction, KErrAlreadyExists );
+ __STUN_ASSERT_L( iRequest && iSharedSecret, KErrNotFound );
+ // Happens if resolving returned unspecified address
+
+ if ( iSocket )
+ {
+ // old way to do it
+ iTransaction = CSTUNTransaction::NewL( *iRequest,
+ iServerAddress,
+ *iSharedSecret,
+ *iSocket,
+ ClientL().TimerProvider(),
+ ClientL().RetransmitInterval(),
+ *this,
+ KStunRelay,
+ ClientL().TransportProtocol() );
+ }
+
+ else
+ {
+ // New way, to be used
+ iTransaction = CSTUNTransaction::NewL( *iRequest,
+ iServerAddress,
+ *iSharedSecret,
+ iStreamId,
+ iConnectionId,
+ ClientL().TimerProvider(),
+ ClientL().RetransmitInterval(),
+ *this,
+ KStunRelay,
+ *iMux,
+ ClientL().TransportProtocol() );
+ }
+
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::SendBindingRequestL end" )
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::WaitBeforeRetrying
+// ---------------------------------------------------------------------------
+//
+void CSTUNRelayBindingImplementation::WaitBeforeRetrying()
+ {
+ StartTimer( STUNUtils::EWaitBeforeRetryDuration );
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::SetICESpecificAttributes
+// ---------------------------------------------------------------------------
+//
+void CSTUNRelayBindingImplementation::SetICESpecificAttributes(
+ const TICEAttributes& aAttributes )
+ {
+ iICEAttributes = aAttributes;
+ }
+
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::Binding
+// ---------------------------------------------------------------------------
+//
+const CBinding& CSTUNRelayBindingImplementation::Binding()
+ {
+ return iBinding;
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::RelayAddr
+// ---------------------------------------------------------------------------
+//
+const TInetAddr& CSTUNRelayBindingImplementation::RelayAddr() const
+ {
+ __STUNTURNCLIENT( "CSTUNRelayBindingImplementation::RelayAddr" )
+
+ return iRelayAddr;
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::IsSharedSecretRequired
+// ---------------------------------------------------------------------------
+//
+TBool CSTUNRelayBindingImplementation::IsSharedSecretRequired() const
+ {
+ return iTransactionError == E401Unauthorized ||
+ iTransactionError == E430StaleCredentials ||
+ iTransactionError == E431IntegrityCheckFailure ||
+ iTransactionError == E432MissingUsername ||
+ iTransactionError == E433UseTLS ||
+ iTransactionError == E434MissingRealm;
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::ClientL
+// ---------------------------------------------------------------------------
+//
+MSTUNBindingObserver& CSTUNRelayBindingImplementation::ClientL() const
+ {
+ __ASSERT_ALWAYS( iClient, User::Leave( KErrNotFound ) );
+
+ return *iClient;
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::DecodeMessageL
+// ---------------------------------------------------------------------------
+//
+CNATFWUNSAFMessage*
+ CSTUNRelayBindingImplementation::DecodeMessageL( const TDesC8& aData ) const
+ {
+ CNATFWUNSAFMessageFactory* decoder = CNATFWUNSAFMessageFactory::NewLC();
+ CNATFWUNSAFMessage* msg = decoder->DecodeL( aData );
+ CleanupStack::PopAndDestroy( decoder );
+ return msg;
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::DecodeTcpRelayMessageL
+// ---------------------------------------------------------------------------
+//
+CNATFWUNSAFTcpRelayPacket*
+ CSTUNRelayBindingImplementation::DecodeTcpRelayMessageL( const TDesC8& aData ) const
+ {
+ CNATFWUNSAFTcpRelayPacketFactory* decoder =
+ CNATFWUNSAFTcpRelayPacketFactory::NewLC();
+ CNATFWUNSAFTcpRelayPacket* msg = decoder->DecodeL( aData );
+ CleanupStack::PopAndDestroy( decoder );
+ return msg;
+ }
+
+// ---------------------------------------------------------------------------
+// CSTUNRelayBindingImplementation::ValidateMsgType
+// ---------------------------------------------------------------------------
+//
+TBool CSTUNRelayBindingImplementation::ValidateMsgType(
+ CNATFWUNSAFMessage* aMsg ) const
+ {
+ if ( aMsg )
+ {
+ return ( aMsg->Type() == CNATFWUNSAFMessage::EBindingResponse ||
+ aMsg->Type() == CNATFWUNSAFMessage::EAllocateResponse ||
+ aMsg->Type() == CNATFWUNSAFMessage::EBindingErrorResponse ||
+ aMsg->Type() == CNATFWUNSAFMessage::EAllocateErrorResponse ||
+ aMsg->Type() == CNATFWUNSAFMessage::EConnectResponse ||
+ aMsg->Type() == CNATFWUNSAFMessage::EConnectErrorResponse ||
+ aMsg->Type() ==
+ CNATFWUNSAFMessage::ESetActiveDestinationResponse ||
+ aMsg->Type() ==
+ CNATFWUNSAFMessage::ESetActiveDestinationErrorResponse );
+ }
+ return EFalse;
+ }
+
+