diff -r 000000000000 -r af10295192d8 networkprotocols/tcpipv4v6prt/src/tcp_sap.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/networkprotocols/tcpipv4v6prt/src/tcp_sap.cpp Tue Jan 26 15:23:49 2010 +0200 @@ -0,0 +1,4585 @@ +// Copyright (c) 2006-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: +// tcp_sap.cpp - TCP service access point +// TCP Service Access Point and most of the protocol logic. +// + + + +/** + @file tcp_sap.cpp +*/ + +#include "tcp.h" +#include +#include +#include +#include +#include + +#ifdef SYMBIAN_ADAPTIVE_TCP_RECEIVE_WINDOW +#include +#endif //SYMBIAN_ADAPTIVE_TCP_RECEIVE_WINDOW + +// speed optimisations +#ifdef __ARMCC__ +#pragma push +#pragma arm +#endif + +#define SIGNED_UNSIGNED_FIX + +// Copied from ip6.cpp. Should move to some common definition file? +static const TLitC8 KInetOptionDisable = {sizeof(TInt), {0}}; + +#define SYMBIAN_NETWORKING_UPS + +// +// +// TCP state diagram from RFC793 +// +// +// +---------+ ---------\ active OPEN +// | CLOSED | \ ----------- +// +---------+<---------\ \ create TCB +// | ^ \ \ snd SYN +// passive OPEN | | CLOSE \ \. +// ------------ | | ---------- \ \. +// create TCB | | delete TCB \ \. +// V | \ \. +// +---------+ CLOSE | \. +// | LISTEN | ---------- | | +// +---------+ delete TCB | | +// rcv SYN | | SEND | | +// ----------- | | ------- | V +// +---------+ snd SYN,ACK / \ snd SYN +---------+ +// | |<----------------- ------------------>| | +// | SYN | rcv SYN | SYN | +// | RCVD |<-----------------------------------------------| SENT | +// | | snd ACK | | +// | |------------------ -------------------| | +// +---------+ rcv ACK of SYN \ / rcv SYN,ACK +---------+ +// | -------------- | | ----------- +// | x | | snd ACK +// | V V +// | CLOSE +---------+ +// | ------- | ESTAB | +// | snd FIN +---------+ +// | CLOSE | | rcv FIN +// V ------- | | ------- +// +---------+ snd FIN / \ snd ACK +---------+ +// | FIN |<----------------- ------------------>| CLOSE | +// | WAIT-1 |------------------ | WAIT | +// +---------+ rcv FIN \ +---------+ +// | rcv ACK of FIN ------- | CLOSE | +// | -------------- snd ACK | ------- | +// V x V snd FIN V +// +---------+ +---------+ +---------+ +// |FINWAIT-2| | CLOSING | | LAST-ACK| +// +---------+ +---------+ +---------+ +// | rcv ACK of FIN | rcv ACK of FIN | +// | rcv FIN -------------- | Timeout=2MSL -------------- | +// | ------- x V ------------ x V +// \ snd ACK +---------+delete TCB +---------+ +// ------------------------>|TIME WAIT|------------------>| CLOSED | +// +---------+ +---------+ +// +// LISTEN - represents waiting for a connection request from any remote +// TCP and port. +// +// SYN-SENT - represents waiting for a matching connection request +// after having sent a connection request. +// +// SYN-RECEIVED - represents waiting for a confirming connection +// request acknowledgment after having both received and sent a +// connection request. +// +// ESTABLISHED - represents an open connection, data received can be +// delivered to the user. The normal state for the data transfer phase +// of the connection. +// +// FIN-WAIT-1 - represents waiting for a connection termination request +// from the remote TCP, or an acknowledgment of the connection +// termination request previously sent. +// +// FIN-WAIT-2 - represents waiting for a connection termination request +// from the remote TCP. +// +// CLOSE-WAIT - represents waiting for a connection termination request +// from the local user. +// +// CLOSING - represents waiting for a connection termination request +// acknowledgment from the remote TCP. +// +// LAST-ACK - represents waiting for an acknowledgment of the +// connection termination request previously sent to the remote TCP +// (which includes an acknowledgment of its connection termination request). +// +// TIME-WAIT - represents waiting for enough time to pass to be sure +// the remote TCP received the acknowledgment of its connection +// termination request. +// +// CLOSED - represents no connection state at all. +// +// CONNECT - represents waiting for a network path to become ready after +// the user has issued an active OPEN request. The request waits in this +// state until all the necessary negotiations have been accomplished, +// including the establishment of security and mobility bindings. CONNECT is +// specific to this TCP implementation and is therefore not visible in the +// state chart above. It would be located between CLOSED and SYN-SENT. +// + + +#ifdef _LOG +const TText *CProviderTCP6::TcpState(TUint aState) + { + TInt i; + static const TText* const tcpStates[] = { + _S("CONSTRUCTING"), + _S("INITIAL"), + _S("LISTEN"), + _S("SYN-SENT"), + _S("SYN-RECEIVED"), + _S("ESTABLISHED"), + _S("FIN-WAIT-1"), + _S("FIN-WAIT-2"), + _S("CLOSE-WAIT"), + _S("CLOSING"), + _S("LAST-ACK"), + _S("TIME-WAIT"), + _S("CLOSED"), + _S("CONNECT"), + _S("INVALID"), + NULL + }; + + if (aState == ~0UL) + aState = iState; + for (i = 0; aState && tcpStates[i+1]; aState >>= 1) + i++; + return tcpStates[i]; + } +#endif + +CProviderTCP6::CProviderTCP6(CProtocolInet6Base* aProtocol) + : CProviderInet6Transport(aProtocol) + { + __DECLARE_NAME(_S("CProviderTCP6")); +#ifdef SYMBIAN_ADAPTIVE_TCP_RECEIVE_WINDOW + iWindowSetByUser = EFalse; +#endif + } + +CProviderTCP6::~CProviderTCP6() + { + LOG(Log::Printf(_L("\ttcp SAP[%u] being deleted"), (TInt)this)); + Stop(); + FreeQueues(); + + delete iTransmitter; + delete iDelayAckTimer; + delete iRetransTimer; + delete iLingerTimer; + + // Delete server socket state + if (iParent) + { + // Notify parent. + iParent->SetChildDeleted(ETrue); + iParent->DetachChild(this); + } + else if (iListenQueue) + { + // + // Delete all pending child sockets. The child socket destructor will + // call DetachChild(), removing itself from the socket queue. If the + // socket has already been registered with the socket server, we will + // simply detach it here. + // + while (iConnectCount) + { + ASSERT(iListenQueue[0]->iParent == this); + if (iListenQueue[0]->iSockFlags.iAttached) + DetachChild(iListenQueue[0]); + else + { + if (iListenQueue[0]->InState(ETcpSynReceived)) + { + iListenQueue[0]->ClearSYNSettings(); + iListenQueue[0]->iSockFlags.iSendClose = ETrue; + iListenQueue[0]->iSockFlags.iRecvClose = ETrue; + iListenQueue[0]->SendSegments(); + DetachChild(iListenQueue[0]); + } + else + delete iListenQueue[0]; + } + } + delete[] iListenQueue; + } + } + +// +// Initialize a SAP with default values +// +void CProviderTCP6::InitL() + { + TCallBack sender(SenderCallBack, this); + TCallBack receiver(ReceiverCallBack, this); + TCallBack delack(DelayAckCallBack, this); + TCallBack transmitter(TransmitterCallBack, this); + TCallBack retransmitter(RetransmitterCallBack, this); + TCallBack linger(LingerTimerCallBack, this); + + CProviderInet6Base::InitL(); + iFlow.SetProtocol(KProtocolInetTcp); + iFlow.SetNotify(this); + + iSendQ.InitL(transmitter, 13); + iRecvQ.InitL(receiver, 12); + + iTransmitter = new CAsyncCallBack(sender, KInet6DefaultPriority); + iDelayAckTimer = new CTcpTimer(delack); + iRetransTimer = new CTcpTimer(retransmitter); + iLingerTimer = new CTcpTimer(linger); + if (!iTransmitter || !iDelayAckTimer || !iRetransTimer || !iLingerTimer) + User::Leave(KErrNoMemory); + + iDelayAckTimer->InitL(); + iRetransTimer->InitL(); + iLingerTimer->InitL(); + iSockInBufSize = Protocol()->RecvBuf(); + iSockOutBufSize = Protocol()->SendBuf(); + iSsthresh = KMaxTInt32; + iRTO = Protocol()->InitialRTO(); + ClearRTT(); + iMSS = Protocol()->MSS(); + iSMSS = KTcpStandardMSS; + iRMSS = KTcpStandardMSS; + iLinger = -1; // linger disabled + iFlags.iSackOk = Protocol()->Sack(); + iFlags.iUseTimeStamps = Protocol()->TimeStamps(); + iFlags.iEcn = (Protocol()->Ecn() != 0); + + // Report ICMP errors to application + iSockFlags.iReportIcmp = ETrue; + + if (iFlags.iUseTimeStamps) + iOptions.SetTimeStamps(0, 0); + + if (iFlags.iSackOk) + iOptions.SetSackOk(); + + iOptions.SetAlignOpt(Protocol()->AlignOpt()); + iStartTime.UniversalTime(); + + iState = ETcpInitial; + +#ifdef SYMBIAN_ADAPTIVE_TCP_RECEIVE_WINDOW + iHiddenFreeWindow = 0; + iNewTcpWindow = 0; + iTcpMaxRecvWin = Protocol()->RecvMaxWnd(); +#endif //SYMBIAN_ADAPTIVE_TCP_RECEIVE_WINDOW + + } + + +void CProviderTCP6::ReadDestinationCache() + { + if (!Protocol()->DstCache()) // Dest. cache is not enabled as ini parameter + { + return; + } + + const TInetAddr& dstaddr = iFlow.FlowContext()->RemoteAddr(); + if (dstaddr.IsUnspecified()) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] ReadDestinationCache() : No destination address"), (TInt)this)); + return; + } + + TInt err = KErrNone; + MDestinationCache *dcache = NULL; + TRAP(err, dcache = IMPORT_API_L(Protocol()->Interfacer(), MDestinationCache)); + if (err != KErrNone || dcache == NULL) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] ReadDestinationCache() : DstCache not available"), (TInt)this)); + return; + } + + const TCacheInfo *cinfo = dcache->Find(dstaddr); + if (!cinfo) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] ReadDestinationCache() : No match for address in cache"), (TInt)this)); + return; + } + + if (cinfo->iMetrics[TCacheInfo::ESsThresh]) + { + iSsthresh = cinfo->iMetrics[TCacheInfo::ESsThresh]; + } + if (cinfo->iMetrics[TCacheInfo::ESRtt]) + { + iSRTT = cinfo->iMetrics[TCacheInfo::ESRtt]; + } + if (cinfo->iMetrics[TCacheInfo::ERto]) + { + iRTO = cinfo->iMetrics[TCacheInfo::ERto]; + } + + LOG(Log::Printf(_L( + "\ttcp SAP[%u] ReadDestinationCache() : Matching DstCache entry found [0x%08x] - ssthresh: %d"), + (TInt)this, cinfo, iSsthresh)); + } + +void CProviderTCP6::StoreDestinationCache() + { + if (!Protocol()->DstCache() || !iFlow.FlowContext()) + { + return; + } + + const TInetAddr& dstaddr = iFlow.FlowContext()->RemoteAddr(); + if (dstaddr.IsUnspecified()) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] StoreDestinationCache() : No destination address"), (TInt)this)); + return; + } + + TInt err = KErrNone; + MDestinationCache *dcache = NULL; + TRAP(err, dcache = IMPORT_API_L(Protocol()->Interfacer(), MDestinationCache)); + if (err != KErrNone || dcache == NULL) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] StoreDestinationCache() : DstCache not available"), (TInt)this)); + return; + } + + TCacheInfo cinfo; + cinfo.ClearAll(); + cinfo.iMetrics[TCacheInfo::ESsThresh] = iSsthresh; + cinfo.iMetrics[TCacheInfo::ESRtt] = iSRTT; + cinfo.iMetrics[TCacheInfo::ERto] = iRTO; + + TRAP(err, dcache->StoreL(dstaddr, cinfo)); + if (err != KErrNone) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] StoreDestinationCache() : DstCache store failed"), (TInt)this)); + return; + } + } + + +void CProviderTCP6::Start() + { + LOG(Log::Printf(_L("Start\ttcp SAP[%u] enter"), (TInt)this)); + CProviderInet6Transport::Start(); + iFlags.iStarted = ETrue; + + // + // Detach from parent socket + // + if (iParent) + { + iSockFlags.iNotify = ETrue; + iParent->DetachChild(this); + + // + // Tell ESock which network interface the child socket is using. + // + const MInterface *iface = Protocol()->Interfacer()->Interface(iFlow.FlowContext()->Interface()); + if (iface != NULL) + { + TPckgBuf netinfo; + netinfo().iIAPId = iface->Scope(EScopeType_IAP); + netinfo().iNetworkId = iface->Scope(EScopeType_NET); + LOG(Log::Printf(_L("\ttcp SAP[%u] Bearer IAP=%d, NID=%d"), (TInt)this, netinfo().iIAPId, netinfo().iNetworkId)); + iSocket->Bearer(netinfo); + } + // + // Report error if TCP has been disconnected already + // + if (InState(ETcpClosed)) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] %s, Error %d"), (TInt)this, TcpState(), KErrDisconnected)); + Error(KErrDisconnected); + } + } + + // + // If an error has occurred, we must deliver it to the + // socket server now that the socket notifier has been + // initialised. + // + if (iLastError.iStatus != KErrNone && iErrorMask != 0) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] Delayed error %d, mask %b delivered."), + (TInt)this, iLastError.iStatus, iErrorMask)); + iSocket->Error(iLastError.iStatus, iErrorMask); + } + + // Wake up the receiver. We might have something to do. + iRecvQ.Wake(); + LOG(Log::Printf(_L("Start\ttcp SAP[%u] exit"), (TInt)this)); + } + + +// +// Stop all transmission (except for iSendQ, which will be allowed to drain) +// +void CProviderTCP6::Stop() + { + if (iTransmitter) + CancelTransmit(); + if (iRetransTimer) + CancelRetransmit(); + if (iDelayAckTimer) + CancelDelayACK(); + if (iLingerTimer) + iLingerTimer->Cancel(); + } + + +// +// Empty all queues +// +void CProviderTCP6::FreeQueues() + { + iSockInQ.Free(); + iSockInQLen = 0; + iSockOutQ.Free(); + iSockOutQLen = 0; + iFragQ.Free(); + iRecvQ.Free(); + iSendQ.Cancel(); + iSendQ.Free(); + } + + +// +// Close the socket down +// +void CProviderTCP6::Close() + { + StoreDestinationCache(); + + iSockFlags.iSendClose = ETrue; + iSockFlags.iRecvClose = ETrue; + Protocol()->UnbindProvider(this); + Stop(); + FreeQueues(); + EnterState(ETcpClosed); + } + + +void CProviderTCP6::Ioctl(TUint aLevel, TUint aName, TDes8* aOption) + { + LOG(Log::Printf(_L("Ioctl\ttcp SAP[%u] %x, %x"), (TInt)this, aLevel, aName)); + if (aLevel == KSolInetTcp && aName == KIoctlTcpNotifyDataSent) + { + iFlags.iDataSentIoctl = ETrue; + CompleteIoctl(KErrNone); + } + else + CProviderInet6Transport::Ioctl(aLevel, aName, aOption); + } + + +void CProviderTCP6::CancelIoctl(TUint aLevel, TUint aName) + { + LOG(Log::Printf(_L("CancelIoctl\ttcp SAP[%u] %x, %x"), (TInt)this, aLevel, aName)); + if (aLevel == KSolInetTcp && aName == KIoctlTcpNotifyDataSent) + CompleteIoctl(KErrCancel); + else + CProviderInet6Transport::CancelIoctl(aLevel, aName); + } + + +TInt CProviderTCP6::SetOption(TUint aLevel, TUint aName, const TDesC8& aOption) + { + TInt ret = KErrNotSupported; + + if (aLevel == KSolInetTcp) + { + TInt intValue; + ret = GetOptionInt(aOption, intValue); + +#ifdef _LOG + if (ret == KErrNone) + Log::Printf(_L("SetOpt\ttcp SAP[%u] KSolInetTcp, %d, %d"), (TInt)this, aName, intValue); + else + Log::Printf(_L("SetOpt\ttcp SAP[%u] KSolInetTcp, %d"), (TInt)this, aName); +#endif + + switch (aName) + { + case KSoTcpAsync2MslWait: + // Not implemented. Not very useful. + ret = KErrNotSupported; + break; + + case KSoTcpKeepAlive: + if (ret == KErrNone) + { + if (!intValue) + { + iFlags.iHaveKeepAlive = FALSE; + iFlags.iHaveTriggeredKeepAlive = FALSE; + } + if (intValue & 1) + iFlags.iHaveKeepAlive = TRUE; + if (intValue & 2) + iFlags.iHaveTriggeredKeepAlive = TRUE; + } + break; + + case KSoTcpMaxSegSize: + if (ret == KErrNone) + { + if (!InState(ETcpInitial)) + ret = KErrLocked; + else if (intValue < STATIC_CAST(TInt, KTcpMinimumMSS)) + ret = KErrArgument; + else + { + iMSS = intValue; + iSMSS = iMSS; + iRMSS = iMSS; + } + } + break; + + case KSoTcpNextSendUrgentData: + if (ret == KErrNone) + iFlags.iNextIsUrgent = intValue ? TRUE : FALSE; + break; + + case KSoTcpNoDelay: + if (ret == KErrNone) + iFlags.iNoDelay = intValue ? TRUE : FALSE; + break; + + case KSoTcpCork: + case KSoTcpNoPush: + if (ret == KErrNone) + { + iFlags.iCork = intValue ? TRUE : FALSE; + if (iFlags.iCork == EFalse && aName == KSoTcpCork) + { + // When turning Cork off, send pending data from output queue immediately. + // This is the only difference between Cork and NoPush + SchedTransmit(); + } + } + break; + + case KSoTcpOobInline: + if (ret == KErrNone) + iFlags.iOobInline = intValue ? TRUE : FALSE; + break; + +#ifdef SYMBIAN_ADAPTIVE_TCP_RECEIVE_WINDOW + case KSoTcpMaxRecvWin: + //Case to set the Max window size from shim. + if(ret == KErrNone) + { + if (InState(ETcpClosed|ETcpFinWait2|ETcpCloseWait|ETcpLastAck)) + ret = KErrLocked; + else + if(intValue > iTcpMaxRecvWin) + { + iTcpMaxRecvWin = intValue; + } + + } + break; + case KSoTcpRecvWinAuto: + { + // If user sets the window size, we have to give the preference for user setting. Automatic + // window setting will be then disabled by default, once user calls the SetOption to set the + // receive window, this feature will be diabled by default till the socket is opened. + if(iWindowSetByUser) + { + return KErrNone; + } + else if (InState(ETcpClosed|ETcpFinWait2|ETcpCloseWait|ETcpLastAck)) + ret = KErrLocked; + else if (intValue < STATIC_CAST(TInt, KTcpMinimumWindow)) + iSockInBufSize = KTcpMinimumWindow; + else + { + + //If new TCP window is larger then the previous window, increase the + //iSockInBufSize right now. TCP recv function takes care of + //advertising a new effective TCP window. + if (intValue >= iSockInBufSize) + { + //Make it Zero so TCP could avoid the + //TCP window shrinking processing in Recv. + iNewTcpWindow = 0; + //FreeWindow has to be increased at the same time. + iFreeWindow += intValue - iSockInBufSize; + // Make the new TCP receive buffer change effective now. + iSockInBufSize = intValue; + } + else + { + //This sets iNewTcpWindow to a non-zero value, which indicates + //to the TCP that window is shrunk and process TCP segments + //which are in air before setting a new TCP receive buffer. + //TCP Receive window starts moving only when TCP hidden window + //size exceeds the size of the shrunk window. + + iNewTcpWindow = intValue; + //Even in case of window shrink we can set the receive buffer size + //immediately. This will be helpful, for processing SYN-ACK and other + //receiver side processing. + //For already connected sockets iNewTcpWindow will be taking care + //of shrinking the window size for that TCP session. + iSockInBufSize = iNewTcpWindow; + if( iAdvertisedWindow > iNewTcpWindow ) + { + iShrinkedWindowSize = iAdvertisedWindow - iNewTcpWindow; + } + else + { + // No Need to process TCP receive window processing. + iNewTcpWindow = 0; + } + } + + } + + } + break; +#endif //SYMBIAN_ADAPTIVE_TCP_RECEIVE_WINDOW + case KSoTcpRecvWinSize: + { +#ifdef SYMBIAN_ADAPTIVE_TCP_RECEIVE_WINDOW + iWindowSetByUser = ETrue; +#endif // SYMBIAN_ADAPTIVE_TCP_RECEIVE_WINDOW + if (ret == KErrNone) + { + if (!InState(ETcpInitial)) + ret = KErrLocked; + else if (intValue < STATIC_CAST(TInt, KTcpMinimumWindow)) + iSockInBufSize = KTcpMinimumWindow; + else + { + iSockInBufSize = intValue; + } + } + } + break; + + case KSoTcpSendWinSize: + if (ret == KErrNone) + { + if (!InState(ETcpInitial)) + ret = KErrLocked; + else if (intValue < STATIC_CAST(TInt, KTcpMinimumWindow)) + iSockOutBufSize = KTcpMinimumWindow; + else + iSockOutBufSize = intValue; + } + break; + + case KSoTcpLinger: + if (aOption.Length() < (TInt)sizeof(TSoTcpLingerOpt)) + { + return KErrArgument; + } + if (iSockFlags.iSendClose) + { + return KErrInUse; + } + + { + TSoTcpLingerOpt *opt = (TSoTcpLingerOpt *)aOption.Ptr(); + if (opt->iOnOff != 0 && opt->iLinger >= 0) + { + if (opt->iLinger > KTcpMaxLingerTime) + { + return KErrArgument; + } + iLinger = opt->iLinger; // linger enabled. + } + else + { + iLinger = -1; // linger disabled. + } + } + break; + + default: + ret = KErrNotSupported; + break; + } + } + + if (aLevel == KSolInetIp) + { + TInt intValue; + ret = GetOptionInt(aOption, intValue); + + switch(aName) + { + case KSoIpTOS: + // Silently filter out the ECN bits from TOS setting. + // We're assuming that Protocol->Ecn() (based on ini params) holds the correct + // values for ECN bits. + { + intValue = (intValue & 0xfc) | Protocol()->Ecn(); + TPckgBuf tosopt(intValue); + + // Rest of the option processing is done at the lower levels. + return CProviderInet6Transport::SetOption(aLevel, aName, tosopt); + } + + case KSoHeaderIncluded: + case KSoRawMode: + // The base class implements HeaderIncluded and RawMode by default. + // Force "Not Supported" for TCP here! + return KErrNotSupported; + default: + ret = KErrNotSupported; + } + } + + + if (ret == KErrNotSupported) + ret = CProviderInet6Transport::SetOption(aLevel, aName, aOption); + + return ret; + } + + +TInt CProviderTCP6::GetOption(TUint aLevel, TUint aName, TDes8& aOption) const + { + LOG(Log::Printf(_L("GetOpt\ttcp SAP[%u] %d, %d"), (TInt)this, aLevel, aName)); + CProviderTCP6 *This = CONST_CAST(CProviderTCP6*, this); + TInt ret = KErrNotSupported, urgentChar; + + switch (aLevel) + { + case KSOLSocket: + switch (aName) + { + case KSOReadBytesPending: + ret = SetOptionInt(aOption, iPending); + break; + + case KSOUrgentDataOffset: + ret = SetOptionInt(aOption, iUpCount ? UrgentOffset() - iUpCount + 1 : 0); + LOG(Log::Printf(_L("\ttcp SAP[%u] Urgent data offset = %d"), + (TInt)this, iUpCount ? UrgentOffset() : 0)); + break; + + default: + break; + } + break; + + case KSolInetTcp: + switch (aName) + { + case KSoTcpAsync2MslWait: + // XXX - Not implemented. Not very useful. + break; + + case KSoTcpKeepAlive: + ret = SetOptionInt(aOption, iFlags.iHaveKeepAlive); + break; + + case KSoTcpMaxSegSize: + ret = SetOptionInt(aOption, iMSS); + break; + + case KSoTcpNextSendUrgentData: + ret = SetOptionInt(aOption, iFlags.iNextIsUrgent); + break; + + case KSoTcpNoDelay: + ret = SetOptionInt(aOption, iFlags.iNoDelay); + break; + + case KSoTcpCork: + case KSoTcpNoPush: + ret = SetOptionInt(aOption, iFlags.iCork); + break; + + case KSoTcpOobInline: + ret = SetOptionInt(aOption, iFlags.iOobInline); + break; + + case KSoTcpRecvWinSize: + ret = SetOptionInt(aOption, iSockInBufSize); + break; + + case KSoTcpSendWinSize: + ret = SetOptionInt(aOption, iSockOutBufSize); + break; + + case KSoTcpListening: + ret = SetOptionInt(aOption, InState(ETcpListen) ? 1 : 0); + break; + + case KSoTcpNumSockets: + ret = SetOptionInt(aOption, Protocol()->SapCount()); + break; + + case KSoTcpLinger: + if (aOption.MaxLength() < (TInt)sizeof(TSoTcpLingerOpt)) + { + return KErrTooBig; + } + + TSoTcpLingerOpt opt; + if (iLinger == -1) + { + opt.iOnOff = 0; + opt.iLinger = 0; + } + else + { + opt.iOnOff = 1; + opt.iLinger = iLinger; + } + aOption.SetLength(sizeof(opt)); + aOption.Copy((TUint8*)&opt, sizeof(opt)); + ret = KErrNone; + break; + + case KSoTcpPeekUrgentData: + if (ret = This->GetUrgent(urgentChar, KSockReadPeek), ret == KErrNone) + ret = SetOptionInt(aOption, urgentChar); + break; + + case KSoTcpRcvAtMark: + ret = SetOptionInt(aOption, (UrgentOffset() == 0) ? 1 : 0); + break; + + case KSoTcpReadBytesPending: + ret = SetOptionInt(aOption, iPending); + break; + + case KSoTcpReadUrgentData: + if (ret = This->GetUrgent(urgentChar, 0), ret == KErrNone) + ret = SetOptionInt(aOption, urgentChar); + break; + + case KSoTcpSendBytesPending: + ret = SetOptionInt(aOption, iSockOutQLen); + break; + + default: + break; + } + break; + + case KSolInetIp: + switch(aName) + { + case KSoIpTOS: + // Clear the ECN bits from the returned value before delivering it to socket. + ret = CProviderInet6Transport::GetOption(aLevel, aName, aOption); + if (ret != KErrNone) + { + TInt intValue; + if (GetOptionInt(aOption, intValue) == KErrNone) + ret = SetOptionInt(aOption, intValue & 0xfc); + } + break; + case KSoHeaderIncluded: + case KSoRawMode: + // The base class implements HeaderIncluded and RawMode by default. + // Force "Not Supported" for TCP here! + return KErrNotSupported; + default: + break; + } + break; + + default: + break; + } + + if (ret == KErrNotSupported) + ret = CProviderInet6Transport::GetOption(aLevel, aName, aOption); + + return ret; + } + + +TInt CProviderTCP6::SetRemName(TSockAddr &aAddr) + { + TInt err; + TInetAddr addr = aAddr; + + // Check port range + if (addr.Port() < 1 || addr.Port() > 65535) + return KErrGeneral; + + // Check address + if (addr.IsUnspecified() || !addr.IsUnicast()) + return KErrBadName; + + if (addr.Family() == KAfInet) + addr.ConvertToV4Mapped(); + + TInt family = addr.IsV4Mapped() ? KAfInet : KAfInet6; + + if(iSockFamily == KAFUnspec) + iSockFamily = family; + else if (iSockFamily != family) + return KErrBadName; + + // + // If we're reusing a local address we must check for an existing + // connection before we can accept the remote address. + // + if (iSockFlags.iReuse && iFlow.FlowContext()->LocalPort() != KInetPortNone) + { + if (Protocol()->LocateSap(EMatchConnection, KAFUnspec, iFlow.FlowContext()->LocalAddr(), addr)) + return KErrInUse; + } + + // Set remote address and port.. use original to get iAppFamily valid. + if (err = CProviderInet6Transport::SetRemName(aAddr), err != KErrNone) + return err; + + return KErrNone; + } + + +void CProviderTCP6::ActiveOpen() + { + LOG(Log::Printf(_L("ActiveOpen\ttcp SAP[%u]"), (TInt)this)); + ASSERT(InState(ETcpInitial)); + + TInt status = iFlow.Connect(); + if (status < 0) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] Flow status = %d"), (TInt)this, status)); + iSockFlags.iConnected = EFalse; + Error(status, MSocketNotify::EErrorConnect); + return; + } + + // Store KeepInterfaceUp to be restored when entering Established state. + StoreKeepInterfaceUp(); + + EnterState(ETcpConnect); + if (status == EFlow_READY) + SendSYN(); + } + + +TInt CProviderTCP6::PassiveOpen(TUint aQueSize) + { + ASSERT(InState(ETcpInitial)); + ASSERT(!iListenQueue); + LOG(Log::Printf(_L("PassiveOpen\ttcp SAP[%u] QueSize=%d"), (TInt)this, aQueSize)); + + // + // On EPOC you can only call listen once, so it is safe to do this here. + // + iListenQueue = new CProviderTCP6*[aQueSize]; + if (!iListenQueue) + return KErrNoMemory; + + iListenQueueSize = aQueSize; + for (TUint i=0; i < iListenQueueSize; i++) + iListenQueue[i] = 0; + EnterState(ETcpListen); + return KErrNone; + } + +// +// This routine tries to detach the socket from the socket server. +// Detach() is called from Shutdown(), when the user has called +// Close() or Shutdown(ENormal). +// +// If linger timeout is active, Shutdown() will complete with +// error KErrWouldBlock. +// +void CProviderTCP6::Detach() + { + LOG(Log::Printf(_L("\ttcp SAP[%u] Detach()"), (TInt)this)); + + StoreDestinationCache(); + + CompleteIoctl(KErrCancel); + if (iLinger > 0) + { + iLingerTimer->Cancel(); + Error(KErrWouldBlock, MSocketNotify::EErrorClose); + } + iLinger = -1; + NoSecurityChecker(); // The checker will be unusable. + iSocket->CanClose(MSocketNotify::EDetach); + iSockFlags.iNotify = EFalse; // No more upcalls + iSockFlags.iAttached = (iSocket != 0); // Did we get detached? + LOG(if (iSockFlags.iAttached) Log::Printf(_L("\ttcp SAP[%u] DETACH FAILED!"), (TInt)this)); + ASSERT(!iSockFlags.iAttached); + } + +void CProviderTCP6::Expire() + { + if (!iSockFlags.iAttached) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] Expire(): SELF DESTRUCT!"), (TInt)this)); + delete this; + return; + } + if (iSockFlags.iNotify) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] Expire(): DISCONNECT!"), (TInt)this)); + Close(); + iSockFlags.iNotify = EFalse; + iSocket->Disconnect(); + } + } + +void CProviderTCP6::Shutdown(TCloseType aOption) + { + LOG(Log::Printf(_L("Shutdown\ttcp SAP[%u] TCloseType=%d"), (TInt)this, aOption)); + + switch(aOption) + { + case ENormal: + if (InState(ETcpListen|ETcpInitial|ETcpConnect|ETcpSynSent|ETcpClosed)) + { + // Just do a brutal shutdown in these states. + Close(); + break; + } + + // No need to negotiate parameters as we only want to notify other end point. + if (InState(ETcpSynReceived)) + ClearSYNSettings(); + + // Send RST if receive queue is not empty. + if (iLinger == 0 || SockInQLen() || !iFragQ.IsEmpty()) + { + SendReset(iSND.NXT); + if (FatalState()) + break; + + // If linger is enabled and timer==0, close the socket immediately + if (iLinger == 0) + { + Close(); + break; + } + } + + iSockFlags.iSendClose = ETrue; + iSockFlags.iRecvClose = ETrue; + iSockInQ.Free(); + iSockInQLen = 0; + iFragQ.Free(); + iNewData = 0; + + if (iLinger == -1 || iSockOutQLen == 0) + { + Detach(); + } + else + { + // + // Start linger timer. RSocket::Close() returns when timer + // expires or when all data has been succesfully transmitted. + // + iLingerTimer->Start(iLinger * KOneSecondUs); + } + SchedTransmit(); + + break; + + case EStopInput: + case EStopOutput: + if (InState(ETcpListen|ETcpInitial)) + { + Error(KErrNotSupported, MSocketNotify::EErrorClose); + return; + } + + if (aOption == EStopInput) + { + iSockFlags.iRecvClose = ETrue; + + // Send RST if receive queue is not empty. + if (SockInQLen() || !iFragQ.IsEmpty()) + { + SendReset(iSND.NXT); + if (FatalState()) + break; + } + iSockInQ.Free(); + iFragQ.Free(); + iNewData = 0; + } + else + { + // HalfDuplex Close and simultaneous SYN_RCVD -> FIN_WAIT_1 is not supported. + iSockFlags.iSendClose = ETrue; + SchedTransmit(); + } + + Error(KErrNone, MSocketNotify::EErrorClose); + Nif::SetSocketState(ENifSocketConnected, this); + break; + + case EImmediate: + if (InState(ETcpSynReceived|ETcpEstablished|ETcpFinWait1|ETcpFinWait2|ETcpCloseWait)) + SendReset(iSND.NXT); + CompleteIoctl(KErrCancel); + Close(); + return; + + default: + Panic(EInet6Panic_NotSupported); + break; + } + + // + // If we encountered a fatal error during the above processing, + // stop everything and enter CLOSED state. + // + if (FatalState()) + Close(); + + if (InState(ETcpFinWait2) && iSockFlags.iRecvClose) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] Setting FIN-WAIT-2 timeout"), (TInt)this)); + SchedMsl2Wait(); + } + + // + // If we're in CLOSED state and we're not detached, + // tell the socket server that we're ready to die. + // + if (iSocket && InState(ETcpClosed)) + iSocket->CanClose(); + + // + // Note! The socket server can immediately delete the socket + // within CanClose(). Don't add anything here! + // + } + + +// +// PRTv1.0 API +// +TUint CProviderTCP6::Write(const TDesC8 & aDesc, TUint aOptions, TSockAddr* /*aAddr*/ /*=NULL*/) + { + TDualBufPtr buf(aDesc); + return Send(buf, aDesc.Length(), aOptions); + } + +void CProviderTCP6::GetData(TDes8 & aDesc, TUint aOptions, TSockAddr* /*aAddr*/) + { + TDualBufPtr aBuf(aDesc); + Recv(aBuf, aDesc.Length(), aOptions); + } + +// +// PRTv1.5 API +// +TInt CProviderTCP6::Write(RMBufChain& aData, TUint aOptions, TSockAddr* /* anAddr*/) + { + TDualBufPtr buf(aData); + return Send(buf, aData.Length(), aOptions); + } + +TInt CProviderTCP6::GetData(RMBufChain& aData, TUint aLength, TUint aOptions, TSockAddr* /*aAddr*/) + { + TDualBufPtr aBuf(aData); + return Recv(aBuf, aLength, aOptions); + } + +TInt CProviderTCP6::Send(TDualBufPtr& aBuf, TInt aLength, TUint aOptions) + { + LOG(Log::Printf(_L("Write\ttcp SAP[%u] len=%d, options=%d"), (TInt)this, aLength, aOptions)); + ASSERT(aLength > 0); + + // + // Limit queue size to maximum socket buffer size rounded down to nearest multiple segment. + // Note: during fast recover we allow the socket buffer to expand in order to have enough + // data to keep the transmission going. + // + TInt effMSS = EffectiveMSS(); + TInt bufSize = iSockOutBufSize + + (iSacked.Count() ? (iSacked.Last()->iRight - iSND.UNA) : (iDupAcks * iSMSS)); + TInt reserve = (bufSize - (iSND.NXT - iSND.UNA)) % effMSS; + TInt space = bufSize - iSockOutQLen - reserve; + + LOG(Log::Printf(_L("\ttcp SAP[%u] len=%d space=%d"), (TInt)this, aLength, space)); + + if ((aOptions & KSockWriteUrgent) || iFlags.iNextIsUrgent) + { + // + // According to RFC1122 the urgent pointer should point to the + // last byte of urgent data. However, practically all TCP stacks + // today choose to conform to BSD unix and follow the original + // behaviour stated in RFC793. Therefore, we set the urgent pointer + // to point to the first non-urgent byte following the urgent data. + // + iFlags.iNextIsUrgent = EFalse; + iSND.UP = iSND.UNA + iSockOutQLen + aLength; + LOG(Log::Printf(_L("\ttcp SAP[%u] Urgent pointer set, seq = %u"), (TInt)this, iSND.UP.Uint32())); + } + + if (space <= 0) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] FLOW STOPPED"), (TInt)this)); + iSockFlags.iFlowStopped = ETrue; + return 0; + } + + // Append data to socket queue. + aLength = aBuf.Append(iSockOutQ, (aLength < space) ? aLength : space); + iSockOutQLen += aLength; + + // We make a simple Nagle check here to avoid starting the transmitter unnecessarily. + // To spell it out: + // - If Cork option is set, always wait for full-sized segment before sending. + // - Without Cork option one under-sized segment is allowed to be outstanding. + // - TODO: With NoPush option segments are only sent when send buffer is full or + // when the connection is closed. + if (!iSND.WND) + { + // Start probing + SchedRetransmit(); + } + else if ((iPartialSeq <= iSND.UNA && !iFlags.iCork) || + iSND.UNA + iSockOutQLen + aLength >= iSND.NXT + effMSS || + iFlags.iNoDelay) + { + // Start transmitter + SchedTransmit(); + } + + return aLength; + } + + +TInt CProviderTCP6::Recv(TDualBufPtr& aBuf, TInt aLength, TUint aOptions) + { + LOG(Log::Printf(_L("GetData\ttcp SAP[%u] len=%d, options=%d"), (TInt)this, aLength, aOptions)); + + // Update iPending + if (!(aOptions & KSocketInternalReadBit)) + iPending -= aLength; + + // This might happen when aborting a connection. + if (iSockInQ.IsEmpty()) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] No data available!"), (TInt)this)); + return 0; + } + + // + // Discard in-band urgent data junk + // + while (iUpCount && UrgentOffset(0) == 0 && (!iFlags.iUrgentMode || UrgentOffset() > 0)) + { + iSockInQ.TrimStart(1); + --iSockInQLen; + ++iFreeWindow; + ForgetUrgentPointer(); + } + + ASSERT((aOptions & KSockReadPeek) || !iCopyOutOffset); + ASSERT(iCopyOutOffset + aLength <= (TInt)iSockInQLen); + + // + // Peek. Copy from queue, fix counters and force receiver restart. + // + if (aOptions & KSockReadPeek) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] Peek"), (TInt)this)); + + TInt err = aBuf.CopyIn(iSockInQ, iCopyOutOffset, aLength); + if (err != KErrNone) + return KErrNoMBufs; + + if (!(aOptions & KSocketInternalReadBit)) + { + iFlags.iCompleteRecv = ETrue; + iNewData += aLength; + iCopyOutOffset += aLength; + iRecvQ.Wake(); + } + + return aLength; + } + + // + // Normal read. Consume data from the queue. + // + // Note: aBuf.Consume() may return less than we asked + // if it is unable to allocate an MBuf for splitting the + // inbound queue. This can only happen with PRTv1.5. + // If we get nothing at all we return KErrNoMBufs here. + // + aLength = aBuf.Consume(iSockInQ, aLength, iBufAllocator); + if (aLength == 0) + return KErrNoMBufs; + + // + // If we are now reading the actual urgent data, force the + // application level read operation to complete and exit + // urgent mode. + // + if (iFlags.iUrgentMode && UrgentOffset() == 0) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] Reading urgent data. Clear urgent mode"), (TInt)this)); + iFlags.iUrgentMode = EFalse; + iFlags.iCompleteRecv = !(aOptions & KSocketInternalReadBit); + ForgetUrgentPointer(); + } + + iSockInQLen -= aLength; + +#ifdef SYMBIAN_ADAPTIVE_TCP_RECEIVE_WINDOW + //Application is reading the data from the receive Q buffer + //We need to make sure, that we will show that window is not shrinked + //to the sender, so tha right edge of the sender window remains constant. + //if This is true, then it is a case of TCP window shrink and we need + //to handle it. + if ( iNewTcpWindow ) + { + //Log this message for information, that Window is shrinked + LOG(Log::Printf(_L("\ttcp SAP[%u] TCP window shrinking mode on"), (TInt)this)); + + //Increase the hidden free TCP receive window. + iHiddenFreeWindow += aLength; + + if (iHiddenFreeWindow >= iShrinkedWindowSize) + { + //Disable window shrink processing, so that TCP could switch + //to the normal processing. + iSockInBufSize = iNewTcpWindow; + + //Add the usable window to the free window. + iFreeWindow += iHiddenFreeWindow - iShrinkedWindowSize; + + //There are chances that TCP receive window might further shrink. + iHiddenFreeWindow = 0; + + //TCP Receive window shrink phase is over. + iNewTcpWindow = 0; + + //Log this message for information, that Window is shrinked + LOG(Log::Printf(_L("\ttcp SAP[%u] TCP window shrinking mode off"), (TInt)this)); + } + } + else +#endif //SYMBIAN_ADAPTIVE_TCP_RECEIVE_WINDOW + + { + iFreeWindow += aLength; + } + + // + // If we have read everything upto the urgent data, + // force the application level read to complete. + // If the urgent data has not yet been read, signal + // the application again (read completion will clear + // the signal). + // + if (UrgentOffset() == 0) + { + iFlags.iCompleteRecv = ETrue; + if (iFlags.iUrgentMode) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] Now at urgent data"), (TInt)this)); + iFlags.iNotifyUrgent = ETrue; + } + } + + // + // Increase receive window in multiples of RMSS + // + // The following assumes that the path MTU is symmetric and that + // the peer uses the same options as we do. We could use some + // kind of peer effective MSS detection heuristic in order to + // make this smarter. + // + TUint effMSS = EffectiveMSS(); + if (iFreeWindow >= effMSS) + { + // + // Round down to nearest multiple of RMSS. However, be careful not to + // shrink a previously advertised window. + // + iFreeWindow += iRCV.WND; + iRCV.WND = Max(iRCV.WND, effMSS * (iFreeWindow / effMSS)); + iFreeWindow -= iRCV.WND; // Leave reminder for later. + + // + // If our last advertised window is so small that the sender might + // block before we would normally send our next ack, we will send + // a window update. This will prevent the sender from unnecessarily + // going into probe mode. We will only do this if we can advertise + // at least half a window. + // + if (InState(ETcpEstablished|ETcpFinWait1|ETcpFinWait2)) + { + if ((TInt)iAdvertisedWindow <= iSockInBufSize/4 && iRCV.WND >= iSockInBufSize/2) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] Sending window update %d -> %d"), (TInt)this, iAdvertisedWindow, iRCV.WND)); + SendSegment(KTcpCtlACK); + } + } + } + + // + // If we have data that has not yet been announced to the socket server, + // make sure we get around to it. + // + if (iNewData) + iRecvQ.Wake(); + + LOG(Log::Printf(_L("GetData\ttcp SAP[%u] returns %d bytes, %d bytes pending"), (TInt)this, aLength, iPending)); + return aLength; + } + + +// +// Process ICMP Error. Treat it as a soft error unless we're trying to connect. +// +void CProviderTCP6::IcmpError(TInt aError, TUint aOperationMask, TInt aType, TInt aCode, + const TInetAddr& aSrcAddr, const TInetAddr& aDstAddr, const TInetAddr& aErrAddr) + { + CProviderInet6Transport::IcmpError(aError,InState(ETcpSynSent|ETcpSynReceived) ? + MSocketNotify::EErrorAllOperations : aOperationMask, + aType, aCode, aSrcAddr, aDstAddr, aErrAddr); + } + +// +// This routine maintains a sorted rotating array of detected urgent pointers. +// The lowest detected UP is always stored in iUpArray[iUpIndex] and the highest +// one is stored in iUpArray[(iUpIndex + iUpCount - 1) % KTcpUpMax]. +// If the array overflows, the lowest UP is always discarded. +// +void CProviderTCP6::RememberUrgentPointer(TTcpSeqNum aUp) + { + // Insert new UP into the sorted list. + TInt i; + TTcpSeqNum up; + + for (i = iUpIndex + iUpCount; i > iUpIndex; --i) + { + up = iUpArray[(i - 1) % KTcpUpMax]; + if (aUp > up) + break; + if (aUp == up) + return; + } + + if (i == iUpIndex && iUpCount == KTcpUpMax) + return; + TUint index; + while (i < iUpIndex + iUpCount) + { + index = i % KTcpUpMax; + up = iUpArray[index]; + iUpArray[index] = aUp; + aUp = up; + ++i; + } + + iUpArray[i % KTcpUpMax] = aUp; + if (iUpCount < KTcpUpMax) + { + ++iUpCount; + } + else + { + iUpIndex += 1; + if(iUpIndex >= KTcpUpMax) + iUpIndex -= KTcpUpMax; + } + + iFlags.iUrgentMode = ETrue; + iFlags.iNotifyUrgent = !iFlags.iOobInline; + } + +void CProviderTCP6::ForgetUrgentPointer() + { + if (iUpCount) + { + if (++iUpIndex >= KTcpUpMax) + iUpIndex = 0; + --iUpCount; + } + } + + +TInt CProviderTCP6::GetUrgent(TInt& aUrgentChar, TUint aOptions) + { + TTcpSeqNum up; + RMBuf *m; + TBool found = EFalse; + TInt urgentOffset = UrgentOffset(); + + if (!iFlags.iUrgentMode || iFlags.iOobInline) + return KErrNotFound; + + up = UrgentHigh(); + if (up <= iRCV.NXT) + { + // It's in the socket queue. + if (urgentOffset == 0 && !(aOptions & KSockReadPeek)) + { + // + // It's first in queue. Fetch it with Recv(), so that receive + // window is correctly updated. + // + TBuf8<1> buf(1); + TDualBufPtr p(buf); + iNewData--; + Recv(p, 1, aOptions | KSocketInternalReadBit); + aUrgentChar = buf[0]; + LOG(Log::Printf(_L("\ttcp SAP[%u] GetUrgent(): Urgent char first in socket queue = %02x"), (TInt)this, aUrgentChar)); + found = ETrue; + } + else + { + for (m = iSockInQ.First(); m != NULL; m = m->Next()) + { + if (urgentOffset < m->Length()) + { + aUrgentChar = *(m->Ptr() + urgentOffset); + LOG(Log::Printf(_L("\ttcp SAP[%u] GetUrgent(): Urgent char in socket queue = %02x"), (TInt)this, aUrgentChar)); + found = ETrue; + break; + } + urgentOffset -= m->Length(); + } + } + } + else if (up <= iSND.WL1 + iRMSS) + { + // Maybe it's in the fragment queue. + TMBufPktQIter iter(iFragQ); + for (iter.SetToFirst(); iter.More(); iter++) + { + RMBufTcpFrag& frag = (RMBufTcpFrag&)iter.Current(); + TTcpSeqNum seq = frag.Offset(); + TUint32 len = frag.FragmentLength(); + + if (up <= seq) + break; + + if (up <= seq + len) + { + TTcpPacket seg(frag); + RMBuf *m, *prev; + TInt off, len; + frag.Goto(seg.iHdr->HeaderLength() + (up - seq) - 1, + m, off, len, prev); + aUrgentChar = *(m->Ptr() + off); + LOG(Log::Printf(_L("\ttcp SAP[%u] GetUrgent(): Urgent char in fragment queue = %02x"), (TInt)this, aUrgentChar)); + found = ETrue; + break; + } + } + } + + if (!found) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] GetUrgent(): Would block"), (TInt)this)); + return KErrWouldBlock; + } + + if (!(aOptions & KSockReadPeek)) + { + iFlags.iUrgentMode = EFalse; + + // Clear urgent mode exception flag + if (iSockFlags.iNotify) + iSocket->Error(KErrNone, 0); + } + + return KErrNone; + } + + +void CProviderTCP6::Process(RMBufChain& aPacket, CProtocolBase* /*aSourceProtocol*/) + { + RMBufRecvInfo *const info = RMBufRecvPacket::PeekInfoInChain(aPacket); +#ifdef SYMBIAN_NETWORKING_UPS + if (info == NULL || (!HasNetworkServices() && (ConnectionInfoSet() == EFalse) && (info->iFlags & KIpLoopbackPacket) == 0)) +#else + if (info == NULL || (!HasNetworkServices() && (info->iFlags & KIpLoopbackPacket) == 0)) +#endif + { + LOG(Log::Printf(_L("\ttcp SAP[%u] Not allowed to receive external packets"), (TInt)this)); + aPacket.Free(); + return; + } + iRecvQ.Append(aPacket); + iRecvQ.Wake(); + } + + +void CProviderTCP6::ErrorExpire(TInt aError) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] ErrorExpire(): Closing SAP on fatal error: %d"), (TInt)this, aError)); + if (!iSockFlags.iAttached) + { + Expire(); + return; + } + Close(); + CProviderInet6Transport::Error(aError); + } + +void CProviderTCP6::CanSend() + { + LOG(Log::Printf(_L("\ttcp SAP[%u] CanSend()"), (TInt)this)); + + // + // If the flow has become unblocked, process all missed events + // and restart transmitter. + // + TInt flowStatus = iFlow.Status(); + if (flowStatus < 0) + { + ErrorExpire(flowStatus); + return; + } + + if (flowStatus == EFlow_READY) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] CanSend(): Flow UNBLOCKED"), (TInt)this)); + + if (InState(ETcpListen|ETcpConnect)) + { + // + // Get the SAP started. + // + if (InState(ETcpConnect)) + SendSYN(); + else if (InState(ETcpListen) && !iRecvQ.IsEmpty()) + iRecvQ.Wake(); + } + + if (iFlags.iRetransmitPending) + { + iFlags.iRetransmitPending = EFalse; + RetransmitSegments(); + } + + if (iFlags.iTransmitPending) + { + iFlags.iTransmitPending = EFalse; + SendSegments(); + } + + if (!iSendQ.IsEmpty()) + iSendQ.Wake(); + + if (CanTriggerKeepAlive()) + { + // The heaviest time check only if we are otherwise allowed to send the keepalive. + TUint32 time_now = TimeStamp(); + if (time_now - iLastTriggeredKeepAlive > KTcpKeepAliveTH * KOneSecondUs) + { + iLastTriggeredKeepAlive = time_now; + LOG(Log::Printf(_L("\ttcp SAP[%u] CanSend(): Sending a Keep-Alive probe"), (TInt)this)); + SendSegment(KTcpCtlACK, iSND.UNA - 1, 0); + } + } + } + } + + +// +// This is the actual event driven asynchronous transmitter loop. +// +void CProviderTCP6::Transmit() + { + LOG(Log::Printf(_L("\ttcp SAP[%u] Transmit"), (TInt)this)); + + RMBufSendPacket packet; + TInt flowStatus; + LOG(TInt count = 0); + + ASSERT(!iSendQ.IsEmpty()); + RMBufSendInfo *info = NULL; + // Transmit packets when flow is ready + while ((flowStatus = iFlow.Status()) == EFlow_READY && iSendQ.Remove(packet)) + { + info = packet.PeekInfo(); + info->iFlow.Open(iFlow); + Protocol()->Send(packet); + LOG(count++); + } + + // Local congestion control + if (flowStatus == EFlow_HOLD && !iSendQ.IsEmpty()) + SourceQuench(); + + LOG(Log::Printf(_L("\ttcp SAP[%u] %d segments transmitted"), (TInt)this, count)); + LOG(if (flowStatus > EFlow_READY) Log::Printf(_L("\ttcp SAP[%u] Flow BLOCKED"), (TInt)this)); + //LOG(if (flowStatus != EFlow_READY) Log::Printf(_L("CProviderTCP6::Transmit(): Flow status = %d.\r\n"), flowStatus)); + + if (flowStatus < 0) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] Error %d. Segments"), (TInt)this, flowStatus)); + ErrorExpire(flowStatus); + } + } + + +// NOTE: There are a few tricks in the ECN implementation that should be considered. ECT bit MUST NOT +// be set in IP packets in the following cases [RFC 3168, Sec. 6.1.4 -- 6.1.6]: +// 1) pure ACKs +// 2) retransmitted segments +// 3) zero window probes + + +// +// This routine generates and transmits a TCP segment. +// +TInt CProviderTCP6::SendSegment(TUint8 aFlags, TTcpSeqNum aSeq, TUint32 aDataLen) + { + //LOG(Log::Printf(_L("CProviderTCP6::SendSegment(%06b,%d,%d)\r\n"),aFlags,aSeq,aMaxLen)); + + ASSERT(!(aFlags & KTcpCtlRST)); + + RMBufSendPacket seg; + RMBufSendInfo *info = NULL; + TInt err; + TInt up = iSND.UP - aSeq; + TInt seqLen = aDataLen; + if (aFlags & (KTcpCtlSYN|KTcpCtlFIN)) + ++seqLen; + + if (iFlags.iEcnHaveCongestion) + { + aFlags |= KTcpCtlECE; + } + + if (iFlags.iEcnSendCWR) + { + aFlags |= KTcpCtlCWR; + iFlags.iEcnSendCWR = EFalse; + } + + // + // Don't output SACK blocks with data segments + // + iOptions.SuppressSack(aDataLen > 0); + TUint headerLen = KTcpMinHeaderLength + iOptions.Length(); + + // + // Allocate memory for TCP segment and IP headers. + // + // Important: we assume that, when using the TimeStamps option, + // iOptions always contains timestamps, so that the header length + // does not change when we update them below. + // + for (;;) + { + TInt hdrReserve = iFlow.FlowContext()->HeaderSize() + headerLen; + if (aDataLen) + { + err = iSockOutQ.Copy(seg, aSeq - iSND.UNA, aDataLen, hdrReserve); + if(err == KErrNone) + { + err = seg.Prepend(hdrReserve, iBufAllocator); + } + } + else + { + err = seg.Alloc(hdrReserve, iBufAllocator); + } + if(err == KErrNone) + { + info = seg.NewInfo(); + if(info) + { + break; + } + else + { + err = KErrNoMBufs; + } + } + + // Allocation failed. Try to recover. + seg.Free(); + + // Try to free up some memory. XXX - We could do better here. + if (!iFragQ.IsEmpty()) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] SendSegment(): RENEGE!!!"), (TInt)this)); + iFragQ.Free(); + continue; + } + + // If we can't scrounge up a buffer, the packet will be dropped. + LOG(Log::Printf(_L("\ttcp SAP[%u] SendSegment(): No memory, packet DROPPED!"), (TInt)this)); + break; + } + + if (err == KErrNone) + { + // Reserve space for IP headers + seg.TrimStart(iFlow.FlowContext()->HeaderSize()); + + // Check for urgent data + if (up > 0 && up < 0x10000) + aFlags |= KTcpCtlURG; + else + up = 0; + + // + // Fill in TCP header. Note that the header length has already + // been set and the checksum will be calculated later. + // + // Note: If we're probing or the receiver has shrunk its advertised + // window our iSND.NXT may be pointing beyond its advertised window. + // If the packet we're trying to send does not occupy any sequence + // space, we will adjust the sequence number so that it falls withín + // the receiver's window. This will make sure that our ACK and RST + // packets will be accepted. In the worst case, a buggy receiver + // might still ignore our window updates, which might lead to a probe + // deadlock. Well, a TCP that shrinks its window without adjusting + // SND.WL1 deserves what it gets... + // + TTcpPacket pkt(seg); + TTcpSeqNum windowEdge = iSND.UNA + iSND.WND; + pkt.iHdr->SetSrcPort(iFlow.FlowContext()->LocalPort()); + pkt.iHdr->SetDstPort(iFlow.FlowContext()->RemotePort()); + pkt.iHdr->SetSequence(seqLen > 0 || aSeq <= windowEdge ? aSeq : windowEdge); + pkt.iHdr->SetAcknowledgment((aFlags & KTcpCtlACK) ? iRCV.NXT.Uint32() : 0); + if (aFlags & KTcpCtlSYN) + { + // Window scale must not be used in SYN or SYN-ACK segments. + // We do not truncate window size to be divisible by MSS. + // The next window advertisments are properly aligned, though. + pkt.iHdr->SetWindow(Min(iRCV.WND, 0xffff)); + } + else + { + pkt.iHdr->SetWindow(iRCV.WND >> iRcvWscale); + } + pkt.iHdr->SetUrgent(up); + pkt.iHdr->SetControl(aFlags); + + // + // Fill in info struct + // + // coverity[dead_error_condition] + // ASSERT statement required here as the condition "info != NULL" could be false + ASSERT(info != NULL); + info->iProtocol = KProtocolInetTcp; + info->iSrcAddr = iFlow.FlowContext()->LocalAddr(); + info->iDstAddr = iFlow.FlowContext()->RemoteAddr(); + info->iLength = headerLen + aDataLen; + info->iFlags = iFlags.iDoPMTUD ? KIpDontFragment : 0; + + // Check if we have to clear the ECN ECT flag because of the few exception cases listed above. + if (iFlags.iEcn && (aSeq < iSND.NXT || aDataLen < 1)) + { + info->iFlags |= KIpNoEcnEct; + } + + // + // Output TCP options (except for RST packets) + // + if (!(aFlags & KTcpCtlRST)) + { + // Get current time + TUint32 usec = TimeStamp(); + + // + // Measure RTT + // + if (iFlags.iUseTimeStamps) + { +#if 0 + // XXX - This code can cause tsVal to go backwards. + TUint32 lastSent, lastEchoed; + iOptions.TimeStamps(lastSent, lastEchoed); + if (usec == lastSent) + usec++; +#endif + iOptions.SetTimeStamps(usec, iTsRecent); + } + if (iFlags.iTiming) + { + // Check for retransmission. We shouldn't do RTT measurements on + // retransmitted segments because of ambiguity (Karn algorithm). + if (iTimingSeq > aSeq && iTimingSeq <= aSeq + aDataLen) + iFlags.iTiming = EFalse; + } + else if (aSeq == iSND.NXT && seqLen > 0 && iSND.WND > 0) + { + // New segment and not a probe. Start timing. + iFlags.iTiming = ETrue; + iTimingSeq = aSeq + aDataLen; + iTimeStamp = usec; + } + + // + // Output TCP options into the segment header. + // + pkt.iHdr->SetOptions(iOptions); + } + + // + // Compute checksum and send the segment. + // + pkt.ComputeChecksum(seg, info); + LOG(CProtocolTCP6::LogPacket('>', seg, info)); + seg.Pack(); + + if(!iFlags.iFastRetransMode) + { + iSendQ.Append(seg); + } + else + { + //If the fast retransmission mode is set then add the segment in the begining of the sendqueue. + iSendQ.Prepend(seg); + } + iSendQ.Wake(); + } + + CancelDelayACK(); + + // Schedule a retransmission. Restart RTO if there was no outstanding segments, otherwise + // it is only restarted if it was not running already. + if (seqLen) + { + if (iSND.NXT == iSND.UNA) + ReSchedRetransmit(); + else + SchedRetransmit(); + } + + aSeq += seqLen; + if (aSeq > iSND.NXT) + iSND.NXT = aSeq; + iLastAck = iRCV.NXT; + iAdvertisedWindow = iRCV.WND; + + return aDataLen; + } + +// +// Transmit one data segment at given position. Use current +// MTU, send window, and congestion window to limit segment +// size. Return number of bytes advanced in the transmit queue. +// This may be less than transmitted bytes if SACK is being used. +// +TInt CProviderTCP6::SendDataSegment(TTcpSeqNum aSeq, TBool aNagleOverride) + { + TInt effMSS = EffectiveMSS(); +#ifdef SIGNED_UNSIGNED_FIX //if user app set the value which is bigger than 0x7ffffff,The largest possible value for a TInt16. + TUint effWND = MinUU(iSND.WND, iSockOutQLen); +#else + TUint effWND = Min(iSND.WND, iSockOutQLen); +#endif + TTcpSeqNum seq = aSeq; + TUint8 flags = KTcpCtlACK; + TInt len; + + // If RTO just occurred and F-RTO processing is underway, override Nagle. + if (iFRTOsent) + { + aNagleOverride = ETrue; + } + + // + // Transmission guided by SACK (RFC2018) + // + if (iFlags.iSackOk) + { + TTcpSeqNum limitSeq, limit; + TInt awnd, sacked; + + // Find a slot between SACKed blocks + sacked = iSacked.FindGap(seq, limit); + + if (iFlags.iFastRetransMode) + { + // Apply the FACK algorithm + TTcpSeqNum fack = iSacked.Count() ? iSacked.Last()->iRight : iSND.UNA; + awnd = iCwnd - (iSND.NXT - fack); + // + // Allow at least one segment if there are no retransmits out. + // This combines new-Reno style retransmit behaviour with the + // FACK algorithm. Pure FACK is somewhat tardy sending out the + // first retransmit, which causes problems in the usual single + // segment loss case. + // + if (iRetranData) + awnd -= iRetranData; + else if (awnd < (TInt)iSMSS) + awnd = iSMSS; + limitSeq = iSND.UNA + effWND; + LOG(Log::Printf(_L("\ttcp SAP[%u] SendDataSegment(): FAST RECOVERY: una=%u fack=%u awnd=%d"), (TInt)this, (iSND.UNA).Uint32(), fack.Uint32(), awnd)); + } + else + { + // + // Apply congestion window and limited transmit window + // + // Note: if there are SACKed blocks within the congestion window, + // the window is extended accordingly. This is compatible with + // the congestion control principles, since we will not be + // retransmitting the SACKed blocks. + // +#ifdef SIGNED_UNSIGNED_FIX + awnd = (TInt)MinUU(effWND, iCwnd + iLwnd + (TUint)Max(sacked, 0)); +#else + awnd = Min(effWND, iCwnd + iLwnd + (sacked < 0 ? 0 : sacked)); +#endif + limitSeq = iSND.UNA + awnd; + LOG(Log::Printf(_L("\ttcp SAP[%u] SendDataSegment(): NORMAL MODE: una=%u limit=%u wnd=%u"), (TInt)this, (iSND.UNA).Uint32(), limitSeq.Uint32(), awnd)); + } + + if (sacked >= 0) + { + if (limit < limitSeq) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] SendDataSegment(): SACK Adjust: RIGHT %u -> %u"), (TInt)this, limitSeq.Uint32(), limit.Uint32())); + limitSeq = limit; + if (limit <= seq + awnd) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] SendDataSegment(): NAGLE override (SACK)"), (TInt)this)); + aNagleOverride = ETrue; + } + } + } + else if (iFlags.iFastRetransMode) + { + // + // We are beyond the last SACK block. Skip to new data. + // + LOG(Log::Printf(_L("\ttcp SAP[%u] SendDataSegment(): SACK Adjust: SEND NEW DATA"), (TInt)this)); + iSendHigh = seq; // Store position of SACK transmit + seq = iSND.NXT; // Start transmitting new data + } + + LOG(if (seq > aSeq) Log::Printf(_L("\ttcp SAP[%u] SendDataSegment(): SACK Adjust: LEFT %u -> %u"), (TInt)this, aSeq.Uint32(), seq.Uint32())); +#ifdef SIGNED_UNSIGNED_FIX + if(limitSeq > seq) + { + len = (TInt)MinUS(limitSeq - seq, awnd); + } + else + { + len = Min(limitSeq - seq, awnd); + } +#else + len = Min(limitSeq - seq, awnd); +#endif + } + else + { +#ifdef SIGNED_UNSIGNED_FIX + len = iSND.UNA + MinUU(effWND, iCwnd + iLwnd) - seq; +#else + len = iSND.UNA + Min(effWND, iCwnd + iLwnd) - seq; +#endif + } + LOG(Log::Printf(_L("\ttcp SAP[%u] SendDataSegment(%u, %d): una=%u nxt=%u off=%d noff=%d len=%d"), + (TInt)this, aSeq.Uint32(), aNagleOverride, (iSND.UNA).Uint32(), (iSND.NXT).Uint32(), aSeq - iSND.UNA, seq - iSND.UNA, len)); + LOG(Log::Printf(_L("\ttcp SAP[%u] Transmit state: effMSS=%d iCwnd=%d iLwnd=%d effWND=%d iRetranData=%d recovery=%d"), + (TInt)this, effMSS, iCwnd, iLwnd, effWND, iRetranData, iFlags.iFastRetransMode)); + + // + // Sender side SWS avoidance and Nagle. + // + if (len <= 0) + len = 0; + else if (len >= effMSS) + len = effMSS; + else if (seq > iSND.NXT && seq + len < iSND.UNA + iSockOutQLen && !aNagleOverride) + // + // Nagle override is not on and we're trying to send a partial + // segment from the middle of the send queue. Therefore, we must + // be constrained by the receiver window or the congestion window. + // + // If receiver window is very small we must allow a small packet out. + // + len = 0; + else + { + // + // Minshall's modification to the Nagle algorithm. We are + // allowed to transmit a partial segment if there are no + // other unacknowledged partial segments in flight. + // + if (iPartialSeq <= iSND.UNA && !Protocol()->StrictNagle() && !iFlags.iCork) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] SendDataSegment(): NAGLE override (Minshall), seq=%u"), (TInt)this, seq.Uint32())); + aNagleOverride = ETrue; + iPartialSeq = seq + len; + } + + // + // Standard Nagle algorithm. Do not send partial packets + // if there are outstanding unacknowledged segments. + // We also refrain from sending small packets if we have + // blocked the application as that means our send buffer + // is full. + // + // Update: With Cork option enabled, only full-sized segments are sent + // + // Note that we temporarily disable Nagle if we have + // some urgent data to send or if the user has already + // closed the outgoing direction of the connection. + // + if((iSockFlags.iFlowStopped || iSND.UNA < seq || iFlags.iCork) + && iSND.UP <= seq && !iFlags.iNoDelay + && !iSockFlags.iSendClose && !aNagleOverride) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] SendDataSegment(): NAGLE kicked in"), (TInt)this)); + len = 0; + } + else + flags |= KTcpCtlPSH; // Set PSH on all partial segments + } + + if (len) + { + if (seq + len == iSND.UNA + iSockOutQLen) + { + // Send queue has drained. Set PSH if application is not blocked. + if (!iSockFlags.iFlowStopped) + flags |= KTcpCtlPSH; + + // Set FIN if the socket is closing. + if (iSockFlags.iSendClose) + { + flags |= KTcpCtlFIN; + if (InState(ETcpEstablished|ETcpCloseWait)) + EnterState(InState(ETcpEstablished) ? ETcpFinWait1 : ETcpLastAck); + } + } + + if (len = SendSegment(flags, seq, len), len >= 0) + { + // Count retransmitted data for FACK + if (iSacked.Count() && seq < iSacked.Last()->iRight) + { + iSendHigh = seq + len; + if (iFlags.iFastRetransMode) + iRetranData += len; + } + + // Take SACK advance into account + len += (seq - aSeq); + } + } + else if (iSND.WND < (TUint)effMSS) + SchedRetransmit(); // Start probing + + // + // Return the number of bytes by which to advance transmit sequence. + // Note: With SACK this is not necessarily the number of bytes sent. + // + return len; + } + + +// +// Send multiple data segments. +// +void CProviderTCP6::SendSegments(TBool aNagleOverride) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] SendSegments(): queue=%u wnd=%u cwnd=%u, ssthresh=%u"), + (TInt)this, iSockOutQLen, iSND.WND, iCwnd, iSsthresh)); + + iFlags.iTransmitPending = (iFlow.Status() == EFlow_PENDING); + if (iFlags.iTransmitPending) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] SendSegments(): Flow pending"), (TInt)this)); + return; + } + + if (InState(ETcpSynReceived|ETcpEstablished|ETcpCloseWait|ETcpLastAck|ETcpFinWait1|ETcpClosing)) + { + TInt advance; + TUint count = 0; + + if (iTransmitSeq < iSND.UNA) + iTransmitSeq = iSND.UNA; + + // Transmit segments from queue + while (advance = SendDataSegment(iTransmitSeq, aNagleOverride), advance > 0) + { + iTransmitSeq += advance; + // + // Limit transmission to KTcpMaxTransmit segments during + // fast retransmit/recovery. + // + if (iFlags.iFastRetransMode && ++count >= Protocol()->MaxBurst()) + break; + } + + // + // User has closed the connection and send queue has drained? + // + // Note: Normally the FIN bit is slapped on the last data segment + // in SendDataSegment(). However, if we have to send a FIN segement + // with no data, we do it here. + // + if (iSockFlags.iSendClose && + ((InState(ETcpEstablished|ETcpCloseWait) && iSND.NXT == iSND.UNA + iSockOutQLen && advance >= 0) || + (InState(ETcpSynReceived) && iSockFlags.iRecvClose))) + { + EnterState(InState(ETcpSynReceived|ETcpEstablished) ? ETcpFinWait1 : ETcpLastAck); + SendSegment(KTcpCtlFIN|KTcpCtlACK); + } + } + } + + +// +// Handle retransmission timeout +// +// The retransmission timer is shared for multiple purposes. +// There are different reasons why we might end up here: +// +// 1) A normal retransmission timeout. In this case, iSND.NXT > iSND.UNA. +// 2) We are probing a zero window. In this case iSND.WND == 0. +// 3) The Nagle override timeout has expired. In this case, +// iSND.WND != 0 and unsent < effMSS. +// 4) TIME-WAIT or FIN-WAIT-2 timeout. +// 5) If TCP keepalive option is set, expiry of the keepalive timer +// +void CProviderTCP6::RetransmitTimeout() + { + LOG(Log::Printf(_L("\ttcp SAP[%u] RetransmitTimeout(): backoff=%d"), (TInt)this, iBackoff)); + + TUint maxRetries = InState(ETcpSynSent|ETcpSynReceived) + ? Protocol()->SynRetries() : Protocol()->Retries2(); + + // Handle backoff and expiration + if (iSND.NXT > iSND.UNA || iSND.WND == 0) + { + // + // Exponential backoff. + // + ++iBackoff; + if (iRTO < Protocol()->MaxRTO()) // Avoid RTO overflow + ResetRTO(); + + // + // Timeout? + // + // Note: we time out if this is a connect attempt or a retransmission, + // but we must not time out if we're probing a zero window. + // Exception: probing timeouts if application has closed and we have zero window + // + if ((iBackoff > maxRetries) && + (iSND.WND > 0 + || InState(ETcpSynSent|ETcpSynReceived) + || (iSockFlags.iSendClose && iSockFlags.iRecvClose))) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] RetransmitTimeout(): TCP timed out"), (TInt)this)); + ErrorExpire(iLastError.iStatus != KErrNone ? iLastError.iStatus : KErrTimedOut); + return; + } + // + // Simple black hole detection for PMTUD. Currently, we never try to re-enable + // PMTUD after hitting a black hole. + // + if (iBackoff > Protocol()->Retries1() && iSND.WND > 0) + { + iFlags.iDoPMTUD = EFalse; + iSMSS = Min(iSMSS, KTcpStandardMSS); + } + } + + else if (iFlags.iHaveKeepAlive && !iSockOutQLen && InState(ETcpEstablished | ETcpCloseWait)) + { + KeepAliveTimeout(); + return; + } + + // TIME-WAIT or FIN-WAIT-2 timeout + if (InState(ETcpTimeWait) || (InState(ETcpFinWait2) && iSockFlags.iRecvClose)) + { + Expire(); + return; + } + + // Not dead yet. Go retransmit some segments. + RetransmitSegments(); + } + + +void CProviderTCP6::KeepAliveTimeout() + { + // Keepalive timer expired. Because 32-bit microseconds are not enough for the minimum keepalive + // timeout of two hours, the keepalive interval needs to be consumed one hour at a time. + + ASSERT(iRetransTimer); + + if (iBackoff >= Protocol()->NumKeepAlives()) + { + // No response from the peer. Terminate connection + LOG(Log::Printf(_L("\ttcp SAP[%u] KeepAliveTimeout(): No response to Keep-Alive probes. Closing connection"), (TInt)this)); + ErrorExpire(KErrTimedOut); + return; + } + + TUint32 usec = TimeStamp(); + + if (!iLastTimeout) + iLastTimeout = usec; + + TUint32 distance = (usec - iLastTimeout) / KOneSecondUs; // seconds + TUint32 interval = iBackoff ? Protocol()->KeepAliveRxmt() : Protocol()->KeepAliveIntv(); + + if (distance > interval) + { + // Send a keepalive probe. If no ack arrives, next one is sent after a shorter time (75 s) + LOG(Log::Printf(_L("\ttcp SAP[%u] KeepAliveTimeout(): Sending a Keep-Alive probe"), (TInt)this)); + SendSegment(KTcpCtlACK, iSND.UNA - 1, 0); + iBackoff++; + iRetransTimer->Restart(Protocol()->KeepAliveRxmt() * KOneSecondUs); + } + else + { + // This branch is entered when the first keepalive has to be issued after an idle period. + distance = Protocol()->KeepAliveIntv() - distance; + iRetransTimer->Restart((distance > 1800) ? + 1800 * KOneSecondUs : (distance * KOneSecondUs)); + } + } + + +void CProviderTCP6::ResetKeepAlives() + { + ASSERT(iRetransTimer); + iRetransTimer->Restart((Protocol()->KeepAliveIntv() > 1800) ? + 1800 * KOneSecondUs : (Protocol()->KeepAliveIntv() * KOneSecondUs)); + // Backoff is used for counting unacknowledged keepalive retransmissions during idle periods + iBackoff = 0; + iLastTimeout = TimeStamp(); + } + +inline TBool CProviderTCP6::CanTriggerKeepAlive() + { + // + // We can only send keep-alive if we're idle and established. + // + return iFlags.iHaveTriggeredKeepAlive + && !iSockOutQLen + && iSendQ.IsEmpty() + && InState(ETcpEstablished) + && iSND.NXT == iSND.UNA + && iSND.WND > 0; + } + +// +// Try to retransmit segments. This routine gets called from two places: +// - directly from RetransmitTimeout() +// - from CanSend(), in which case this is a delayed retransmission timeout +// +void CProviderTCP6::RetransmitSegments() + { + ASSERT(iRetransTimer); + + LOG(Log::Printf(_L("\ttcp SAP[%u] RetransmitSegments(): queue=%u wnd=%u cwnd=%u, ssthresh=%u dupacks=%d"), + (TInt)this, iSockOutQLen, iSND.WND, iCwnd, iSsthresh, iDupAcks)); + + TInt unacked = Min(iSND.NXT - iSND.UNA, iSockOutQLen); // Adjust for FIN + TInt effMSS = EffectiveMSS(); + + // Delay retransmission if the flow is pending + iFlags.iRetransmitPending = (iFlow.Status() == EFlow_PENDING); + if (iFlags.iRetransmitPending) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] RetransmitSegments(): Flow pending"), (TInt)this)); + ReSchedRetransmit(); + return; + } + + // + // Handle retransmission of data segments and zero window probing. + // We must have something in output queue. SYN or FIN retransmissions + // and TIME-WAIT or FIN-WAIT-2 timeouts are handled later. + // + if (iSockOutQLen && InState(ETcpEstablished|ETcpFinWait1|ETcpClosing|ETcpCloseWait|ETcpLastAck)) + { + if (iSND.WND == 0) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] RetransmitSegments(): Window PROBE"), (TInt)this)); + + // + // We are probing a zero window. + // + switch (Protocol()->ProbeStyle()) + { + case 1: + // Probe with a full segment (BSD style) + SendSegment(KTcpCtlACK, iSND.UNA, Min(iSockOutQLen - unacked, effMSS)); + break; + + case 2: + // + // Probe with a below-window ACK (Linux style). + // + // Note: SendSegment() does not allow sending an above-window ACK. + // Below-window ACK is fine, though. + // + // Note 2: If we have urgent data to send, we fall back on standard probe. + // since a pure ack can not deliver the urgent pointer. + // + if (iSND.UP <= iSND.UNA) + { + SendSegment(KTcpCtlACK, iSND.UNA - 1); + SchedRetransmit(); + break; + } + // Fall through + + default: + // Standard probe. We use a single byte to probe a zero window. + SendSegment(KTcpCtlACK, iSND.UNA, 1); + } + // + // Note 1: With probes including data iSND.NXT will now be pointing beyond + // the advertised window of the receiver! This will allow us to accept the + // incoming probe ack without any special hacks. However, it also means + // that if we send an ACK or RST, we need to make sure the sequence number + // is within the receiver window! SendSegment() now makes this adjustment + // as a special case. + // + // Note 2: We may also end up with iSND.NXT pointing beyond the window if + // the receiver suddenly shrinks its window. The current solution covers + // both cases. + // + return; + } + + // + // This is a retransmit timout. Do we have anything to do? + // + if (!unacked) + return; + + LOG(if (iFlags.iFastRetransMode) Log::Printf(_L("\ttcp SAP[%u] RetransmitSegments(): Leaving FAST RETRANS mode"), (TInt)this)); + iFlags.iFastRetransMode = EFalse; + iDupAcks = 0; + + // + // Congestion control + // + iSsthresh = Max((iSND.NXT - iSND.UNA) / 2, 2 * iSMSS); + iCwnd = EffectiveMSS(); + iLwnd = 0; + iRetranData = 0; + iPartialSeq = iSND.UNA; + + // + // The receiver may have reneged. Clear SACK info. + // + // Only clear the SACK queue if we have a SACKed block immediately + // after iSND.UNA. In this case the sender has clearly reneged. + // If the peer does this even once, we will no longer trust it + // to retain its above-sequence queue and will revert to the normal + // RFC2018 behaviour of clearing all SACK info on retransmit timeout. + // + if (!iFlags.iSackOk || (iFlags.iPeerHasReneged || + (iSacked.Count() && iSacked.First()->iLeft == iSND.UNA))) + { + if (iFlags.iSackOk) + { + LOG(if(!iFlags.iPeerHasReneged) Log::Printf(_L("\ttcp SAP[%u] RetransmitSegments(): Peer reneged"), (TInt)this)); + iFlags.iPeerHasReneged = ETrue; + iSacked.Clear(); + } + + // + // New reno "bugfix" [RFC2582] is applied only if we are forced + // to discard the SACK info. If we can keep the info we will + // never send spurious retransmits and thus there will be no + // dupacks to trigger fast recovery, unless segments have + // really been lost. + // + iSendHigh = iSND.NXT; + } + + iRealSendHigh = iSND.NXT; + + // Save timestamp for delay spike detection + if (iFlags.iUseTimeStamps) + iLastTimeout = TimeStamp(); + + // + // Retransmit segments. + // + if (Protocol()->FRTO()) + { + // F-RTO: Send first unacknowledged segment and continue transmitting new data + iFRTOsent = 1; + SendDataSegment(iSND.UNA, ETrue); + } + else + { + // Normal retransmit + iTransmitSeq = iSND.UNA; + SendSegments(ETrue); + } + + // Store retransmit sequence for SACK retransmit + if (iTransmitSeq > iSendHigh && !iFRTOsent) + iSendHigh = iTransmitSeq; + + // If the server doesn't respond because of broken NAT/FW or other, don't keep interface up. + if (InState(ETcpFinWait1|ETcpClosing|ETcpLastAck)) + DetachIfDead(); + return; + } + + // + // Ok, this is either a SYN/FIN retransmit or a TIME-WAIT/FIN-WAIT-2 timeout. + // + if (InState(ETcpSynSent)) + { + // Retransmit SYN + SendSegment(KTcpCtlSYN, iSND.UNA); + return; + } + + if (InState(ETcpSynReceived)) + { + // Retransmit SYN,ACK + SendSegment(KTcpCtlSYN|KTcpCtlACK, iSND.UNA); + return; + } + + if (InState(ETcpFinWait1|ETcpClosing|ETcpLastAck)) + { + // If the server doesn't respond because of broken NAT/FW or other, don't keep interface up. + DetachIfDead(); + // Retransmit FIN + SendSegment(KTcpCtlFIN|KTcpCtlACK, iSND.UNA); + return; + } + + LOG(Log::Printf(_L("\ttcp SAP[%u] RetransmitSegments(): Retransmitter stopping"), (TInt)this)); + if (!iSockFlags.iAttached) + Expire(); + return; + } + + +// +// Respond to an explicit congestion control signal. +// Currently, the signal can come from the link layer +// or from the network as an ICMP source quench. +// +TBool CProviderTCP6::SourceQuench() + { + // + // Allow source quenching approximately once per window. + // Note: the test is written in such a way that iQuenchSeq + // does not need to be updated during normal TCP operation. + // + // Do not shrink the congestion window if we're doing fast + // retransmits. That would mess up the congestion window + // deflation when exiting fast retransmit mode. + // + if (iQuenchSeq.Outside(iSND.NXT, iSND.NXT + iSND.WND) && !iFlags.iFastRetransMode) + { + iSsthresh = Max((iSND.NXT - iSND.UNA) / 2, 2 * iSMSS); + iCwnd = iSsthresh; + iQuenchSeq = iSND.NXT + iSND.WND; + LOG(Log::Printf(_L("\ttcp SAP[%u] SourceQuench(): flight=%d, cwnd=%d, ssthresh=%d"), + (TInt)this, iSND.NXT - iSND.UNA, iCwnd, iSsthresh)); + return ETrue; + } + return EFalse; + } + + +TInt CProviderTCP6::SenderCallBack(TAny* aProviderTCP) + { + LOG(Log::Printf(_L("<>\ttcp SAP[%u] SenderCallBack"), (TInt)aProviderTCP)); + ((CProviderTCP6*)aProviderTCP)->SendSegments(); + return 0; + } + +TInt CProviderTCP6::ReceiverCallBack(TAny* aProviderTCP) + { + LOG(Log::Printf(_L("<>\ttcp SAP[%u] ReceiverCallBack"), (TInt)aProviderTCP)); + ((CProviderTCP6*)aProviderTCP)->ProcessSegments(); + return 0; + } + +TInt CProviderTCP6::DelayAckCallBack(TAny* aProviderTCP) + { + LOG(Log::Printf(_L("<>\ttcp SAP[%u] DelayAckCallBack"), (TInt)aProviderTCP)); + ((CProviderTCP6*)aProviderTCP)->SendSegment(KTcpCtlACK); + return 0; + } + +TInt CProviderTCP6::TransmitterCallBack(TAny* aProviderTCP) + { + LOG(Log::Printf(_L("<>\ttcp SAP[%u] TransmitterCallBack"), (TInt)aProviderTCP)); + ((CProviderTCP6*)aProviderTCP)->Transmit(); + return 0; + } + +TInt CProviderTCP6::RetransmitterCallBack(TAny* aProviderTCP) + { + LOG(Log::Printf(_L("<>\ttcp SAP[%u] RetransmitterCallback"), (TInt)aProviderTCP)); + ((CProviderTCP6*)aProviderTCP)->RetransmitTimeout(); + return 0; + } + + +TInt CProviderTCP6::LingerTimerCallBack(TAny *aProviderTCP) + { + LOG(Log::Printf(_L("<>\ttcp SAP[%u] Linger timeout()"), (TInt)aProviderTCP)); + ((CProviderTCP6*)aProviderTCP)->Detach(); + return 0; + } + + +// +// Initialize SRTT measurement +// +void CProviderTCP6::ClearRTT() + { + iSRTT = 0; + iMDEV = Protocol()->MdevSmooth() * Protocol()->InitialRTO(); + if (Protocol()->RTO_K()) + iMDEV /= Protocol()->RTO_K(); + } + + +// +// Update RTO using van Jacobson's algorithm +// +void CProviderTCP6::UpdateRTO(TUint32 aRTT) + { + if (!iSRTT && aRTT > Protocol()->InitialRTO() / 2) + { + iSRTT = Protocol()->SrttSmooth() * aRTT; + iMDEV = Protocol()->MdevSmooth() * aRTT / 2; + } + else + { + TInt delta = aRTT - (iSRTT / Protocol()->SrttSmooth()); // delta >= -iSRTT + iSRTT += delta; + if (!iSRTT) + iSRTT = 1; + if (delta < 0) + delta = -delta; + delta -= (iMDEV / Protocol()->MdevSmooth()); // delta >= -iMDEV + iMDEV += delta; + } + + ResetRTO(); + LOG(Log::Printf(_L("\ttcp SAP[%u] UpdateRTO(): RTT=%d SRTT=%d MDEV=%d BACKOFF=%d RTO=%d"), + (TInt)this, aRTT, iSRTT / Protocol()->SrttSmooth(), iMDEV / Protocol()->MdevSmooth(), iBackoff, iRTO)); + } + +// +// Calculate RTO with backoff +// +void CProviderTCP6::ResetRTO() + { + iRTO = (iSRTT / Protocol()->SrttSmooth()) + + Max(Protocol()->RTO_K() * iMDEV / Protocol()->MdevSmooth(), Protocol()->RTO_G()); + if (iRTO < Protocol()->MinRTO()) + iRTO = Protocol()->MinRTO(); + iRTO <<= iBackoff; + if (iRTO > Protocol()->MaxRTO()) + iRTO = Protocol()->MaxRTO(); + LOG(Log::Printf(_L("\ttcp SAP[%u] ResetRTO(): SRTT=%d MDEV=%d BACKOFF=%d RTO=%d"), + (TInt)this, iSRTT / Protocol()->SrttSmooth(), iMDEV / Protocol()->MdevSmooth(), iBackoff, iRTO)); + } + +// +// Set initial congestion window +// +void CProviderTCP6::ResetCwnd(TUint aSMSS) + { + if (Protocol()->RFC2414()) + iCwnd = Min(4 * aSMSS, Max(2 * aSMSS, 4380)); + else + iCwnd = Protocol()->InitialCwnd() * aSMSS; + LOG(Log::Printf(_L("\ttcp SAP[%u] ResetCwnd(): cwnd=%d"), (TInt)this, iCwnd)); + } + + +// +// Schedule a FIN-WAIT-2 or TIME-WAIT timeout +// +void CProviderTCP6::SchedMsl2Wait() + { + ASSERT(iRetransTimer); + + iBackoff = 0; + iRetransTimer->Restart(Protocol()->Msl2Delay()); + + // + // Remove this SAP from the user socket count. + // + DetachFromInterface(); + } + +// +// Detach the SAP if the application has closed and we seem to keep resending stuff. +// +void CProviderTCP6::DetachIfDead() + { + if (iSockFlags.iRecvClose && iSockFlags.iSendClose && Protocol()->FinPersistency() + && iBackoff >= Protocol()->FinPersistency()) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] Peer looks dead while %d bytes unacked data. Detach!"), + (TInt)this, Min(iSND.NXT - iSND.UNA, iSockOutQLen))); + DetachFromInterface(); + } + } + +void CProviderTCP6::DetachFromInterface() +/** +Removes this SAP from the user socket count and interface flow count. +This allows link layer and TCP/IP stack go down if no one else uses it. +*/ + { + const TInt off = 0; + SetOption(KSolInetIp, KSoUserSocket, TPtr8((TUint8*)&off, sizeof(TInt), sizeof(TInt))); + SetOption(KSolInetIp, KSoKeepInterfaceUp, TPtr8((TUint8*)&off, sizeof(TInt), sizeof(TInt))); + } + + +// +// Check ioctl completion +// +void CProviderTCP6::CompleteIoctl(TInt aError) + { + if (iFlags.iDataSentIoctl && iSockFlags.iNotify) + { + if (!iSockOutQLen) + { + iFlags.iDataSentIoctl = EFalse; + iSocket->IoctlComplete(0); + } + else if (aError != KErrNone) + { + iFlags.iDataSentIoctl = EFalse; + Error(KErrCancel, MSocketNotify::EErrorIoctl); + } + } + } + +// +// Main incoming segment processing loop +// +void CProviderTCP6::ProcessSegments() + { + LOG(Log::Printf(_L("\ttcp SAP[%u] ProcessSegments()"), (TInt)this)); + + ASSERT(iRetransTimer); + + RMBufRecvPacket packet; + TBool immediateAck = EFalse; + + while (iRecvQ.Remove(packet)) + { + RMBufRecvInfo* info = packet.Unpack(); + TIpHeader *ip = ((RMBufPacketPeek &)packet).GetIpHeader(); + TTcpPacket seg(packet, info->iOffset); + TTcpOptions opt; + seg.iHdr->Options(opt); + TInt len = packet.Length() - seg.iHdr->HeaderLength() - info->iOffset; + TTcpSeqNum seq = seg.iHdr->Sequence(); + TTcpSeqNum ack = seg.iHdr->ACK() ? seg.iHdr->Acknowledgment() : TTcpSeqNum(0); + TInt fin = seg.iHdr->FIN(); // Save this for FIN processing + CProviderTCP6 *nSAP = NULL; + TTcpSeqNum rcvNxt = iRCV.NXT; // Used in SYN_RECEIVED state + + // Get current time + TUint32 usec = TimeStamp(); + + if (InState(ETcpListen)) + { + TUint32 tsEcr; + + if (!iParent) + { + // + // Server socket LISTEN state processing. + // + + // Sanity checks. + if (iConnectCount >= iListenQueueSize || seg.iHdr->RST()) + goto discard; + + // Check invalid address. + if (!TInetAddr::Cast(info->iSrcAddr).IsUnicast() || + !TInetAddr::Cast(info->iDstAddr).IsUnicast()) + goto discard; + + if (IsLandAttack(info)) + goto discard; + + if (seg.iHdr->ACK()) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] ProcessSegments(): ACK received in LISTEN state. Sending RST"), (TInt)this)); + SendReset(ack, info->iDstAddr, info->iSrcAddr); + goto discard; + } + + if (!seg.iHdr->SYN()) + goto discard; + + if (opt.Error()) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] ProcessSegments(): Invalid TCP options"), (TInt)this)); + SendResetNoSync(seq+len+1, info->iDstAddr, info->iSrcAddr); + goto discard; + } + + if (opt.MSS() > 0 && opt.MSS() < STATIC_CAST(TInt, KTcpMinimumMSS)) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] ProcessSegments(): Invalid MSS option"), (TInt)this)); + SendResetNoSync(seq+len+1, info->iDstAddr, info->iSrcAddr); + goto discard; + } + + // + // Sometimes SYN retransmissions get compressed in the network + // and get queued in the listen socket. If this happens we just + // punt the SYN packet to the correct SAP. + // +#ifndef SYMBIAN_STRICT_EXPLICIT_SOCKET_BINDING + nSAP = (CProviderTCP6*)Protocol()->LocateSap(EMatchConnection, KAFUnspec, + info->iDstAddr, info->iSrcAddr); +#else + nSAP = (CProviderTCP6*)Protocol()->LocateSap(EMatchConnection, KAFUnspec, + info->iDstAddr, info->iSrcAddr, NULL, info->iInterfaceIndex); +#endif //SYMBIAN_STRICT_EXPLICIT_SOCKET_BINDING + if (nSAP == NULL) + { + // Create a new SAP and initialize it + if (CreateChild(nSAP) != KErrNone) + goto discard; + +#ifdef SYMBIAN_NETWORKING_UPS + nSAP->iConnectionInfoReceived = 1; +#endif + RFlowContext flowCtxt = nSAP->iFlow; + flowCtxt.SetLocalAddr(info->iDstAddr); + flowCtxt.SetRemoteAddr(info->iSrcAddr); + nSAP->EnterState(ETcpListen); + + // Create a flow context + TInt status = flowCtxt.Connect(); + if (status < KErrNone) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] ProcessSYN(): Error %d opening flow context"), (TInt)this, status)); + delete nSAP; + SendResetNoSync(seq+len+1, info->iDstAddr, info->iSrcAddr); + goto discard; + } + + // Disable on-demand interface setup for the child + flowCtxt.FlowContext()->iInfo.iNoInterfaceUp = 1; + nSAP->iSockFlags.iConnected = ETrue; + Protocol()->BindProvider(nSAP); + } + + // Get the new SAP started. It will handle the rest, + packet.Pack(); + nSAP->Process(packet); + continue; + } + + + // + // Child socket LISTEN state processing. + // + + /* + Set RCV.NXT to SEG.SEQ+1, IRS is set to SEG.SEQ and any other + control or text should be queued for processing later. ISS + should be selected and a SYN segment sent of the form: + + + + SND.NXT is set to ISS+1 and SND.UNA to ISS. The connection + state should be changed to SYN-RECEIVED. Note that any other + incoming control or data (combined with SYN) will be processed + in the SYN-RECEIVED state, but processing of SYN and ACK should + not be repeated. If the listen was not fully specified (i.e., + the foreign socket was not fully specified), then the + unspecified fields should be filled in now. + */ + + // Peer groks timestamps? + iFlags.iUseTimeStamps &= opt.TimeStamps(iTsRecent, tsEcr); + if (!iFlags.iUseTimeStamps) + iOptions.ClearTimeStamps(); + + // Peer groks SACK? + iFlags.iSackOk &= opt.SackOk(); + if (!iFlags.iSackOk) + iOptions.ClearSackOk(); + + // Peer denies ECN + if (!seg.iHdr->ECE() || !seg.iHdr->CWR()) + { + iFlags.iEcn = EFalse; + } + + // Note: the parent does error checking before instantiating a child socket. + if (opt.MSS() >= 0) + iSMSS = opt.MSS(); + + // + // The following is based on network interface MTU. + // We limit the advertised MSS to window size divided by four, + // in case the interface supportes a large (or infinite) MTU. + // + iRMSS = Min(LinkRMSS(), iSockInBufSize / 2); + iOptions.SetMSS(iRMSS); + + // Initialise send sequence number + iTransmitSeq = iSND.NXT = Protocol()->RandomSequence(); + + if (!opt.WindowScale() || Protocol()->WinScale() == -1) + { + iRcvWscale = 0; + iOptions.SetWindowScale(0); + iSockInBufSize = Min(iSockInBufSize, 0xffff); + } + else + { + iSndWscale = opt.WindowScale() - 1; + iRcvWscale = (Protocol()->WinScale() > 0 ? + Protocol()->WinScale() - 1 : NeedWindowScale()); + iOptions.SetWindowScale((TUint8)(iRcvWscale + 1)); + } + + // Initialize receiver sequence and window + iFreeWindow = iSockInBufSize % iRMSS; + iRCV.WND = iSockInBufSize - iFreeWindow; + iRCV.NXT = seg.iHdr->Sequence() + 1; + + TUint8 flags = KTcpCtlSYN|KTcpCtlACK; + if (iFlags.iEcn) + { + flags |= KTcpCtlECE; + } + + // Do not kick idle timers until we are in established state + StoreKeepInterfaceUp(); + iFlow.FlowContext()->SetOption(KSolInetIp, KSoKeepInterfaceUp, KInetOptionDisable); + + // Send SYN-ACK + iSND.UNA = iSND.NXT; + SendSegment(flags); + ++iTransmitSeq; + + // Make sure we don't miss our window update. + iSND.WL1 = iRCV.NXT; + iSND.WL2 = iSND.NXT; + + iPartialSeq = iSND.UNA; + + EnterState(ETcpSynReceived); + goto accept; + } + + if (InState(ETcpSynSent)) + { + if (seg.iHdr->ACK() && ack.Outside(iSND.UNA, iSND.NXT)) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] ProcessSegments(): Invalid ack=%u"), (TInt)this, ack.Uint32())); + if (!seg.iHdr->RST()) + SendReset(ack); + goto discard; + } + + if (seg.iHdr->RST()) + { + if (seg.iHdr->ACK()) // No need to check ack sequence because it was tested above + { + LOG(Log::Printf(_L("\ttcp SAP[%u] ProcessSegments(): RST packet"), (TInt)this)); + Error(KErrCouldNotConnect, MSocketNotify::EErrorConnect); + EnterState(ETcpClosed); + goto wrapup; + } + else + goto discard; + } + + if (!seg.iHdr->SYN()) + goto discard; + + // Process options for SYN packet. + if (opt.Error()) + { + // In case we receive invalid optons, we should just report an error and reset the connection. + LOG(Log::Printf(_L("\ttcp SAP[%u] ProcessSegments(): Invalid options, reset connection"), (TInt)this)); + if (seg.iHdr->ACK()) + SendReset(ack); + else + SendResetNoSync(seq+len+1); + Error(KErrCouldNotConnect, MSocketNotify::EErrorConnect); + EnterState(ETcpClosed); + goto wrapup; + } + + if (opt.MSS() > 0) + { + if (opt.MSS() < STATIC_CAST(TInt, KTcpMinimumMSS)) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] ProcessSegments(): Invalid MSS option"), (TInt)this)); + if (seg.iHdr->ACK()) + SendReset(ack); + else + SendResetNoSync(seq+len+1); + goto discard; + } + iSMSS = opt.MSS(); + } + + iFlags.iUseTimeStamps &= opt.TimeStamps(); + iFlags.iSackOk &= opt.SackOk(); + + if (opt.WindowScale()) + { + iSndWscale = opt.WindowScale() - 1; + iFreeWindow = iSockInBufSize % iRMSS; + iRCV.WND = iSockInBufSize - iFreeWindow; + } + else + { + // iFreeWindow and iRCV.WND were confirmed to be less than 0xffff with SYN + iRcvWscale = 0; + iSockInBufSize = Min(iSockInBufSize, 0xffff); + } + + iRCV.NXT = seq+1; + + // Our SYN was acked? + if (seg.iHdr->ACK() && ack == iSND.NXT) + { + iSND.UNA = ack; + iPartialSeq = ack; + + TUint32 tsEcr; + // Check that the timestamp echo has sane values + if (iFlags.iUseTimeStamps && opt.TimeStamps(iTsRecent, tsEcr) + && tsEcr && usec > tsEcr && usec - tsEcr <= Protocol()->MaxRTO()) + UpdateRTO(usec - tsEcr); + else if (iFlags.iTiming) + UpdateRTO(usec - iTimeStamp); + iFlags.iTiming = EFalse; + + // + // Initial window update as per RFC1122. + // + //LOG(Log::Printf(_L("CProviderTCP6::ProcessSegments(): Window update.\r\n"))); + iSND.WND = seg.iHdr->Window(); // no scaling in SYN + iSND.WL1 = seq; + iSND.WL2 = ack; + iLastWnd = seg.iHdr->Window(); // no scaling in SYN + + ResetCwnd(iSMSS); + iSsthresh = KMaxTInt; + + ClearSYNSettings(); + + // Peer denies ECN with SYN-ACK. + if (!seg.iHdr->ECE()) + { + iFlags.iEcn = EFalse; + } + + ReadDestinationCache(); + + // Open up the window for sender + SendSegment(KTcpCtlACK); + EnterState(ETcpEstablished); + //__ASSERT_DEBUG(iSockFlags.iNotify, User::Panic(_L("notifier"),0)); + // Assert removed. There's a rare case when the stack is unloading. -MikaL + + // if ECN negotiation was succesful, tell IP layer (iface.cpp) that ECT bit can be enabled. + if (iFlags.iEcn) + { + SetEcn(Protocol()->Ecn()); + } + + if (iSockFlags.iNotify) + iSocket->ConnectComplete(); + + // SYN uses one slot in sequence number space. Account for it before processing data. + ++seq; + + goto process_data; + } + + // + // Simultaneous open, send SYN+ACK + // + --iSND.NXT; + + // Peer denies ECN in SYN + if (!seg.iHdr->ECE() || !seg.iHdr->CWR()) + { + iFlags.iEcn = EFalse; + } + + TUint8 flags = KTcpCtlSYN|KTcpCtlACK; + if (iFlags.iEcn) + { + flags |= KTcpCtlECE; + } + SendSegment(flags); + EnterState(ETcpSynReceived); + // + // According to RFC793 we should queue any additional data + // and controls (e.g. URG) for processing once the established + // state has been reached. + // + // No current TCP implementations actually do this (aside from + // T/TCP), because of its vulnerability to DoS attacks. In any + // case, we're compatible with a peer that wants to send data with + // the SYN packet, since the peer will retransmit the data after + // we only ack the SYN. + // + goto accept; + } + + if (!InState(ETcpSynReceived|ETcpEstablished|ETcpFinWait1|ETcpFinWait2| + ETcpCloseWait|ETcpClosing|ETcpLastAck|ETcpTimeWait)) + { + if (InState(ETcpClosed)) + { + // We have extra segments after entering CLOSED state. Send RST. + if (seg.iHdr->ACK()) + SendReset(ack, info->iDstAddr, info->iSrcAddr); + else + SendResetNoSync(seq+len, info->iDstAddr, info->iSrcAddr); + } + goto wrapup; + } + +/* + Four cases From RFC793 to test in order to determine whether + an arriving segment is acceptable: + + Segment Receive Test + Length Window + ------- ------- ------------------------------------------- + 0 0 SEG.SEQ = RCV.NXT + 0 >0 RCV.NXT =< SEG.SEQ < RCV.NXT+RCV.WND + >0 0 not acceptable + >0 >0 RCV.NXT =< SEG.SEQ < RCV.NXT+RCV.WND + or RCV.NXT =< SEG.SEQ+SEG.LEN-1 < RCV.NXT+RCV.WND + + We transform these into two basic tests, both of which must evaluate + true in order to DISCARD a segment. The first test clears the majority + of segments that are correctly within receive window. The second test + clears special cases where receive window is zero or the incoming segment + is only partially within receive window. + + Note that we accept a segment that is straddling iRCV.NXT even if + the receive window is zero. In this case the segment is discarded + later during data segment processing. + */ + + // Eliminate earlier DSACK blocks. One duplicate segment can be reported only once. + if (iFlags.iSackOk && Protocol()->DSack()) + iOptions.SackBlocks().Prune(iRCV.NXT); + + if (InState(ETcpSynReceived)) + { + // In case of simultanous connections TCP retransmits SYN with initial sequence + // number when it gets incoming SYN in SYN_SENT state + // (it acks received SYN+1 in that case). + // Since RCV.NXT is increased after receiving the first SYN, the SYN retransmission + // would appear as out-of-order segment, in which case it would be ignored and the TCP + // session would never get to ESTABLISHED state. + // + // This adjustment is meant to avoid the above problem while causing minimal compromise + // to TCP sequence number check safety. + rcvNxt = iRCV.NXT - 1; + } + + // if ((seq < iRCV.NXT || seq >= iRCV.NXT + iRCV.WND) && // 1st test + // ((iRCV.WND > 0) ? (seq + len <= iRCV.NXT) : (len > 0 || seq > iRCV.NXT))) // 2nd test + // PeLu: The above test passed case where seq > nxt+wnd + if ((seq < rcvNxt || seq >= iRCV.NXT + iRCV.WND) && // 1st test + ((iRCV.WND > 0) ? ((len > 0) ? (seq+len <= iRCV.NXT) || (seq+len > iRCV.NXT + iRCV.WND) : 1) + : ((len > 0) ? 1 : (seq.Outside(rcvNxt, iRCV.NXT))))) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] ProcessSegments(): Segment out of window"), (TInt)this)); + if (!seg.iHdr->RST()) + { + // if receiving data partially below RCV.NXT, + // generate DSACK only for the duplicate portion + if (iFlags.iSackOk && Protocol()->DSack() && seq < iRCV.NXT && len > 0) + { + if (seq + len <= iRCV.NXT) + { + iOptions.SackBlocks().AddUnordered(seq, seq + len); + } + else + { + iOptions.SackBlocks().AddUnordered(seq, iRCV.NXT); + } + iOptions.SackBlocks().Limit(4); + } + SendSegment(KTcpCtlACK); + } + if (InState(ETcpTimeWait|ETcpFinWait2)) + SchedMsl2Wait(); + // Accept RST segments that fit within the last advertised window. + if (seg.iHdr->RST() && !seq.Outside(iLastAck, iLastAck + iRCV.WND)) + LOG(Log::Printf(_L("\ttcp SAP[%u] ProcessSegments(): Lagged RST"), (TInt)this)); + else if (iRCV.WND > 0 || iRCV.NXT.Outside(seq, seq+len)) + goto discard; + } + + if (seg.iHdr->RST()) + { + // Ignore RST in TIME-WAIT state to prevent TIME-WAIT assassination + if (InState(ETcpTimeWait)) + goto discard; + + // Stop processing and enter CLOSED state + LOG(Log::Printf(_L("\ttcp SAP[%u] ProcessSegments(): RST packet"), (TInt)this)); + Error(KErrDisconnected); + EnterState(ETcpClosed); + goto wrapup; + } + + if (seg.iHdr->SYN() && seq >= iRCV.NXT) + { + /* If the SYN is in the window it is an error, send a reset, any + outstanding RECEIVEs and SEND should receive "reset" responses, + all segment queues should be flushed, the user should also + receive an unsolicited general "connection reset" signal, enter + the CLOSED state, delete the TCB, and return. + + Note: the extra test against RCV.NXT is there to let through a SYN-ACK + with the sequence number RCV.NXT-1. This may happen in a simultaneous + connect case, where the SYN-ACK packet is retransmitted. The SYN-ACK + will be hanled below as part of SYN-RECEIVED state processing. + + Otherwise, if the SYN is not in the window this step would not be + reached and an ack would have been sent in the first step (sequence + number check). + */ + SendReset(iSND.NXT); + Error(KErrDisconnected); + EnterState(ETcpClosed); + goto wrapup; + } + + + // + // ACK PROCESSING + // + if (!seg.iHdr->ACK()) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] ProcessSegments(): No ACK"), (TInt)this)); + goto discard; + } + + // Check options + if (opt.Error()) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] ProcessSegments(): Invalid options"), (TInt)this)); + SendReset(ack); + goto discard; + } + + if (InState(ETcpSynReceived)) + if(ack.Inside(iSND.UNA, iSND.NXT)) + { + EnterState(ETcpEstablished); + + const TInt ifup = iFlags.iKeepInterfaceUp; + SetOption(KSolInetIp, KSoKeepInterfaceUp, TPtr8((TUint8*)&ifup, sizeof(TInt), sizeof(TInt))); + + // if ECN negotiation was succesful, tell IP layer that ECT bit can be enabled. + if (iFlags.iEcn) + { + SetEcn(Protocol()->Ecn()); + } + + // + // Initial window update as per RFC1122. + // + //LOG(Log::Printf(_L("CProviderTCP6::ProcessSegments(): Window update.\r\n"))); + iSND.WND = seg.iHdr->Window() << iSndWscale; + iSND.WL1 = seq; + iSND.WL2 = ack; + iLastWnd = seg.iHdr->Window() << iSndWscale; + + ResetCwnd(iSMSS); + iCwnd -= iSMSS; // Compensate for cwnd increase below during ack processing + iSsthresh = KMaxTInt; + + ClearSYNSettings(); + + ReadDestinationCache(); + + // The following causes the socket server to (eventually) call Start() + if (iParent) + { + iSockFlags.iAttached = ETrue; + ASSERT(iParent->iSockFlags.iNotify); + if (iParent->CompleteChildConnect(this) == KErrAbort) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] ProcessSegments(): SAP Deleted, abort ProcessSegments()"), (TInt)this)); + // Object in use was deleted. Just free the packet and return. + packet.Free(); + return; + } + } + else + { + //__ASSERT_DEBUG(iSockFlags.iNotify, User::Panic(_L("notifier"), 0)); + // Assert removed. There's a rare case when the stack is unloading. -MikaL + if (iSockFlags.iNotify) + iSocket->ConnectComplete(); + } + } + else + { + LOG(Log::Printf(_L("\ttcp SAP[%u] ProcessSegments(): Bad SYN-ACK"), (TInt)this)); + SendReset(ack); + goto discard; + } + + // + // Check ack sequence for all remaining states + // + if(ack > iSND.NXT) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] ProcessSegments(): ACK above window"), (TInt)this)); + SendSegment(KTcpCtlACK); + goto discard; + } + + // A good packet arrived and the connection is idle, reset the keep-alives state + if (CanFireKeepAlives()) + ResetKeepAlives(); + + // Check for ECN congestion established bit. + if (info->iVersion == 4) + { + if (iFlags.iEcn && ip->ip4.EcnIsCongestion()) + { + iFlags.iEcnHaveCongestion = ETrue; + LOG(Log::Printf(_L("\ttcp SAP[%u] ProcessSegments(): Have ECN CE bit in IPv4 packet"), (TInt)this)); + } + } + else + { + if (iFlags.iEcn && ip->ip6.EcnIsCongestion()) + { + iFlags.iEcnHaveCongestion = ETrue; + LOG(Log::Printf(_L("\ttcp SAP[%u] ProcessSegments(): Have ECN CE bit in IPv6 packet"), (TInt)this)); + } + } + + if (InState(ETcpEstablished|ETcpFinWait1|ETcpFinWait2|ETcpCloseWait|ETcpClosing|ETcpLastAck)) + { + TInt acked = ack - iSND.UNA; + + if (acked >= 0) + { + // ECN CWR means that receiver has noticed our Congestion Echo. No more congestion. + if (seg.iHdr->CWR()) + { + iFlags.iEcnHaveCongestion = EFalse; + } + + if (iFlags.iUseTimeStamps) + { + TUint32 tsVal, tsEcr; + // Check that tsEcr has sane values + if (opt.TimeStamps(tsVal, tsEcr) && tsEcr && usec - tsEcr <= KTcpMaxRTO) + { + if (acked > 0) + { + UpdateRTO(usec - tsEcr); + + // Got one RTT with timestamps. Don't take another. + if (iFlags.iTiming && ack >= iTimingSeq) + iFlags.iTiming = EFalse; + + // + // Delay spike detection. If the echoed timestamp predates + // our last retransmission timeout we abort the retransmission + // sequence. However, we do not inflate the congestion window + // for two reasons: 1) a long delay might also be a sign of + // congestion, and 2) this feature could potentially be used + // by a hostile peer to artificially inflate our congestion + // window. + // + // PS: Delay spike detection with timestamps is not in use with F-RTO + if (!Protocol()->FRTO() && tsEcr < iLastTimeout && + !opt.SackBlocks().Count()) + { + iTransmitSeq = iSND.NXT; + SpuriousTimeout(acked); + } + } + + // + // The condition in RFC1323 is buggy(?): + // + // SEG.SEQ <= Last.ACK.sent < SEG.SEQ + SEG.LEN + // + // The best current practise is the following, because it updates + // TSrecent also on pure ACKs: + // + // SEG.TSval >= TSrecent and SEG.SEQ <= Last.ACK.sent + // + if (tsVal > iTsRecent && seq <= iLastAck) + iTsRecent = tsVal; + } + } + if (iFlags.iTiming && ack >= iTimingSeq) + { + UpdateRTO(usec - iTimeStamp); + iFlags.iTiming = EFalse; + } + + // SACK book keeping + if (iFlags.iSackOk) + { + // Remove acknowledged blocks + if (opt.SackBlocks().Count()) + { + SequenceBlockQueueIter iter(opt.SackBlocks()); + SequenceBlock *block; + while (block = iter++, block != NULL) + { + // + // Record SACK block but do some sanity checking first. + // + if (ack < block->iLeft && block->iLeft < block->iRight + && block->iRight <= iSND.NXT) + { + iSacked.AddOrdered(block); + } + } + + // Take RTT estimate using SACK info + if (iFlags.iTiming && iSacked.Count() && iSacked.Last()->iRight >= iTimingSeq) + { + UpdateRTO(usec - iTimeStamp); + iFlags.iTiming = EFalse; + } + } + iSacked.Prune(ack); + + // Update iRetranData + if (iSacked.Count() && ack < iSendHigh) + { + iRetranData = iSendHigh - ack; + SequenceBlockQueueIter iter(opt.SackBlocks()); + SequenceBlock *block; + while (block = iter++, block != NULL && block->iLeft < iSendHigh) + { + if (block->iRight < iSendHigh) + iRetranData -= (block->iRight - block->iLeft); + else + iRetranData -= (iSendHigh - block->iLeft); + } + } + else + iRetranData = 0; + + LOG(Log::Printf(_L("\ttcp SAP[%u] ProcessSegments(): acked=%d iRetranData = %d"), + (TInt)this, acked, iRetranData)); + } + + // Did they acknowledge any new data? + if (acked > 0) + { + // Reset retransmit backoff + if (iBackoff && iSND.WND > 0) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] ProcessSegments(): Some data ACKed, clearing Backoff"), (TInt)this)); + iBackoff = 0; + } + + // Trim send queue + iSND.UNA = ack; + if ((TInt)iSockOutQLen > acked) // Avoid barfing on acked SYN or FIN packet + { + iSockOutQ.TrimStart(acked); + iSockOutQLen -= acked; + } + else + { + iSockOutQ.Free(); + iSockOutQLen = 0; + CompleteIoctl(KErrNone); + } + + // Tag along + if (iSND.UP < ack) + iSND.UP = ack - 1; + + // Tag along + if (iPartialSeq < ack) + iPartialSeq = ack; + + // Tag along + if (iSendHigh < ack) + iSendHigh = ack - 1; + + if (iFlags.iFastRetransMode) + { + if (iFlags.iSackOk ? !iSacked.Count() : ack >= iRecoverSeq) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] ProcessSegments(): Leaving FAST RETRANS mode"), (TInt)this)); + iFlags.iFastRetransMode = EFalse; + iDupAcks = 0; + // Deflate congestion window + iCwnd = Min(iSsthresh, iSND.NXT - iSND.UNA + iSMSS); + } + else if (!iFlags.iSackOk) + { + // NewReno partial ACK processing. + + /* From RFC2582: + If this ACK does *not* acknowledge all of the data up to and + including "recover", then this is a partial ACK. In this case, + retransmit the first unacknowledged segment. Deflate the + congestion window by the amount of new data acknowledged, then + add back one MSS and send a new segment if permitted by the new + value of cwnd. This "partial window deflation" attempts to + ensure that, when Fast Recovery eventually ends, approximately + ssthresh amount of data will be outstanding in the network. Do + not exit the Fast Recovery procedure (i.e., if any duplicate ACKs + subsequently arrive, execute Steps 3 and 4 above). + + For the first partial ACK that arrives during Fast Recovery, also + reset the retransmit timer. + */ + + iCwnd -= acked; + iCwnd += iSMSS; + LOG(Log::Printf(_L("\ttcp SAP[%u] ProcessSegments(): FAST RETRANSMIT on PARTIAL ACK"), (TInt)this)); + SendDataSegment(ack); + + // + // Socket write makes use of the duplicate ack count to + // temporarily extend the send buffer during fast recovery. + // We deflate the buffer here. + // + ASSERT(iSMSS); + iDupAcks = Max(iDupAcks - acked / (TInt)iSMSS, 0); + } + } + + // Reset limited transmit window + iLwnd = 0; + + // Everything acked? + if (ack == iSND.NXT) + { + iTransmitSeq = iSND.NXT; + } + else + { + // Restart retransmission timeout + ReSchedRetransmit(); + } + + // + // Adjust congestion window. + // + TUint incr = iSMSS; + + if (iCwnd < iSsthresh) + iCwnd += incr; // Slow-start + else + iCwnd += Max(incr * incr / iCwnd, 1); // Congestion avoidance + } + + else if (ack < iSND.NXT) + + { + // + // Fast retransmit algorithm. + // + // Note! We only reset the duplicate ack count if the received + // segment acknowledges some new data or if a timeout has + // occurred. However, we simply ignore window updates and piggy-back + // acknowledgements unless they also acknowledge new data. Other + // duplicate acknowledgements increase the duplicate ack count and + // may trigger a fast retransmission. + // + if (len == 0 && (seg.iHdr->Window() << iSndWscale) == iLastWnd) + { + if (iFlags.iSackOk) + { + TTcpSeqNum fack = iSacked.Count() ? iSacked.Last()->iRight : iSND.UNA; + + // Acks caused by out-of-window segments don't count + if (opt.SackBlocks().Count() && opt.SackBlocks().First()->iRight > iSND.UNA) + iDupAcks++; + + //Removed the conditional checking ack > iSendHigh as it is only being used for if SACK option is not set. + // Set the retransmission mode as the fast retransmit if the three TCP duplicate ACKs are received from the server and + //send the lost segment(s) to the server. + if ( + (iDupAcks >= Protocol()->Reordering() || + fack > iSND.UNA + Protocol()->Reordering() * iRMSS)) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] ProcessSegments(): SACK RETRANSMIT!"), (TInt)this)); + iFlags.iFastRetransMode = ETrue; + iSsthresh = Max((iSND.NXT - iSND.UNA) / 2, 2 * iSMSS); + iCwnd = iSsthresh; + iSendHigh = ack; + ReSchedRetransmit(); // Restart retransmission timeout + iTransmitSeq = iSendHigh; // Rewind transmitter for SACK retransmit + SendSegments(ETrue); + iDupAcks = 0; + + } + } + else + { + if (ack > iSendHigh) // Never retransmitted? + iDupAcks++; + if (iFlags.iFastRetransMode) + { + iCwnd += iSMSS; // Inflate congestion window + } + else if (iDupAcks == Protocol()->Reordering()) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] ProcessSegments(): FAST RETRANSMIT!"), (TInt)this)); + iFlags.iFastRetransMode = ETrue; + iSsthresh = Max((iSND.NXT - iSND.UNA) / 2, 2 * iSMSS); + iCwnd = iSsthresh + 3 * iSMSS; + iRecoverSeq = iSND.NXT; + iLwnd = 0; // Reset limited transmit window + ReSchedRetransmit(); // Restart retransmission timeout + SendDataSegment(ack); // Retransmit a single segment + } + } + if (!iFlags.iFastRetransMode) + { + // + // Increment limited transmit window + // + if (iSND.UNA + iCwnd >= iSND.NXT) // FIXME. Prevent lwnd increase during RTO + iLwnd = Min(iLwnd + iSMSS, iSMSS * Protocol()->LtxWindow()); + ReSchedRetransmit(); + } + } + + } + else + iDupAcks = 0; + + // F-RTO stuff. If we enter here, RTO has just occurred, and e.g. iDupAcks should be 0 + if (iFRTOsent) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] F-RTO: iFRTOsent=%d acked=%d ack-sndhigh=%d"), + (TInt)this, iFRTOsent, acked, (ack - iSendHigh))); + + // The ack following the RTO did not advance window, or we cannot transmit new + // unsent data => conventional recovery + // + // The last term of the condition below is due to special case: + // If the first ack after RTO covers all outstanding data, the RTO was due + // to lost retransmit and fixed the whole outstanding window). + if (!acked || !CanForwardTransmit() || (ack >= iRealSendHigh && iFRTOsent == 1)) + { + + LOG(Log::Printf(_L("\ttcp SAP[%u] F-RTO: Doing go-back-N"), (TInt)this)); + iTransmitSeq = iSND.UNA; + if (iFRTOsent == 1 && !acked) + { + // dupack arrives before RTO retransmission is acknowledged. + // don't retransmit the same segment again. + iTransmitSeq += EffectiveMSS(); + } + iCwnd = EffectiveMSS() * iFRTOsent; // number of RTTs after RTO + iFRTOsent = 0; + } + else + { + // Force 2 segments out on first ack after RTO. For that purpose we have to + // estimate the current flightsize (-> iCwnd) and set iLwnd to 2. + // Note that after this step iCwnd is set either to 2*MSS or iSsthresh, so + // the setup below is very temporary. + if (iFRTOsent == 1) // first new ACK after RTO + { + iCwnd = iSND.NXT - iSND.UNA - iSacked.ByteCount() + 2 * iSMSS; + } + else if (iFRTOsent == 2) + { + // Spurious RTO. + // second new ACK after RTO, continue in earlier state because the second + // ACK was delayed and the RTO was likely spurious. + SpuriousTimeout(acked); + } + // coverity[write_write_order] + iFRTOsent = (++iFRTOsent) % 3; + } + } + + iLastWnd = seg.iHdr->Window() << iSndWscale; + + if (iSND.WL1 < seq || (iSND.WL1 == seq && iSND.WL2 <= ack)) + { + iSND.WND = seg.iHdr->Window() << iSndWscale; + iSND.WL1 = seq; + iSND.WL2 = ack; + //LOG(Log::Printf(_L("CProviderTCP6::ProcessSegments(): Window update: %d.\r\n"), iSND.WND)); + } + + // if we are not yet in recovery, reduce congestion window on ECN CE Echo. + if (iFlags.iEcn && seg.iHdr->ECE()) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] ProcessSegments(): Got ECN echo"), (TInt)this)); + + // SourceQuench returns False, if congestion window was reduced for some other + // reason in the last RTT. However, if it was reduced due to FR + // (i.e. last QuenchSeq was more than one RTT ago), we better send CWR to suppress + // ECE flags at the other end. + if (SourceQuench() || iQuenchSeq.Outside(iSND.NXT, iSND.NXT + iSND.WND)) + { + iFlags.iEcnSendCWR = ETrue; + } + } + if((iSND.NXT - ack) >0 && InState(ETcpEstablished) && (acked ==0)) + { + iRetryAck++; + if(iRetryAck >=4) // 4 an arbitary number; as this count does not refer to dup_ack, this will not interfere with Fast retransmission + { + LOG(Log::Printf(_L("\ttcp SAP[%u] ProcessSegments(): retransmitting the segment"), (TInt)this)); + SendSegments(ETrue); + iRetryAck = 0; // reset the retry count + } + } + + } + } + + // + // Everything acked? Check if we need to do a state transition. + // + if (ack == iSND.NXT) + { + if (InState(ETcpFinWait1)) + { + EnterState(ETcpFinWait2); + + // + // If the peer does not send a FIN for some reason, + // we might hang in FIN-WAIT-2 indefinitely. We use + // a 2*MSL timeout to break out of FIN-WAIT-2. + // + // Note that we can only do this if the application + // has closed both directions of the connection. + // However, this should take care of cleanup in the + // the most common case where a server socket hangs + // in FIN-WAIT-2, because the client has crashed or + // disappeared before sending a FIN-ACK. + // + if (iSockFlags.iRecvClose) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] ProcessSegments(): Setting FIN-WAIT-2 timeout"), (TInt)this)); + SchedMsl2Wait(); + } + } + + // + // If we have a lingering Close() or Shutdown(ENormal), complete it with KErrNone. + // + if (iLinger > 0 && InState(ETcpFinWait2|ETcpClosing|ETcpLastAck)) + { + /* + In addition to the processing for the ESTABLISHED state, if + the retransmission queue is empty, the user's CLOSE can be + acknowledged ("ok") but do not delete the TCB.*/ + iLinger = -1; + iLingerTimer->Cancel(); + Detach(); + } + + if (InState(ETcpClosing)) + { + EnterState(ETcpTimeWait); + } + + if (InState(ETcpLastAck)) + { + if (!Protocol()->LocalTimeWait()) + { + // + // Local resource optimization. If the peer is on localhost, + // we will terminate it here. Normally, it would wait for + // a duration of 2*MSL in TIME-WAIT state before deleting + // itself. + // + CFlowContext *flow = iFlow.FlowContext(); +#ifndef SYMBIAN_STRICT_EXPLICIT_SOCKET_BINDING + CProviderTCP6 *sap = + (CProviderTCP6*)Protocol()->LocateSap(EMatchConnection, + KAFUnspec, + flow->RemoteAddr(), + flow->LocalAddr()); +#else + CProviderTCP6 *sap = + (CProviderTCP6*)Protocol()->LocateSap(EMatchConnection, + KAFUnspec, + flow->RemoteAddr(), + flow->LocalAddr(), + NULL, info->iInterfaceIndex); +#endif //SYMBIAN_STRICT_EXPLICIT_SOCKET_BINDING + if (sap != NULL && sap->InState(ETcpTimeWait)) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] ProcessSegments(): Deleting local peer"), (TInt)this)); + sap->Expire(); + } + } + /* + The only thing that can arrive in this state is an + acknowledgment of our FIN. If our FIN is now acknowledged, + delete the TCB, enter the CLOSED state, and return.*/ + EnterState(ETcpClosed); + goto wrapup; + } + } + + if (InState(ETcpTimeWait)) + { + /* + The only thing that can arrive in this state is a + retransmission of the remote FIN. Acknowledge it, and restart + the 2 MSL timeout.*/ + // + // RFC793 appears to be wrong here. A retransmitted FIN should + // already have been acknowledged as an out-of-sequence packet. + // Also, a simultaneous close can cause both end points to send + // an ack and enter the TIME-WAIT state at the same time. In this + // rare case, the following line will cause an ack storm, where + // each side is acknowledging ack packets from the other side. + // + //SendSegment(KTcpCtlACK); + SchedMsl2Wait(); + } + + process_data: + + // + // DATA SEGMENT PROCESSING + // + if (InState(ETcpEstablished|ETcpFinWait1|ETcpFinWait2)) + { + if (seg.iHdr->URG()) + { + TTcpSeqNum up = seq + seg.iHdr->Urgent(); + /* + If the URG bit is set, RCV.UP <- max(RCV.UP,SEG.UP), and signal + the user that the remote side has urgent data if the urgent + pointer (RCV.UP) is in advance of the data consumed. If the + user has already been signaled (or is still in the "urgent + mode") for this continuous sequence of urgent data, do not + signal the user again.*/ + if (up > seq) + RememberUrgentPointer(up); + } + + // + // Process data segments. + // + if (len > 0) + { + if (iSockFlags.iRecvClose) + { + // Receive direction has been shut down. Send RST. + SendReset(ack); + } + else + { + // + // Zero window? We will already have sent and ACK in response, + // so we can just discard the segment. + // + if (!iRCV.WND) + goto accept; + + TInt maxLen = iRCV.NXT + iRCV.WND - seq; + if (len > maxLen) + { + // + // Part of segment is above window -> truncate. This + // can happen when peer is probing a zero window. + // + LOG(Log::Printf(_L("\ttcp SAP[%u] ProcessSegments(): Above window payload: %u"), (TInt)this, len - maxLen)); + if (maxLen <= 0) // Sanity check + goto accept; + + len = maxLen; + packet.TrimEnd(seg.iHdr->HeaderLength() + info->iOffset + len); + fin = 0; // Cannot process FIN yet. + } + + // Remember to ack a pushed segment immediately + if (seg.iHdr->PSH() && Protocol()->PushAck()) + immediateAck = ETrue; + + if (seq <= iRCV.NXT) + { + // + // WARNING! This will destroy the segment header! + // + LOG(if(seqHeaderLength() + info->iOffset); + + // Put in receive queue. + iSockInQ.Append(packet); + len -= (iRCV.NXT - seq); + iSockInQLen += len; + iNewData += len; + iRCV.NXT += len; + iRCV.WND -= len; + + // + // Check if we can take something out of the reassembly queue. + // + TTcpSeqNum fragOff; + TBool fastAck = !iFragQ.IsEmpty(); + while (!iFragQ.IsEmpty()) + { + fragOff = iFragQ.First().Offset(); + if (fragOff > iRCV.NXT) + break; + + RMBufTcpFrag frag; + iFragQ.Remove(frag); + TUint32 fragLen = frag.FragmentLength(); + + // Already got this? + if (fragOff + fragLen <= iRCV.NXT) + { + frag.Free(); + continue; + } + + // Ok. Trim it and put it in receive queue. + TTcpPacket seg(frag); + + frag.TrimStart(seg.iHdr->HeaderLength() + (iRCV.NXT - fragOff)); + fragLen -= (iRCV.NXT - fragOff); + + ASSERT(fragLen == (TUint)frag.Length()); + + iSockInQ.Append(frag); + iSockInQLen += fragLen; + iNewData += fragLen; + + ASSERT((TUint)iSockInQLen >= iNewData); + + LOG(Log::Printf(_L("\ttcp SAP[%u] ProcessSegments(): Took %d:%d(%d) from reassembly queue"), + (TInt)this, iRCV.NXT.Uint32(), (iRCV.NXT + fragLen).Uint32(), fragLen)); + + iRCV.NXT += fragLen; + iRCV.WND -= fragLen; + + } + // + // Update SACK book keeping + // + if (iFlags.iSackOk) + iOptions.SackBlocks().Prune(iRCV.NXT); + + /* + To provide feedback to senders recovering from losses, the receiver + SHOULD send an immediate ACK when it receives a data segment that + fills in all or part of a gap in the sequence space. */ + if (fastAck) + SendSegment(KTcpCtlACK); + } + else + { + LOG(Log::Printf(_L("\ttcp SAP[%u] ProcessSegments(): Processing out-of-order segment!"), (TInt)this)); + TTcpSeqNum blockSeq; + TUint32 blockLen; + + // Fragment queue assumes packet starts from TCP header + packet.TrimStart(info->iOffset); + + //packet.FreeInfo(); -- Leave the info. We need it below. + RMBufTcpFrag frag; + frag.Assign(packet); + iFragQ.Add(frag, (TUint32*)&blockSeq, &blockLen); + if (iFlags.iSackOk) + { + iOptions.SackBlocks().AddUnordered(blockSeq, blockSeq + blockLen); + iOptions.SackBlocks().Limit(4); + } + + /* + Out-of-order data segments SHOULD be acknowledged immediately, in + order to accelerate loss recovery. To trigger the fast retransmit + algorithm, the receiver SHOULD send an immediate duplicate ACK when + it receives a data segment above a gap in the sequence space. */ + SendSegment(KTcpCtlACK); + } + } + } + } + + + // + // FIN PROCESSING + // + +/* + If the FIN bit is set, signal the user "connection closing" and + return any pending RECEIVEs with same message, advance RCV.NXT + over the FIN, and send an acknowledgment for the FIN. Note that + FIN implies PUSH for any segment text not yet delivered to the + user. + */ + // + // Remember that we have received a FIN. Note: the FIN may have + // been received as part of an out-of-sequence segment, in which + // case we may not be ready to process it yet. + // + if (fin && !iFlags.iFinReceived) + { + iFlags.iFinReceived = ETrue; + iFinSeq = seq + len; + } + + // + // Process FIN when all data has been received. + // + if (iFlags.iFinReceived && iRCV.NXT == iFinSeq) + { + // + // Advance iRCV.NXT past the FIN. This also ensures + // that we never end up here again. + // + iRCV.NXT++; + + if (InState(ETcpFinWait1|ETcpFinWait2)) + { + //LOG(Log::Printf(_L("IMMEDIATE FIN ACK\r\n"))); + SendSegment(KTcpCtlACK); + } + else + { + //LOG(Log::Printf(_L("DELAYED FIN ACK\r\n"))); + SendDelayACK(); + } + +/* + SYN-RECEIVED STATE + ESTABLISHED STATE + + Enter the CLOSE-WAIT state. + */ + if (InState(ETcpSynReceived|ETcpEstablished)) + { + EnterState(ETcpCloseWait); + } + +/* + FIN-WAIT-1 STATE + + If our FIN has been ACKed (perhaps in this segment), then + enter TIME-WAIT, start the time-wait timer, turn off the other + timers; otherwise enter the CLOSING state. + */ + if (InState(ETcpFinWait1)) + { + /* + If our FIN has been ACKed (perhaps in this segment), then + enter TIME-WAIT, start the time-wait timer, turn off the other + timers; otherwise enter the CLOSING state.*/ + if (iSND.UNA == iSND.NXT) + { + EnterState(ETcpTimeWait); + SchedMsl2Wait(); + } + else + EnterState(ETcpClosing); + } + +/* + FIN-WAIT-2 STATE + + Enter the TIME-WAIT state. Start the time-wait timer, turn + off the other timers. + */ + if (InState(ETcpFinWait2)) + { + /* + Enter the TIME-WAIT state. Start the time-wait timer, turn + off the other timers.*/ + Stop(); + EnterState(ETcpTimeWait); + SchedMsl2Wait(); + } + + // + // Anything that could arrive in TIME-WAIT is an out-of-window + // segment and should never end up here. + // +#ifdef notdef +/* + TIME-WAIT STATE + + Remain in the TIME-WAIT state. Restart the 2 MSL time-wait + timeout. + */ + if (InState(ETcpTimeWait)) + { + /* + Remain in the TIME-WAIT state. Restart the 2 MSL time-wait + timeout.*/ + SchedMsl2Wait(); + } +#endif + } + + wrapup: + // + // Time to die. + // + if (InState(ETcpClosed) && iRecvQ.IsEmpty()) + { + packet.Free(); + Close(); + Expire(); + return; + } + + accept: + // Reset idle timers + Protocol()->Interfacer()->PacketAccepted(info->iOriginalIndex); + + discard: + packet.Free(); + } + + // Complete application level read. + if (iFlags.iCompleteRecv) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] ProcessSegments(): Complete application read"), (TInt)this)); + iFlags.iCompleteRecv = EFalse; + iCopyOutOffset = 0; + if (iSockFlags.iNotify) + iSocket->Error(KErrNone, MSocketNotify::EErrorRecv); + } + + // This may cause an immediate ACK to be sent from within GetData() + if (iNewData) + { + TInt newData = iNewData, up = 0; + + // Adjust for urgent data. + if (iUpCount) + { + // + // Find the offset of an urgent byte following a block of + // non-urgent data. The number of junked (already delivered) + // urgent bytes will be left in . This looks complicated + // but should normally be very quick. + // + newData = iSockInQLen; + for (up = 0; up < iUpCount; up++) + { + TInt offset = UrgentOffset(up); + if (offset > up) + { + newData = Min(offset, iSockInQLen); + break; + } + } + + // We have an undelivered urgent byte within the block. + if (iFlags.iUrgentMode && !iFlags.iOobInline && newData > UrgentOffset()) + newData = UrgentOffset(); + + ASSERT(newData <= (TInt)iSockInQLen); + + // Subtract bytes that have already been advertised to ESock + newData -= (iSockInQLen - iNewData); + + // Subtract out-of-band bytes + if (!iFlags.iOobInline) + newData -= up; + } + + // Do we have something? + if (newData > 0 && iFlags.iStarted && iSockFlags.iNotify) + { + ASSERT(newData + (iFlags.iOobInline ? 0 : up) <= (TInt)iNewData); + iNewData -= newData; + if (!iFlags.iOobInline) + iNewData -= up; + + LOG(Log::Printf(_L("\ttcp SAP[%u] ProcessSegments(): NewData(%d), %d urgent bytes %s, %d bytes not reported"), + (TInt)this, newData, up, iFlags.iOobInline ? _S("inline") : _S("junked"), iNewData)); + iPending += newData; + iSocket->NewData(newData); + } + } + + // Notify urgent data to application + if (iFlags.iNotifyUrgent && iSockFlags.iNotify) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] ProcessSegments(): Urgent data notification"), (TInt)this)); + iFlags.iNotifyUrgent = EFalse; + iSocket->Error(KErrUrgentData, 0); + } + + // Notify application that connection has been closed by peer. + if (InState(ETcpCloseWait|ETcpClosing|ETcpTimeWait) && !iFlags.iCloseNotified && !iNewData) + { + iFlags.iCloseNotified = ETrue; + if (iFlags.iStarted && iSockFlags.iNotify && !iSockFlags.iRecvClose) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] ProcessSegments(): Calling NewData(KNewDataEndOfData)"), (TInt)this)); + if(iSocket) + iSocket->NewData(KNewDataEndofData); + } + } + + if (iLastAck < iRCV.NXT) + { + // + // We are required to send an ACK for at least every two full sized + // segments but at most once every full sized segment. + // + // In steady state the following rule will acknowledge every second + // segment regardless of the options in the segments (unless maximum + // segment size is VERY small). However, if we receive segments smaller + // than MSS/2, we will send an ACK roughly once for every full segment + // of received data. This works out pretty good. However, if the + // received segments are significantly smaller, we will start to + // experience stretch ACK problems. This might happen, for instance, + // if peer is using PMTUD and the path is constrained. Currently, we + // ignore the problem. + // + // We ack all pushed segments immediately. This improves performance + // for interactive traffic, since partial segments typically have the + // PSH bit set. + // + if (iLastAck + iRMSS < iRCV.NXT || immediateAck) + SendSegment(KTcpCtlACK); + else + SendDelayACK(); + } + + // Wake up transmitter. + SchedTransmit(); + + // + // Wake up application + // + // Note: We do this even if send window is zero, because + // new data from application will trigger zero window probing. + // + // Note 2: Do not wake up app, if lingering close is pending. + // + if (iSockFlags.iFlowStopped) + { + LOG(Log::Printf(_L("\ttcp SAP[%u] ProcessSegments(): FLOW STARTED"), (TInt)this)); + iSockFlags.iFlowStopped = EFalse; + if (iSocket && iSockFlags.iNotify && (iLinger == -1 || !iSockFlags.iSendClose)) + iSocket->CanSend(); + } + } + + +void CProviderTCP6::SpuriousTimeout(TUint aAcked) +/** +Spurious timeout occurred. + +Sets the congestion control parameters depending on the ini setting "tcp_spurious_rto_recovery". + +@param aAcked Number of bytes acknowledged with the ACK that triggered this method. +*/ + { + // TODO: if ACK has ECN-Echo flag, congestion control should not be reverted + LOG(Log::Printf(_L("\ttcp SAP[%u] SpuriousTimeout(%u) ENTER: cwnd: %u ssthresh: %u"), + (TInt)this, aAcked, iCwnd, iSsthresh)); + + switch(Protocol()->SpuriousRtoResponse()) + { + case 1: + default: + // Eifel response (draft uses initial window as burst limit, we use MaxBurst) + // Assume that ssthresh was set to FlightSize / 2 when RTO occurred. + iSsthresh = iSsthresh << 1; + iCwnd = iSND.NXT - iSND.UNA + Min(aAcked, Protocol()->MaxBurst()); + break; + + case 2: + // Half sending rate (ssthresh has been adjusted when RTO occurred) + iCwnd = iSsthresh; + break; + + case 3: + // DCLOR - like behaviour (from earlier draft versions) + iSsthresh = iSsthresh << 1; + iCwnd = 1; + } + LOG(Log::Printf(_L("\ttcp SAP[%u] SpuriousTimeout() EXIT: cwnd: %u ssthresh: %u"), + (TInt)this, iCwnd, iSsthresh)); + // TODO: Do we want to do the RTO adjustment required in Eifel Response draft? Maybe not. -PS + } + + +// +// Initiate a connection by sending the first SYN. +// +// Note: The flow MUST be ready when calling this method. +// +void CProviderTCP6::SendSYN() + { + ASSERT(iState == ETcpConnect); + ASSERT(iFlow.FlowContext() != 0); + + // Initialise send sequence number + iTransmitSeq = iSND.NXT = Protocol()->RandomSequence(); + + if (Protocol()->WinScale() != -1) + { + iRcvWscale = (Protocol()->WinScale() > 0 ? Protocol()->WinScale() - 1 : NeedWindowScale()); + iOptions.SetWindowScale((TUint8)(iRcvWscale + 1)); + } + + iRMSS = Min(LinkRMSS(), iSockInBufSize / 2); + iOptions.SetMSS(iRMSS); + iFreeWindow = Min(iSockInBufSize, 0xffff) % iRMSS; + iRCV.WND = Min(iSockInBufSize, 0xffff) - iFreeWindow; + iSND.UNA = iSND.NXT; + + TUint8 flags = KTcpCtlSYN; + // Start ECN negotiation by setting both ECN bits on in a SYN packet [RFC 3168]. + if (iFlags.iEcn) + { + flags |= KTcpCtlECE | KTcpCtlCWR; + } + + SendSegment(flags); + iTransmitSeq++; + EnterState(ETcpSynSent); + + // Disable on-demand interface setup for this flow + iFlow.FlowContext()->iInfo.iNoInterfaceUp = 1; + + // Lock source address for this flow. It needs to persist over link change events. + iFlow.FlowContext()->iInfo.iLocalSet = 1; + } + + +TInt CProviderTCP6::CreateChild(CProviderTCP6*& aSAP) + { + ASSERT(iConnectCount < iListenQueueSize); + ASSERT(iListenQueue); + + // Create a new SAP and initialize it + TRAPD(err, aSAP = (CProviderTCP6*)Protocol()->NewSAPL(KSockStream)); + if (err == KErrNone) //lint -save -esym(613, aSAP) Possible NULL trapped here + { + aSAP->iParent = this; + aSAP->iSockFamily = iSockFamily; + aSAP->iAppFamily = iAppFamily; + + // The new SAP is not yet known to socket server + aSAP->iSockFlags.iAttached = EFalse; + aSAP->iSockFlags.iNotify = EFalse; + aSAP->iHasNetworkServices = HasNetworkServices(); + // Copy TCP options to the new socket. + aSAP->iSockInBufSize = iSockInBufSize; + aSAP->iSockOutBufSize = iSockOutBufSize; + aSAP->iMSS = iMSS; + aSAP->iSockFlags.iReuse = iSockFlags.iReuse; + aSAP->iFlags.iOobInline = iFlags.iOobInline; + aSAP->iFlags.iNoDelay = iFlags.iNoDelay; + aSAP->iFlags.iEcn = iFlags.iEcn; + + // Clone the flow. Lower layer socket options get copied to the new flow. + aSAP->iFlow.Clone(iFlow); + + // Clone currently resets the notifier, so we reinstall it here + aSAP->iFlow.SetNotify(aSAP); + + // If the listen queue is full, kill a random connection. + if (iConnectCount == iListenQueueSize) + { + TInt i = Protocol()->Random(iListenQueueSize); + CProviderTCP6 *sap = iListenQueue[i]; + sap->iParent = 0; + iListenQueue[i] = iListenQueue[--iConnectCount]; + iListenQueue[iConnectCount] = 0; + sap->Expire(); + } + + // Add the new connection into listen queue. + iListenQueue[iConnectCount++] = aSAP; + } //lint -restore + return err; + } + + +void CProviderTCP6::DetachChild(CProviderTCP6* aSAP) + { + ASSERT(iConnectCount > 0); + ASSERT(iListenQueue); + + // Remove child from listen queue + for (TUint i = 0; i < iConnectCount; i++) + if (iListenQueue[i] == aSAP) + { + aSAP->iParent = 0; + iListenQueue[i] = iListenQueue[--iConnectCount]; + iListenQueue[iConnectCount] = 0; + return; + } + +#ifdef _DEBUG + User::Panic(_L("CProviderTCP6::DetachChild()"), 0); +#endif + } + +TInt CProviderTCP6::CompleteChildConnect(CProviderTCP6* aSAP) + { + ASSERT(aSAP); + ASSERT(iSocket); + iChildDeleted = EFalse; + iSocket->ConnectComplete(*aSAP); + // In some cases SocketServer might have destroyed SAP. + // If this happens, return error for deleted SAP. + return iChildDeleted ? KErrAbort : KErrNone; + } + +inline void CProviderTCP6::SetChildDeleted(TBool aDeleted) + { + iChildDeleted = aDeleted; + } + + +// +// Clear all set option bits for SYNs +// +void CProviderTCP6::ClearSYNSettings() + { + // Remove SYN options from active TCP options. + iOptions.SetWindowScale(0); + iOptions.ClearMSS(); + iOptions.ClearSackOk(); + if (!iFlags.iUseTimeStamps) + iOptions.ClearTimeStamps(); + } + +TUint8 CProviderTCP6::NeedWindowScale() + { + TUint bufbits = 0; +#ifdef SYMBIAN_ADAPTIVE_TCP_RECEIVE_WINDOW + //Window scaling factor needs to be negotiated if the iMaxRecvWin + //is more than the 64K, irrespective of the socket receive buffer + //size. This will help in achieving the maximum throughput in case + //modulation changes + if(iTcpMaxRecvWin > iSockInBufSize ) + { + bufbits = iTcpMaxRecvWin >> 16; + } + else +#endif //SYMBIAN_ADAPTIVE_TCP_RECEIVE_WINDOW + { + bufbits = iSockInBufSize >> 16; + } + TUint8 scale = 0; + + while (bufbits) + { + scale++; + bufbits >>= 1; + } + + return scale; + } + +TInt CProviderTCP6::CheckPolicy(const TSecurityPolicy& aPolicy, const char *aDiagnostic) + { + return iParent ? iParent->CheckPolicy(aPolicy, aDiagnostic) : CProviderInet6Transport::CheckPolicy(aPolicy, aDiagnostic); + } +// +// TCP reassembly queue implementation +// +TUint RMBufTcpFrag::Offset() + { + //LOG(Log::Printf(_L("RMBufTcpFrag::FragOffset()\r\n"))); + + TTcpPacket seg(*this); + return seg.iHdr->Sequence().Uint32(); + } + +TUint RMBufTcpFrag::FragmentLength() + { + //LOG(Log::Printf(_L("RMBufTcpFrag::FragLength(): length = %d\r\n"), Length())); + + TTcpPacket seg(*this); + return Length() - seg.iHdr->HeaderLength(); + } + +void RMBufTcpFrag::Join(RMBufChain& aSeg) + { + //LOG(Log::Printf(_L("RMBufTcpFrag::Join()\r\n"))); + + TTcpPacket thisSeg(*this), newSeg(aSeg); + + // Remove header and overlapping data from aSeg. + aSeg.TrimStart(newSeg.iHdr->HeaderLength() + + (Length() - thisSeg.iHdr->HeaderLength()) - + (newSeg.iHdr->Sequence() - thisSeg.iHdr->Sequence())); + + // Append. + Append(aSeg); + } + +#ifdef __ARMCC__ +#pragma pop +#endif