sctp-timestamp.cc

Go to the documentation of this file.
00001 /*
00002  * Copyright (c) 2001-2004 by the Protocol Engineering Lab, U of Delaware
00003  * All rights reserved.
00004  *
00005  * Armando L. Caro Jr. <acaro@@cis,udel,edu>
00006  *
00007  * Redistribution and use in source and binary forms, with or without
00008  * modification, are permitted provided that the following conditions
00009  * are met:
00010  *
00011  * 1. Redistributions of source code must retain the above copyright
00012  *    notice, this list of conditions and the following disclaimer.
00013  *
00014  * 2. Redistributions in binary form must reproduce the above copyright
00015  *    notice, this list of conditions and the following disclaimer in the
00016  *    documentation and/or other materials provided with the distribution.
00017  *
00018  * 3. Neither the name of the University nor of the Laboratory may be used
00019  *    to endorse or promote products derived from this software without
00020  *    specific prior written permission.
00021  *
00022  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
00023  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
00024  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
00025  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
00026  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
00027  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
00028  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
00029  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00030  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
00031  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
00032  * SUCH DAMAGE.
00033  */
00034 
00035 /* Timestamp extension adds a TIMESTAMP chunk into every packet with DATA
00036  * or SACK chunk(s). The timestamps allow RTT measurements to be made per
00037  * packet on both original transmissiosn and retransmissions. Thus, Karn's
00038  * algorithm is no longer needed.
00039  */
00040 
00041 #ifndef lint
00042 static const char rcsid[] =
00043 "@(#) $Header: /nfs/jade/vint/CVSROOT/ns-2/sctp/sctp-timestamp.cc,v 1.3 2005/10/07 05:58:29 tomh Exp $ (UD/PEL)";
00044 #endif
00045 
00046 #include "ip.h"
00047 #include "sctp-timestamp.h"
00048 #include "flags.h"
00049 #include "random.h"
00050 #include "template.h"
00051 
00052 #include "sctpDebug.h"
00053 
00054 #ifdef DMALLOC
00055 #include "dmalloc.h"
00056 #endif
00057 
00058 #define MIN(x,y)    (((x)<(y))?(x):(y))
00059 #define MAX(x,y)    (((x)>(y))?(x):(y))
00060 
00061 static class TimestampSctpClass : public TclClass 
00062 { 
00063 public:
00064   TimestampSctpClass() : TclClass("Agent/SCTP/Timestamp") {}
00065   TclObject* create(int, const char*const*) 
00066   {
00067     return (new TimestampSctpAgent());
00068   }
00069 } classSctpTimestamp;
00070 
00071 TimestampSctpAgent::TimestampSctpAgent() : SctpAgent()
00072 {
00073 }
00074 
00075 void TimestampSctpAgent::delay_bind_init_all()
00076 {
00077 
00078   SctpAgent::delay_bind_init_all();
00079 }
00080 
00081 int TimestampSctpAgent::delay_bind_dispatch(const char *cpVarName, 
00082                         const char *cpLocalName, 
00083                         TclObject *opTracer)
00084 {
00085   return SctpAgent::delay_bind_dispatch(cpVarName, cpLocalName, opTracer);
00086 }
00087 
00088 void TimestampSctpAgent::OptionReset()
00089 {
00090   DBG_I(OptionReset);
00091 
00092   eNeedTimestampEcho = FALSE;
00093 
00094   DBG_X(OptionReset);
00095 }
00096 
00097 /* This function returns the reserved size needed for timestamp chunks.
00098  */
00099 u_int TimestampSctpAgent::ControlChunkReservation()
00100 {
00101   DBG_I(ControlChunkReservation);
00102   DBG_PL(ControlChunkReservation, "returning %d"), 
00103     sizeof(SctpTimestampChunk_S) DBG_PR;
00104   DBG_X(ControlChunkReservation);
00105   return sizeof(SctpTimestampChunk_S);
00106 }
00107 
00108 /* This function bundles control chunks with data chunks. We copy the timestamp
00109  * chunk into the outgoing packet and return the size of the timestamp chunk.
00110  */
00111 int TimestampSctpAgent::BundleControlChunks(u_char *ucpOutData)
00112 {
00113   DBG_I(BundleControlChunks);
00114   SctpTimestampChunk_S *spTimestampChunk = (SctpTimestampChunk_S *) ucpOutData;
00115 
00116   spTimestampChunk->sHdr.usLength = sizeof(SctpTimestampChunk_S);
00117 
00118   /* We need to send a timestamp in the timestamp chunk when sending data
00119    * chunks (and not sack or foward tsn chunks)... otherwise, we may only
00120    * need the timestamp echo to be set.
00121    */
00122   if((eSendNewDataChunks == TRUE || eMarkedChunksPending == TRUE) && 
00123      eSackChunkNeeded == FALSE  && 
00124      eForwardTsnNeeded == FALSE)
00125     {
00126       spTimestampChunk->sHdr.ucType = SCTP_CHUNK_TIMESTAMP;
00127       spTimestampChunk->sHdr.ucFlags |= SCTP_TIMESTAMP_FLAG_TS;
00128       spTimestampChunk->fTimestamp = (float) Scheduler::instance().clock();
00129     }
00130   if(eNeedTimestampEcho == TRUE)
00131     {
00132       spTimestampChunk->sHdr.ucType = SCTP_CHUNK_TIMESTAMP;
00133       spTimestampChunk->sHdr.ucFlags |= SCTP_TIMESTAMP_FLAG_ECHO;
00134       spTimestampChunk->fEcho = fOutTimestampEcho;
00135       eNeedTimestampEcho = FALSE;
00136     }
00137 
00138   DBG_PL(BundleControlChunks, "returning %d"), 
00139     spTimestampChunk->sHdr.usLength DBG_PR;
00140   DBG_X(BundleControlChunks);
00141   return spTimestampChunk->sHdr.usLength;
00142 }
00143 
00144 void TimestampSctpAgent::AddToSendBuffer(SctpDataChunkHdr_S *spChunk, 
00145                 int iChunkSize,
00146                 u_int uiReliability,
00147                 SctpDest_S *spDest)
00148 {
00149   DBG_I(AddToSendBuffer);
00150   DBG_PL(AddToSendBuffer, "spDest=%p  iChunkSize=%d"), 
00151     spDest, iChunkSize DBG_PR;
00152 
00153   Node_S *spNewNode = new Node_S;
00154   spNewNode->eType = NODE_TYPE_SEND_BUFFER;
00155   spNewNode->vpData = new SctpSendBufferNode_S;
00156 
00157   SctpSendBufferNode_S * spNewNodeData 
00158     = (SctpSendBufferNode_S *) spNewNode->vpData;
00159 
00160   /* This can NOT simply be a 'new SctpDataChunkHdr_S', because we need to
00161    * allocate the space for the ENTIRE data chunk and not just the data
00162    * chunk header.  
00163    */
00164   spNewNodeData->spChunk = (SctpDataChunkHdr_S *) new u_char[iChunkSize];
00165   memcpy(spNewNodeData->spChunk, spChunk, iChunkSize);
00166 
00167   spNewNodeData->eAdvancedAcked = FALSE;
00168   spNewNodeData->eGapAcked = FALSE;
00169   spNewNodeData->eAddedToPartialBytesAcked = FALSE;
00170   spNewNodeData->iNumMissingReports = 0;
00171   spNewNodeData->iUnrelRtxLimit = uiReliability;
00172   spNewNodeData->eMarkedForRtx = NO_RTX;
00173   spNewNodeData->eIneligibleForFastRtx = FALSE;
00174   spNewNodeData->iNumTxs = 1;
00175   spNewNodeData->spDest = spDest;
00176 
00177   // BEGIN -- Timestamp changes to this function
00178   spNewNodeData->dTxTimestamp = Scheduler::instance().clock();
00179   // END -- Timestamp changes to this function
00180 
00181   InsertNode(&sSendBuffer, sSendBuffer.spTail, spNewNode, NULL);
00182 
00183   DBG_X(AddToSendBuffer);
00184 }
00185 
00186 /* Go thru the send buffer deleting all chunks which have a tsn <= the 
00187  * tsn parameter passed in. We assume the chunks in the rtx list are ordered by 
00188  * their tsn value. In addtion, for each chunk deleted:
00189  *   1. we add the chunk length to # newly acked bytes and partial bytes acked
00190  *   2. we update round trip time if appropriate
00191  *   3. stop the timer if the chunk's destination timer is running
00192  */
00193 void TimestampSctpAgent::SendBufferDequeueUpTo(u_int uiTsn)
00194 {
00195   DBG_I(SendBufferDequeueUpTo);
00196 
00197   Node_S *spDeleteNode = NULL;
00198   Node_S *spCurrNode = sSendBuffer.spHead;
00199   SctpSendBufferNode_S *spCurrNodeData = NULL;
00200 
00201   iAssocErrorCount = 0;
00202 
00203   while(spCurrNode != NULL &&
00204     ((SctpSendBufferNode_S*)spCurrNode->vpData)->spChunk->uiTsn <= uiTsn)
00205     {
00206       spCurrNodeData = (SctpSendBufferNode_S *) spCurrNode->vpData;
00207 
00208       /* Only count this chunk as newly acked and towards partial bytes
00209        * acked if it hasn't been gap acked or marked as ack'd due to rtx
00210        * limit.  
00211        */
00212       if((spCurrNodeData->eGapAcked == FALSE) &&
00213      (spCurrNodeData->eAdvancedAcked == FALSE) )
00214     {
00215       uiHighestTsnNewlyAcked = spCurrNodeData->spChunk->uiTsn;
00216 
00217       spCurrNodeData->spDest->iNumNewlyAckedBytes 
00218         += spCurrNodeData->spChunk->sHdr.usLength;
00219 
00220       /* only add to partial bytes acked if we are in congestion
00221        * avoidance mode and if there was cwnd amount of data
00222        * outstanding on the destination (implementor's guide) 
00223        */
00224       if(spCurrNodeData->spDest->iCwnd >spCurrNodeData->spDest->iSsthresh &&
00225          ( spCurrNodeData->spDest->iOutstandingBytes 
00226            >= spCurrNodeData->spDest->iCwnd) )
00227         {
00228           spCurrNodeData->spDest->iPartialBytesAcked 
00229         += spCurrNodeData->spChunk->sHdr.usLength;
00230         }
00231     }
00232 
00233 
00234       /* This is to ensure that Max.Burst is applied when a SACK
00235        * acknowledges a chunk which has been fast retransmitted. If it is
00236        * ineligible for fast rtx, that can only be because it was fast
00237        * rtxed or it timed out. If it timed out, a burst shouldn't be
00238        * possible, but shouldn't hurt either. The fast rtx case is what we
00239        * are really after. This is a proposed change to RFC2960 section
00240        * 7.2.4
00241        */
00242       if(spCurrNodeData->eIneligibleForFastRtx == TRUE)
00243     eApplyMaxBurst = TRUE;
00244 
00245 
00246       // BEGIN -- Timestamp changes to this function
00247 
00248       /* We update the RTT estimate if the following hold true:
00249        *   1. Timestamp set for this chunk matches echoed timestamp
00250        *   2. This chunk has not been gap acked already 
00251        *   3. This chunk has not been advanced acked (pr-sctp: exhausted rtxs)
00252        */
00253       if(fInTimestampEcho == (float) spCurrNodeData->dTxTimestamp &&
00254      spCurrNodeData->eGapAcked == FALSE &&
00255      spCurrNodeData->eAdvancedAcked == FALSE) 
00256     {
00257       RttUpdate(spCurrNodeData->dTxTimestamp, spCurrNodeData->spDest);
00258     }
00259 
00260       // END -- Timestamp changes to this function
00261 
00262       
00263       /* if there is a timer running on the chunk's destination, then stop it
00264        */
00265       if(spCurrNodeData->spDest->eRtxTimerIsRunning == TRUE)
00266     StopT3RtxTimer(spCurrNodeData->spDest);
00267 
00268       /* We don't want to clear the error counter if it's cleared already;
00269        * otherwise, we'll unnecessarily trigger a trace event.
00270        *
00271        * Also, the error counter is cleared by SACKed data ONLY if the
00272        * TSNs are not marked for timeout retransmission and has not been
00273        * gap acked before. Without this condition, we can run into a
00274        * problem for failure detection. When a failure occurs, some data
00275        * may have made it through before the failure, but the sacks got
00276        * lost. When the sender retransmits the first outstanding, the
00277        * receiver will sack all the data whose sacks got lost. We don't
00278        * want these sacks to clear the error counter, or else failover
00279        * would take longer.
00280        */
00281       if(spCurrNodeData->spDest->iErrorCount != 0 &&
00282      spCurrNodeData->eMarkedForRtx != TIMEOUT_RTX &&
00283      spCurrNodeData->eGapAcked == FALSE)
00284     {
00285       DBG_PL(SendBufferDequeueUpTo, 
00286          "clearing error counter for %p with tsn=%lu"), 
00287         spCurrNodeData->spDest, spCurrNodeData->spChunk->uiTsn DBG_PR;
00288 
00289       spCurrNodeData->spDest->iErrorCount = 0; // clear error counter
00290       tiErrorCount++;                          // ... and trace it too!
00291       spCurrNodeData->spDest->eStatus = SCTP_DEST_STATUS_ACTIVE;
00292       if(spCurrNodeData->spDest == spPrimaryDest &&
00293          spNewTxDest != spPrimaryDest) 
00294         {
00295           DBG_PL(SendBufferDequeueUpTo,
00296              "primary recovered... migrating back from %p to %p"),
00297         spNewTxDest, spPrimaryDest DBG_PR;
00298           spNewTxDest = spPrimaryDest; // return to primary
00299         }
00300     }
00301 
00302       spDeleteNode = spCurrNode;
00303       spCurrNode = spCurrNode->spNext;
00304       DeleteNode(&sSendBuffer, spDeleteNode);
00305       spDeleteNode = NULL;
00306     }
00307 
00308   DBG_X(SendBufferDequeueUpTo);
00309 }
00310 
00311 /* This function goes through the entire send buffer filling a packet with 
00312  * chunks marked for retransmission. Once a packet is full (according to MTU)
00313  * it is transmittted. If the eLimit is one packet, than that is all that is
00314  * done. If the eLimit is cwnd, then packets full of marked tsns are sent until
00315  * cwnd is full.
00316  */
00317 void TimestampSctpAgent::RtxMarkedChunks(SctpRtxLimit_E eLimit)
00318 {
00319   DBG_I(RtxMarkedChunks);
00320 
00321   u_char ucpOutData[uiMaxPayloadSize];
00322   u_char *ucpCurrOutData = ucpOutData;
00323   int iBundledControlChunkSize = 0;
00324   int iCurrSize = 0;
00325   int iOutDataSize = 0;
00326   Node_S *spCurrBuffNode = NULL;
00327   SctpSendBufferNode_S *spCurrBuffNodeData = NULL;
00328   SctpDataChunkHdr_S  *spCurrChunk;
00329   SctpDest_S *spRtxDest = NULL;
00330   Node_S *spCurrDestNode = NULL;
00331   SctpDest_S *spCurrDestNodeData = NULL;
00332   Boolean_E eControlChunkBundled = FALSE;
00333   int iNumPacketsSent = 0;
00334 
00335   memset(ucpOutData, 0, uiMaxPayloadSize);
00336 
00337   uiBurstLength = 0;
00338 
00339   /* make sure we clear all the spFirstOutstanding pointers before using them!
00340    */
00341   for(spCurrDestNode = sDestList.spHead;
00342       spCurrDestNode != NULL;
00343       spCurrDestNode = spCurrDestNode->spNext)
00344     {
00345       spCurrDestNodeData = (SctpDest_S *) spCurrDestNode->vpData;
00346       spCurrDestNodeData->spFirstOutstanding = NULL;  // reset
00347     }
00348 
00349   /* We need to set the destination address for the retransmission(s). We assume
00350    * that on a given call to this function, all should all be sent to the same
00351    * address (should be a reasonable assumption). So, to determine the address,
00352    * we find the first marked chunk and determine the destination it was last 
00353    * sent to. 
00354    *
00355    * Also, we temporarily count all marked chunks as not outstanding. Why? Well,
00356    * if we try retransmitting on the same dest as used previously, the cwnd may
00357    * never let us retransmit because the outstanding is counting marked chunks
00358    * too. At the end of this function, we'll count all marked chunks as 
00359    * outstanding again. (ugh... there has to be a better way!)
00360    */
00361   for(spCurrBuffNode = sSendBuffer.spHead; 
00362       spCurrBuffNode != NULL;
00363       spCurrBuffNode = spCurrBuffNode->spNext)
00364     {
00365       spCurrBuffNodeData = (SctpSendBufferNode_S *) spCurrBuffNode->vpData;
00366       
00367       if(spCurrBuffNodeData->eMarkedForRtx != NO_RTX)
00368     {
00369       spCurrChunk = spCurrBuffNodeData->spChunk;
00370 
00371       if(spRtxDest == NULL)
00372         {
00373           /* RFC2960 says that retransmissions should go to an
00374            * alternate destination when available, which is the
00375            * default behavior characterized by
00376            * eRtxToAlt=RTX_TO_ALT_ON. 
00377            *
00378            * We add two experimental options:
00379            *    1. rtx all data to same destination (RTX_TO_ALT_OFF)
00380            *    2. rtx only timeouts to alt dest (RTX_TO_ALT_TIMEOUTS_ONLY)
00381            *
00382            * Note: Even with these options, if the same dest is inactive,
00383            * then alt dest is used.
00384            */
00385           switch(eRtxToAlt)
00386         {
00387         case RTX_TO_ALT_OFF:
00388           if(spCurrBuffNodeData->spDest->eStatus 
00389              == SCTP_DEST_STATUS_ACTIVE)
00390             {
00391               spRtxDest = spCurrBuffNodeData->spDest;
00392             }
00393           else
00394             {
00395               spRtxDest = GetNextDest(spCurrBuffNodeData->spDest);
00396             }
00397           break;
00398           
00399         case RTX_TO_ALT_ON:
00400           spRtxDest = GetNextDest(spCurrBuffNodeData->spDest);
00401           break;
00402 
00403         case RTX_TO_ALT_TIMEOUTS_ONLY:
00404           if(spCurrBuffNodeData->eMarkedForRtx == FAST_RTX &&
00405              spCurrBuffNodeData->spDest->eStatus 
00406              == SCTP_DEST_STATUS_ACTIVE)
00407             {
00408               spRtxDest = spCurrBuffNodeData->spDest; 
00409             }
00410           else
00411             {
00412               spRtxDest = GetNextDest(spCurrBuffNodeData->spDest);
00413             }
00414           break;
00415         }
00416         }
00417 
00418       spCurrBuffNodeData->spDest->iOutstandingBytes
00419         -= spCurrChunk->sHdr.usLength;
00420     }
00421     }
00422 
00423   spCurrBuffNode = sSendBuffer.spHead;
00424 
00425   while( (eLimit == RTX_LIMIT_ONE_PACKET && 
00426       iNumPacketsSent < 1 && 
00427       spCurrBuffNode != NULL) ||
00428      (eLimit == RTX_LIMIT_CWND &&
00429       spRtxDest->iOutstandingBytes < spRtxDest->iCwnd &&
00430       spCurrBuffNode != NULL) )
00431     {
00432       DBG_PL(RtxMarkedChunks, 
00433          "eLimit=%s pktsSent=%d out=%d cwnd=%d spCurrBuffNode=%p"),
00434     (eLimit == RTX_LIMIT_ONE_PACKET) ? "ONE_PACKET" : "CWND",
00435     iNumPacketsSent, spRtxDest->iOutstandingBytes, spRtxDest->iCwnd,
00436     spCurrBuffNode
00437     DBG_PR;
00438       
00439       /* section 7.2.4.3
00440        *
00441        * continue filling up the packet with chunks which are marked for
00442        * rtx. exit loop when we have either run out of chunks or the
00443        * packet is full.
00444        *
00445        * note: we assume at least one data chunk fits in the packet.  
00446        */
00447       for(eControlChunkBundled = FALSE; 
00448       spCurrBuffNode != NULL; 
00449       spCurrBuffNode = spCurrBuffNode->spNext)
00450     {
00451       spCurrBuffNodeData = (SctpSendBufferNode_S *) spCurrBuffNode->vpData;
00452       
00453       /* is this chunk the first outstanding on its destination?
00454        */
00455         if(spCurrBuffNodeData->spDest->spFirstOutstanding == NULL &&
00456          spCurrBuffNodeData->eGapAcked == FALSE &&
00457          spCurrBuffNodeData->eAdvancedAcked == FALSE)
00458         {
00459           /* yes, it is the first!
00460            */
00461           spCurrBuffNodeData->spDest->spFirstOutstanding 
00462         = spCurrBuffNodeData;
00463         }
00464 
00465       /* Only retransmit the chunks which have been marked for rtx.
00466        */
00467       if(spCurrBuffNodeData->eMarkedForRtx != NO_RTX)
00468         {
00469           spCurrChunk = spCurrBuffNodeData->spChunk;
00470 
00471           /* bundle the control chunk before any data chunks and only
00472            * once per packet
00473            */
00474           if(eControlChunkBundled == FALSE)
00475         {
00476           eControlChunkBundled = TRUE;
00477           iBundledControlChunkSize =BundleControlChunks(ucpCurrOutData);
00478           ucpCurrOutData += iBundledControlChunkSize;
00479           iOutDataSize += iBundledControlChunkSize;
00480         }
00481 
00482           /* can we fit this chunk into the packet without exceeding MTU?? 
00483            */
00484           if((iOutDataSize + spCurrChunk->sHdr.usLength) 
00485          > (int) uiMaxPayloadSize)
00486           break;  // doesn't fit in packet... jump out of the for loop
00487 
00488           /* If this chunk was being used to measure the RTT, stop using it.
00489            */
00490           if(spCurrBuffNodeData->spDest->eRtoPending == TRUE &&
00491          spCurrBuffNodeData->dTxTimestamp > 0)
00492         {
00493           spCurrBuffNodeData->dTxTimestamp = 0;
00494           spCurrBuffNodeData->spDest->eRtoPending = FALSE;
00495         }
00496 
00497           /* section 7.2.4.4 (condition 2) - is this the first
00498            * outstanding for the destination and are there still
00499            * outstanding bytes on the destination? if so, restart
00500            * timer.  
00501            */
00502           if(spCurrBuffNodeData->spDest->spFirstOutstanding 
00503          == spCurrBuffNodeData)
00504         {
00505           if(spCurrBuffNodeData->spDest->iOutstandingBytes > 0)
00506             StartT3RtxTimer(spCurrBuffNodeData->spDest);
00507         }
00508 
00509           /* JRI, ALC - Bugfix 2004/01/21 - section 6.1 - Whenever a
00510            * transmission or retransmission is made to any address, if
00511            * the T3-rtx timer of that address is not currently
00512            * running, the sender MUST start that timer.  If the timer
00513            * for that address is already running, the sender MUST
00514            * restart the timer if the earliest (i.e., lowest TSN)
00515            * outstanding DATA chunk sent to that address is being
00516            * retransmitted.  Otherwise, the data sender MUST NOT
00517            * restart the timer.  
00518            */
00519           if(spRtxDest->spFirstOutstanding == NULL ||
00520          spCurrChunk->uiTsn <
00521          spRtxDest->spFirstOutstanding->spChunk->uiTsn)
00522         {
00523           /* This chunk is now the first outstanding on spRtxDest.
00524            */
00525           spRtxDest->spFirstOutstanding = spCurrBuffNodeData;
00526           StartT3RtxTimer(spRtxDest);
00527         }
00528           
00529           memcpy(ucpCurrOutData, spCurrChunk, spCurrChunk->sHdr.usLength);
00530           iCurrSize = spCurrChunk->sHdr.usLength;
00531 
00532           /* the chunk length field does not include the padded bytes,
00533            * so we need to account for these extra bytes.
00534            */
00535           if( (iCurrSize % 4) != 0 ) 
00536         iCurrSize += 4 - (iCurrSize % 4);
00537 
00538           ucpCurrOutData += iCurrSize;
00539           iOutDataSize += iCurrSize;
00540           spCurrBuffNodeData->spDest = spRtxDest;
00541           spCurrBuffNodeData->iNumTxs++;
00542           spCurrBuffNodeData->eMarkedForRtx = NO_RTX;
00543           
00544           // BEGIN -- Timestamp changes to this function
00545           spCurrBuffNodeData->dTxTimestamp = Scheduler::instance().clock();
00546           // END -- Timestamp changes to this function
00547 
00548           /* fill in tracing fields too
00549            */
00550           spSctpTrace[uiNumChunks].eType = SCTP_CHUNK_DATA;
00551           spSctpTrace[uiNumChunks].uiTsn = spCurrChunk->uiTsn;
00552           spSctpTrace[uiNumChunks].usStreamId = spCurrChunk->usStreamId;
00553           spSctpTrace[uiNumChunks].usStreamSeqNum 
00554         = spCurrChunk->usStreamSeqNum;
00555           uiNumChunks++;
00556 
00557           /* the chunk is now outstanding on the alternate destination
00558            */
00559           spCurrBuffNodeData->spDest->iOutstandingBytes
00560         += spCurrChunk->sHdr.usLength;
00561           uiPeerRwnd -= spCurrChunk->sHdr.usLength; // 6.2.1.B
00562           DBG_PL(RtxMarkedChunks, "spDest->iOutstandingBytes=%d"), 
00563         spCurrBuffNodeData->spDest->iOutstandingBytes DBG_PR;
00564 
00565           DBG_PL(RtxMarkedChunks, "TSN=%d"), spCurrChunk->uiTsn DBG_PR;
00566         }
00567       else if(spCurrBuffNodeData->eAdvancedAcked == TRUE)
00568         {
00569           if(spCurrBuffNodeData->spDest->spFirstOutstanding 
00570          == spCurrBuffNodeData)
00571         {
00572           /* This WAS considered the first outstanding chunk for
00573            * the destination, then stop the timer if there are no
00574            * outstanding chunks waiting behind this one in the
00575            * send buffer.  However, if there ARE more outstanding
00576            * chunks on this destination, we need to restart timer
00577            * for those.
00578            */
00579           if(spCurrBuffNodeData->spDest->iOutstandingBytes > 0)
00580             StartT3RtxTimer(spCurrBuffNodeData->spDest);
00581           else
00582             StopT3RtxTimer(spCurrBuffNodeData->spDest);
00583         }
00584         }
00585     }
00586 
00587       /* Transmit the packet now...
00588        */
00589       if(iOutDataSize > 0)
00590     {
00591       SendPacket(ucpOutData, iOutDataSize, spRtxDest);
00592       if(spRtxDest->eRtxTimerIsRunning == FALSE)
00593         StartT3RtxTimer(spRtxDest);
00594       iNumPacketsSent++;      
00595       iOutDataSize = 0; // reset
00596       ucpCurrOutData = ucpOutData; // reset
00597       memset(ucpOutData, 0, uiMaxPayloadSize); // reset
00598 
00599       spRtxDest->opCwndDegradeTimer->resched(spRtxDest->dRto);
00600 
00601       /* This addresses the proposed change to RFC2960 section 7.2.4,
00602        * regarding using of Max.Burst. We have an option which allows
00603        * to control if Max.Burst is applied.
00604        */
00605       if(eUseMaxBurst == MAX_BURST_USAGE_ON)
00606         if( (eApplyMaxBurst == TRUE) && (uiBurstLength++ >= MAX_BURST) )
00607           {
00608         /* we've reached Max.Burst limit, so jump out of loop
00609          */
00610         eApplyMaxBurst = FALSE; // reset before jumping out of loop
00611         break;
00612           }
00613     }
00614     } 
00615 
00616   /* Ok, let's count all marked chunks as outstanding again. (ugh... there
00617    * has to be a better way!)  
00618    */
00619   for(spCurrBuffNode = sSendBuffer.spHead; 
00620       spCurrBuffNode != NULL;
00621       spCurrBuffNode = spCurrBuffNode->spNext)
00622     {
00623       spCurrBuffNodeData = (SctpSendBufferNode_S *) spCurrBuffNode->vpData;
00624       
00625       if(spCurrBuffNodeData->eMarkedForRtx != NO_RTX)
00626     {
00627       spCurrChunk = spCurrBuffNodeData->spChunk;
00628       spCurrBuffNodeData->spDest->iOutstandingBytes
00629         += spCurrChunk->sHdr.usLength;
00630     }
00631     }
00632     
00633   /* If we made it here, either our limit was only one packet worth of
00634    * retransmissions or we hit the end of the list and there are no more
00635    * marked chunks. If we didn't hit the end, let's see if there are more marked
00636    * chunks.
00637    */
00638   eMarkedChunksPending = AnyMarkedChunks();
00639 
00640   DBG_X(RtxMarkedChunks);
00641 }
00642 
00643 /* returns a boolean of whether a fast retransmit is necessary
00644  */
00645 Boolean_E TimestampSctpAgent::ProcessGapAckBlocks(u_char *ucpSackChunk,
00646                           Boolean_E eNewCumAck)
00647 {
00648   DBG_I(ProcessGapAckBlocks);
00649 
00650   Boolean_E eFastRtxNeeded = FALSE;
00651   u_int uiHighestTsnSacked = uiHighestTsnNewlyAcked;
00652   u_int uiStartTsn;
00653   u_int uiEndTsn;
00654   Node_S *spCurrNode = NULL;
00655   SctpSendBufferNode_S *spCurrNodeData = NULL;
00656   Node_S *spCurrDestNode = NULL;
00657   SctpDest_S *spCurrDestNodeData = NULL;
00658 
00659   SctpSackChunk_S *spSackChunk = (SctpSackChunk_S *) ucpSackChunk;
00660 
00661   u_short usNumGapAcksProcessed = 0;
00662   SctpGapAckBlock_S *spCurrGapAck 
00663     = (SctpGapAckBlock_S *) (ucpSackChunk + sizeof(SctpSackChunk_S));
00664 
00665   DBG_PL(ProcessGapAckBlocks,"CumAck=%d"), spSackChunk->uiCumAck DBG_PR;
00666 
00667   if(sSendBuffer.spHead == NULL) // do we have ANYTHING in the rtx buffer?
00668     {
00669       /* This COULD mean that this sack arrived late, and a previous one
00670        * already cum ack'd everything. ...so, what do we do? nothing??
00671        */
00672     }
00673   
00674   else // we do have chunks in the rtx buffer
00675     {
00676       /* make sure we clear all the spFirstOutstanding pointers before
00677        * using them!  
00678        */
00679       for(spCurrDestNode = sDestList.spHead;
00680       spCurrDestNode != NULL;
00681       spCurrDestNode = spCurrDestNode->spNext)
00682     {
00683       spCurrDestNodeData = (SctpDest_S *) spCurrDestNode->vpData;
00684       spCurrDestNodeData->spFirstOutstanding = NULL;
00685     }
00686 
00687       for(spCurrNode = sSendBuffer.spHead;
00688       (spCurrNode != NULL) &&
00689         (usNumGapAcksProcessed != spSackChunk->usNumGapAckBlocks);
00690       spCurrNode = spCurrNode->spNext)
00691     {
00692       spCurrNodeData = (SctpSendBufferNode_S *) spCurrNode->vpData;
00693 
00694       /* is this chunk the first outstanding on its destination?
00695        */
00696       if(spCurrNodeData->spDest->spFirstOutstanding == NULL &&
00697          spCurrNodeData->eGapAcked == FALSE &&
00698          spCurrNodeData->eAdvancedAcked == FALSE)
00699         {
00700           /* yes, it is the first!
00701            */
00702           spCurrNodeData->spDest->spFirstOutstanding = spCurrNodeData;
00703         }
00704 
00705       DBG_PL(ProcessGapAckBlocks, "--> rtx list chunk begin") DBG_PR;
00706 
00707       DBG_PL(ProcessGapAckBlocks, "    TSN=%d"), 
00708         spCurrNodeData->spChunk->uiTsn 
00709         DBG_PR;
00710 
00711       DBG_PL(ProcessGapAckBlocks, "    %s=%s %s=%s"),
00712         "eGapAcked", 
00713         spCurrNodeData->eGapAcked ? "TRUE" : "FALSE",
00714         "eAddedToPartialBytesAcked",
00715         spCurrNodeData->eAddedToPartialBytesAcked ? "TRUE" : "FALSE" 
00716         DBG_PR;
00717 
00718       DBG_PL(ProcessGapAckBlocks, "    NumMissingReports=%d NumTxs=%d"),
00719         spCurrNodeData->iNumMissingReports, 
00720         spCurrNodeData->iNumTxs 
00721         DBG_PR;
00722 
00723       DBG_PL(ProcessGapAckBlocks, "<-- rtx list chunk end") DBG_PR;
00724       
00725       DBG_PL(ProcessGapAckBlocks,"GapAckBlock StartOffset=%d EndOffset=%d"),
00726         spCurrGapAck->usStartOffset, spCurrGapAck->usEndOffset DBG_PR;
00727 
00728       uiStartTsn = spSackChunk->uiCumAck + spCurrGapAck->usStartOffset;
00729       uiEndTsn = spSackChunk->uiCumAck + spCurrGapAck->usEndOffset;
00730       
00731       DBG_PL(ProcessGapAckBlocks, "GapAckBlock StartTsn=%d EndTsn=%d"),
00732         uiStartTsn, uiEndTsn DBG_PR;
00733 
00734       if(spCurrNodeData->spChunk->uiTsn < uiStartTsn)
00735         {
00736           /* This chunk is NOT being acked and is missing at the receiver
00737            */
00738 
00739           /* If this chunk was GapAcked before, then either the
00740            * receiver has renegged the chunk (which our simulation
00741            * doesn't do) or this SACK is arriving out of order.
00742            */
00743           if(spCurrNodeData->eGapAcked == TRUE)
00744         {
00745           DBG_PL(ProcessGapAckBlocks, 
00746              "out of order SACK? setting TSN=%d eGapAcked=FALSE"),
00747             spCurrNodeData->spChunk->uiTsn DBG_PR;
00748           spCurrNodeData->eGapAcked = FALSE;
00749           spCurrNodeData->spDest->iOutstandingBytes 
00750             += spCurrNodeData->spChunk->sHdr.usLength;
00751 
00752           /* section 6.3.2.R4 says that we should restart the
00753            * T3-rtx timer here if it isn't running already. In our
00754            * implementation, it isn't necessary since
00755            * ProcessSackChunk will restart the timer for any
00756            * destinations which have outstanding data and don't
00757            * have a timer running.
00758            */
00759         }
00760         }
00761       else if((uiStartTsn <= spCurrNodeData->spChunk->uiTsn) && 
00762           (spCurrNodeData->spChunk->uiTsn <= uiEndTsn) )
00763         {
00764           /* This chunk is being acked via a gap ack block
00765            */
00766           DBG_PL(ProcessGapAckBlocks, "gap ack acks this chunk: %s%s"),
00767         "eGapAcked=",
00768         spCurrNodeData->eGapAcked ? "TRUE" : "FALSE" 
00769         DBG_PR;
00770 
00771           /* HTNA algorithm... we need to know the highest TSN
00772            * sacked (even if it isn't new), so that when the sender
00773            * is in Fast Recovery, the outstanding tsns beyond the 
00774            * last sack tsn do not have their missing reports incremented
00775            */
00776           if(uiHighestTsnSacked < spCurrNodeData->spChunk->uiTsn)
00777         uiHighestTsnSacked = spCurrNodeData->spChunk->uiTsn;
00778 
00779           if(spCurrNodeData->eGapAcked == FALSE)
00780         {
00781           DBG_PL(ProcessGapAckBlocks, "setting eGapAcked=TRUE") DBG_PR;
00782           spCurrNodeData->eGapAcked = TRUE;
00783 
00784           /* HTNA algorithm... we need to know the highest TSN
00785            * newly acked
00786            */
00787           if(uiHighestTsnNewlyAcked < spCurrNodeData->spChunk->uiTsn)
00788             uiHighestTsnNewlyAcked = spCurrNodeData->spChunk->uiTsn;
00789 
00790           if(spCurrNodeData->eAdvancedAcked == FALSE)
00791             {
00792               spCurrNodeData->spDest->iNumNewlyAckedBytes 
00793             += spCurrNodeData->spChunk->sHdr.usLength;
00794             }
00795 
00796           /* only increment partial bytes acked if we are in
00797            * congestion avoidance mode, we have a new cum ack, and
00798            * we haven't already incremented it for this sack
00799            */
00800           if(( spCurrNodeData->spDest->iCwnd 
00801                > spCurrNodeData->spDest->iSsthresh) &&
00802              eNewCumAck == TRUE &&
00803              spCurrNodeData->eAddedToPartialBytesAcked == FALSE)
00804             {
00805               DBG_PL(ProcessGapAckBlocks, 
00806                  "setting eAddedToPartiallyBytesAcked=TRUE") DBG_PR;
00807 
00808               spCurrNodeData->eAddedToPartialBytesAcked = TRUE; // set
00809 
00810               spCurrNodeData->spDest->iPartialBytesAcked 
00811             += spCurrNodeData->spChunk->sHdr.usLength;
00812             }
00813 
00814           // BEGIN -- Timestamp changes to this function
00815 
00816           /* We update the RTT estimate if the following hold true:
00817            *   1. Timestamp set for this chunk matches echoed timestamp
00818            *   2. This chunk has not been gap acked already 
00819            *   3. This chunk has not been advanced acked
00820            */
00821           if(fInTimestampEcho == (float) spCurrNodeData->dTxTimestamp &&
00822              spCurrNodeData->eAdvancedAcked == FALSE) 
00823             {
00824               RttUpdate(spCurrNodeData->dTxTimestamp, 
00825                 spCurrNodeData->spDest);
00826             }
00827 
00828           // END -- Timestamp changes to this function
00829 
00830 
00831           /* section 6.3.2.R3 - Stop the timer if this is the
00832            * first outstanding for this destination (note: it may
00833            * have already been stopped if there was a new cum
00834            * ack). If there are still outstanding bytes on this
00835            * destination, we'll restart the timer later in
00836            * ProcessSackChunk() 
00837            */
00838           if(spCurrNodeData->spDest->spFirstOutstanding 
00839              == spCurrNodeData)
00840             
00841             {
00842               if(spCurrNodeData->spDest->eRtxTimerIsRunning == TRUE)
00843             StopT3RtxTimer(spCurrNodeData->spDest);
00844             }
00845           
00846           iAssocErrorCount = 0;
00847           
00848           /* We don't want to clear the error counter if it's
00849            * cleared already; otherwise, we'll unnecessarily
00850            * trigger a trace event.
00851            *
00852            * Also, the error counter is cleared by SACKed data
00853            * ONLY if the TSNs are not marked for timeout
00854            * retransmission and has not been gap acked
00855            * before. Without this condition, we can run into a
00856            * problem for failure detection. When a failure occurs,
00857            * some data may have made it through before the
00858            * failure, but the sacks got lost. When the sender
00859            * retransmits the first outstanding, the receiver will
00860            * sack all the data whose sacks got lost. We don't want
00861            * these sacks * to clear the error counter, or else
00862            * failover would take longer.
00863            */
00864           if(spCurrNodeData->spDest->iErrorCount != 0 &&
00865              spCurrNodeData->eMarkedForRtx != TIMEOUT_RTX)
00866             {
00867               DBG_PL(ProcessGapAckBlocks,
00868                  "clearing error counter for %p with tsn=%lu"), 
00869             spCurrNodeData->spDest, 
00870             spCurrNodeData->spChunk->uiTsn DBG_PR;
00871 
00872               spCurrNodeData->spDest->iErrorCount = 0; // clear errors
00873               tiErrorCount++;                       // ... and trace it!
00874               spCurrNodeData->spDest->eStatus = SCTP_DEST_STATUS_ACTIVE;
00875               if(spCurrNodeData->spDest == spPrimaryDest &&
00876              spNewTxDest != spPrimaryDest) 
00877             {
00878               DBG_PL(ProcessGapAckBlocks,
00879                  "primary recovered... "
00880                  "migrating back from %p to %p"),
00881                 spNewTxDest, spPrimaryDest DBG_PR;
00882               spNewTxDest = spPrimaryDest; // return to primary
00883             }
00884             }
00885           spCurrNodeData->eMarkedForRtx = NO_RTX; // unmark
00886         }
00887         }
00888       else if(spCurrNodeData->spChunk->uiTsn > uiEndTsn)
00889         {
00890           /* This point in the rtx buffer is already past the tsns which are
00891            * being acked by this gap ack block.  
00892            */
00893           usNumGapAcksProcessed++; 
00894 
00895           /* Did we process all the gap ack blocks?
00896            */
00897           if(usNumGapAcksProcessed != spSackChunk->usNumGapAckBlocks)
00898         {
00899           DBG_PL(ProcessGapAckBlocks, "jump to next gap ack block") 
00900             DBG_PR;
00901 
00902           spCurrGapAck 
00903             = ((SctpGapAckBlock_S *)
00904                (ucpSackChunk + sizeof(SctpSackChunk_S)
00905             + (usNumGapAcksProcessed * sizeof(SctpGapAckBlock_S))));
00906         }
00907 
00908           /* If this chunk was GapAcked before, then either the
00909            * receiver has renegged the chunk (which our simulation
00910            * doesn't do) or this SACK is arriving out of order.
00911            */
00912           if(spCurrNodeData->eGapAcked == TRUE)
00913         {
00914           DBG_PL(ProcessGapAckBlocks, 
00915              "out of order SACK? setting TSN=%d eGapAcked=FALSE"),
00916             spCurrNodeData->spChunk->uiTsn DBG_PR;
00917           spCurrNodeData->eGapAcked = FALSE;
00918           spCurrNodeData->spDest->iOutstandingBytes 
00919             += spCurrNodeData->spChunk->sHdr.usLength;
00920           
00921           /* section 6.3.2.R4 says that we should restart the
00922            * T3-rtx timer here if it isn't running already. In our
00923            * implementation, it isn't necessary since
00924            * ProcessSackChunk will restart the timer for any
00925            * destinations which have outstanding data and don't
00926            * have a timer running.
00927            */
00928         }
00929         }
00930     }
00931 
00932       /* By this time, either we have run through the entire send buffer or we
00933        * have run out of gap ack blocks. In the case that we have run out of gap
00934        * ack blocks before we finished running through the send buffer, we need
00935        * to mark the remaining chunks in the send buffer as eGapAcked=FALSE.
00936        * This final marking needs to be done, because we only trust gap ack info
00937        * from the last SACK. Otherwise, renegging (which we don't do) or out of
00938        * order SACKs would give the sender an incorrect view of the peer's rwnd.
00939        */
00940       for(; spCurrNode != NULL; spCurrNode = spCurrNode->spNext)
00941     {
00942       /* This chunk is NOT being acked and is missing at the receiver
00943        */
00944       spCurrNodeData = (SctpSendBufferNode_S *) spCurrNode->vpData;
00945 
00946       /* If this chunk was GapAcked before, then either the
00947        * receiver has renegged the chunk (which our simulation
00948        * doesn't do) or this SACK is arriving out of order.
00949        */
00950       if(spCurrNodeData->eGapAcked == TRUE)
00951         {
00952           DBG_PL(ProcessGapAckBlocks, 
00953              "out of order SACK? setting TSN=%d eGapAcked=FALSE"),
00954         spCurrNodeData->spChunk->uiTsn DBG_PR;
00955           spCurrNodeData->eGapAcked = FALSE;
00956           spCurrNodeData->spDest->iOutstandingBytes 
00957         += spCurrNodeData->spChunk->sHdr.usLength;
00958 
00959           /* section 6.3.2.R4 says that we should restart the T3-rtx
00960            * timer here if it isn't running already. In our
00961            * implementation, it isn't necessary since ProcessSackChunk
00962            * will restart the timer for any destinations which have
00963            * outstanding data and don't have a timer running.
00964            */
00965         }
00966     }
00967 
00968       DBG_PL(ProcessGapAckBlocks, "now incrementing missing reports...") DBG_PR;
00969       DBG_PL(ProcessGapAckBlocks, "uiHighestTsnNewlyAcked=%d"), 
00970          uiHighestTsnNewlyAcked DBG_PR;
00971 
00972       for(spCurrNode = sSendBuffer.spHead;
00973       spCurrNode != NULL; 
00974       spCurrNode = spCurrNode->spNext)
00975     {
00976       spCurrNodeData = (SctpSendBufferNode_S *) spCurrNode->vpData;
00977 
00978       DBG_PL(ProcessGapAckBlocks, "TSN=%d eGapAcked=%s"), 
00979         spCurrNodeData->spChunk->uiTsn,
00980         spCurrNodeData->eGapAcked ? "TRUE" : "FALSE"
00981         DBG_PR;
00982 
00983       if(spCurrNodeData->eGapAcked == FALSE)
00984         {
00985           /* HTNA (Highest TSN Newly Acked) algorithm from
00986            * implementer's guide. The HTNA increments missing reports
00987            * for TSNs not GapAcked when one of the following
00988            * conditions hold true:
00989            *
00990            *    1. The TSN is less than the highest TSN newly acked.
00991            *
00992            *    2. The TSN is less than the highest TSN sacked so far
00993            *    (not necessarily newly acked), the sender is in Fast
00994            *    Recovery, the cum ack changes, and the new cum ack is less
00995            *    than recover.
00996            */
00997           if( (spCurrNodeData->spChunk->uiTsn < uiHighestTsnNewlyAcked) ||
00998           (eNewCumAck == TRUE && 
00999            uiHighestTsnNewlyAcked <= uiRecover &&
01000            spCurrNodeData->spChunk->uiTsn < uiHighestTsnSacked))
01001         {
01002           spCurrNodeData->iNumMissingReports++;
01003           DBG_PL(ProcessGapAckBlocks, 
01004              "incrementing missing report for TSN=%d to %d"), 
01005             spCurrNodeData->spChunk->uiTsn,
01006             spCurrNodeData->iNumMissingReports
01007             DBG_PR;
01008 
01009           if(spCurrNodeData->iNumMissingReports >= iFastRtxTrigger &&
01010              spCurrNodeData->eIneligibleForFastRtx == FALSE &&
01011              spCurrNodeData->eAdvancedAcked == FALSE)
01012             {
01013               MarkChunkForRtx(spCurrNodeData, FAST_RTX);
01014               eFastRtxNeeded = TRUE;
01015               spCurrNodeData->eIneligibleForFastRtx = TRUE;
01016               DBG_PL(ProcessGapAckBlocks, 
01017                  "setting eFastRtxNeeded = TRUE") DBG_PR;
01018             }
01019         }
01020         }
01021     }
01022     }
01023 
01024   if(eFastRtxNeeded == TRUE)
01025     tiFrCount++;
01026 
01027   DBG_PL(ProcessGapAckBlocks, "eFastRtxNeeded=%s"), 
01028     eFastRtxNeeded ? "TRUE" : "FALSE" DBG_PR;
01029   DBG_X(ProcessGapAckBlocks);
01030   return eFastRtxNeeded;
01031 }
01032 
01033 /* This function is left as a hook for extensions to process chunk types not
01034  * supported in the base sctp. Here, we can only expect the timestamp chunk.
01035  */
01036 void TimestampSctpAgent::ProcessOptionChunk(u_char *ucpInChunk)
01037 {
01038   DBG_I(ProcessOptionChunk);
01039   double dCurrTime = Scheduler::instance().clock();
01040 
01041   switch( ((SctpChunkHdr_S *)ucpInChunk)->ucType)
01042     {
01043     case SCTP_CHUNK_TIMESTAMP:
01044       ProcessTimestampChunk((SctpTimestampChunk_S *) ucpInChunk);
01045       break;
01046       
01047     default:
01048       DBG_PL(ProcessOptionChunk, "unexpected chunk type (unknown: %d) at %f"),
01049     ((SctpChunkHdr_S *)ucpInChunk)->ucType, dCurrTime DBG_PR;
01050       printf("[ProcessOptionChunk] unexpected chunk type (unknown: %d) at %f\n",
01051          ((SctpChunkHdr_S *)ucpInChunk)->ucType, dCurrTime);
01052       break;
01053     }
01054 
01055   DBG_X(ProcessOptionChunk);
01056 }
01057 
01058 void TimestampSctpAgent::ProcessTimestampChunk(SctpTimestampChunk_S 
01059                            *spTimestampChunk)
01060 {
01061   DBG_I(ProcessTimestampChunk);
01062 
01063   /* Only echo the timestamp if there isn't already an echo ready to go. Since
01064    * the receiver may delay sacks and ack 2 data packets with one sack, we want
01065    * to echo the timestamp of the first data chunk that triggers the sack.
01066    */
01067   if(spTimestampChunk->sHdr.ucFlags & SCTP_TIMESTAMP_FLAG_TS)
01068     {
01069       if(eNeedTimestampEcho == FALSE)
01070     {
01071       eNeedTimestampEcho = TRUE;
01072       fOutTimestampEcho = spTimestampChunk->fTimestamp;
01073       DBG_PL(ProcessTimestampChunk, "timestamp=%f ...echoing!"), 
01074         spTimestampChunk->fTimestamp DBG_PR;
01075     }
01076       else
01077     {
01078       DBG_PL(ProcessTimestampChunk, 
01079          "timestamp=%f ...ignoring (already echoing one)"), 
01080         spTimestampChunk->fTimestamp DBG_PR;
01081     }
01082     }
01083 
01084   if(spTimestampChunk->sHdr.ucFlags & SCTP_TIMESTAMP_FLAG_ECHO)
01085     {
01086       fInTimestampEcho = spTimestampChunk->fEcho;
01087       DBG_PL(ProcessTimestampChunk, "echo=%f"), 
01088     spTimestampChunk->fEcho DBG_PR;
01089     }
01090 
01091   DBG_X(ProcessTimestampChunk);
01092 }

Generated on Tue Mar 6 16:47:50 2007 for ns2 Network Simulator 2.29 by  doxygen 1.4.6