PacketProcessor.cpp (21185B)
1 /* 2 =========================================================================== 3 4 Doom 3 BFG Edition GPL Source Code 5 Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. 6 7 This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). 8 9 Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify 10 it under the terms of the GNU General Public License as published by 11 the Free Software Foundation, either version 3 of the License, or 12 (at your option) any later version. 13 14 Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, 15 but WITHOUT ANY WARRANTY; without even the implied warranty of 16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 GNU General Public License for more details. 18 19 You should have received a copy of the GNU General Public License 20 along with Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>. 21 22 In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. 23 24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. 25 26 =========================================================================== 27 */ 28 29 #pragma hdrstop 30 #include "../idLib/precompiled.h" 31 32 idCVar net_maxRate( "net_maxRate", "50", CVAR_INTEGER, "max send rate in kilobytes per second" ); 33 34 idCVar net_showReliableCompression( "net_showReliableCompression", "0", CVAR_BOOL, "Show reliable compression ratio." ); 35 36 // we use an assert(0); return idiom in some places, which lint complains about 37 //lint -e527 unreachable code at token 'return' 38 39 /* 40 ================================================ 41 idPacketProcessor::QueueReliableAck 42 ================================================ 43 */ 44 void idPacketProcessor::QueueReliableAck( int lastReliable ) { 45 // NOTE - Even if it was the last known sequence, go ahead and ack it, in case our last ack for this sequence got dropped 46 if ( lastReliable >= reliableSequenceRecv ) { 47 queuedReliableAck = lastReliable; 48 reliableSequenceRecv = lastReliable; 49 } 50 } 51 52 /* 53 ================================================ 54 idPacketProcessor::FinalizeRead 55 ================================================ 56 */ 57 int idPacketProcessor::FinalizeRead( idBitMsg & inMsg, idBitMsg & outMsg, int & userValue ) { 58 userValue = 0; 59 60 idInnerPacketHeader header; 61 header.ReadFromMsg( inMsg ); 62 63 if ( !verify( header.Type() != PACKET_TYPE_FRAGMENTED ) ) { // We shouldn't be fragmented at this point 64 idLib::Printf("Received invalid fragmented packet.\n" ); 65 return RETURN_TYPE_NONE; 66 } 67 68 if ( header.Type() == PACKET_TYPE_RELIABLE_ACK ) { 69 // Handle reliable ack 70 int reliableSequence = inMsg.ReadLong(); 71 reliable.RemoveOlderThan( reliableSequence + 1 ); 72 header.ReadFromMsg( inMsg ); // Read the new header, since the reliable ack sits on top the actual header of the message 73 } 74 75 if ( header.Type() == PACKET_TYPE_OOB ) { 76 // out-of-band packet 77 userValue = header.Value(); 78 } else { 79 // At this point, this MUST be an in-band packet 80 if ( !verify( header.Type() == PACKET_TYPE_INBAND ) ) { 81 idLib::Printf("In-band packet expected, received type %i instead.\n", header.Type() ); 82 return RETURN_TYPE_NONE; 83 } 84 85 // Reset number of reliables received (NOTE - This means you MUST unload all reliables as they are received) 86 numReliable = 0; 87 88 // Handle reliable portion of in-band packets 89 int numReliableRecv = header.Value(); 90 int bufferPos = 0; 91 92 if ( numReliableRecv > 0 ) { 93 // Byte align msg 94 inMsg.ReadByteAlign(); 95 96 int compressedSize = inMsg.ReadShort(); 97 98 lzwCompressionData_t lzwData; 99 idLZWCompressor lzwCompressor( &lzwData ); 100 101 lzwCompressor.Start( (uint8*)inMsg.GetReadData() + inMsg.GetReadCount(), compressedSize ); // Read from msg 102 103 int reliableSequence = 0; 104 105 lzwCompressor.ReadAgnostic< int >( reliableSequence ); 106 107 for ( int r = 0; r < numReliableRecv; r++ ) { 108 uint8 uncompMem[ MAX_MSG_SIZE ]; 109 110 uint16 reliableDataLength = 0; 111 lzwCompressor.ReadAgnostic< uint16 >( reliableDataLength ); 112 lzwCompressor.Read( uncompMem, reliableDataLength ); 113 114 if ( reliableSequence + r > reliableSequenceRecv ) { // Only accept newer reliable msg's than we've currently already received 115 if ( !verify( bufferPos + reliableDataLength <= sizeof( reliableBuffer ) ) ) { 116 idLib::Printf( "Reliable msg size overflow.\n" ); 117 return RETURN_TYPE_NONE; 118 } 119 if ( !verify( numReliable < MAX_RELIABLE_QUEUE ) ) { 120 idLib::Printf( "Reliable msg count overflow.\n" ); 121 return RETURN_TYPE_NONE; 122 } 123 memcpy( reliableBuffer + bufferPos, uncompMem, reliableDataLength ); 124 reliableMsgSize[ numReliable ] = reliableDataLength; 125 reliableMsgPtrs[ numReliable++ ] = &reliableBuffer[ bufferPos ]; 126 bufferPos += reliableDataLength; 127 } else { 128 extern idCVar net_verboseReliable; 129 if ( net_verboseReliable.GetBool() ) { 130 idLib::Printf( "Ignoring reliable msg %i because %i was already acked\n", ( reliableSequence + r ), reliableSequenceRecv ); 131 } 132 } 133 134 if ( !verify( lzwCompressor.IsOverflowed() == false ) ) { 135 idLib::Printf( "lzwCompressor.IsOverflowed() == true.\n" ); 136 return RETURN_TYPE_NONE; 137 } 138 } 139 140 inMsg.SetReadCount( inMsg.GetReadCount() + compressedSize ); 141 142 QueueReliableAck( reliableSequence + numReliableRecv - 1 ); 143 } 144 } 145 146 // Load actual msg 147 outMsg.BeginWriting(); 148 outMsg.WriteData( inMsg.GetReadData() + inMsg.GetReadCount(), inMsg.GetRemainingData() ); 149 outMsg.SetSize( inMsg.GetRemainingData() ); 150 151 return ( header.Type() == PACKET_TYPE_OOB ) ? RETURN_TYPE_OOB : RETURN_TYPE_INBAND; 152 } 153 154 /* 155 ================================================ 156 idPacketProcessor::QueueReliableMessage 157 ================================================ 158 */ 159 bool idPacketProcessor::QueueReliableMessage( byte type, const byte * data, int dataLen ) { 160 return reliable.Append( reliableSequenceSend++, &type, 1, data, dataLen ); 161 } 162 163 /* 164 ======================== 165 idPacketProcessor::CanSendMoreData 166 ======================== 167 */ 168 bool idPacketProcessor::CanSendMoreData() const { 169 if ( net_maxRate.GetInteger() == 0 ) { 170 return true; 171 } 172 173 return ( outgoingRateBytes <= net_maxRate.GetInteger() * 1024 ); 174 } 175 176 /* 177 ======================== 178 idPacketProcessor::UpdateOutgoingRate 179 ======================== 180 */ 181 void idPacketProcessor::UpdateOutgoingRate( const int time, const int size ) { 182 outgoingBytes += size; 183 184 // update outgoing rate variables 185 if ( time > outgoingRateTime ) { 186 outgoingRateBytes -= outgoingRateBytes * (float)( time - outgoingRateTime ) / 1000.0f; 187 if ( outgoingRateBytes < 0.0f ) { 188 outgoingRateBytes = 0.0f; 189 } 190 } 191 192 outgoingRateTime = time; 193 outgoingRateBytes += size; 194 195 // compute an average bandwidth at intervals 196 if ( time - lastOutgoingRateTime > BANDWIDTH_AVERAGE_PERIOD ) { 197 currentOutgoingRate = 1000 * ( outgoingBytes - lastOutgoingBytes ) / ( time - lastOutgoingRateTime ); 198 lastOutgoingBytes = outgoingBytes; 199 lastOutgoingRateTime = time; 200 } 201 } 202 203 /* 204 ================= 205 idPacketProcessor::UpdateIncomingRate 206 ================= 207 */ 208 void idPacketProcessor::UpdateIncomingRate( const int time, const int size ) { 209 incomingBytes += size; 210 211 // update incoming rate variables 212 if ( time > incomingRateTime ) { 213 incomingRateBytes -= incomingRateBytes * (float)( time - incomingRateTime ) / 1000.0f; 214 if ( incomingRateBytes < 0.0f ) { 215 incomingRateBytes = 0.0f; 216 } 217 } 218 incomingRateTime = time; 219 incomingRateBytes += size; 220 221 // compute an average bandwidth at intervals 222 if ( time - lastIncomingRateTime > BANDWIDTH_AVERAGE_PERIOD ) { 223 currentIncomingRate = 1000 * ( incomingBytes - lastIncomingBytes ) / ( time - lastIncomingRateTime ); 224 lastIncomingBytes = incomingBytes; 225 lastIncomingRateTime = time; 226 } 227 } 228 229 /* 230 ================================================ 231 idPacketProcessor::ProcessOutgoing 232 NOTE - We only compress reliables because we assume everything else has already been compressed. 233 ================================================ 234 */ 235 bool idPacketProcessor::ProcessOutgoing( const int time, const idBitMsg & msg, bool isOOB, int userData ) { 236 // We can only do ONE ProcessOutgoing call, then we need to do GetSendFragment to 237 // COMPLETELY empty unsentMsg before calling ProcessOutgoing again. 238 if ( !verify( fragmentedSend == false ) ) { 239 idLib::Warning( "ProcessOutgoing: fragmentedSend == true!"); 240 return false; 241 } 242 243 if ( !verify( unsentMsg.GetRemainingData() == 0 ) ) { 244 idLib::Warning( "ProcessOutgoing: unsentMsg.GetRemainingData() > 0!"); 245 return false; 246 } 247 248 // Build the full msg to send, which could include reliable data 249 unsentMsg.InitWrite( unsentBuffer, sizeof( unsentBuffer ) ); 250 unsentMsg.BeginWriting(); 251 252 // Ack reliables if we need to (NOTE - We will send this ack on both the in-band and out-of-band channels) 253 if ( queuedReliableAck >= 0 ) { 254 idInnerPacketHeader header( PACKET_TYPE_RELIABLE_ACK, 0 ); 255 header.WriteToMsg( unsentMsg ); 256 unsentMsg.WriteLong( queuedReliableAck ); 257 queuedReliableAck = -1; 258 } 259 260 if ( isOOB ) { 261 if ( msg.GetSize() + unsentMsg.GetSize() > MAX_OOB_MSG_SIZE ) { // Fragmentation not allowed for out-of-band msg's 262 idLib::Printf("Out-of-band packet too large %i\n", unsentMsg.GetSize() ); 263 assert( 0 ); 264 return false; 265 } 266 // We don't need to worry about reliable for out of band packets 267 idInnerPacketHeader header( PACKET_TYPE_OOB, userData ); 268 header.WriteToMsg( unsentMsg ); 269 } else { 270 // Add reliable msg's here if this is an in-band packet 271 idInnerPacketHeader header( PACKET_TYPE_INBAND, reliable.Num() ); 272 header.WriteToMsg( unsentMsg ); 273 if ( reliable.Num() > 0 ) { 274 // Byte align unsentMsg 275 unsentMsg.WriteByteAlign(); 276 277 lzwCompressionData_t lzwData; 278 idLZWCompressor lzwCompressor( &lzwData ); 279 280 lzwCompressor.Start( unsentMsg.GetWriteData() + unsentMsg.GetSize() + 2, unsentMsg.GetRemainingSpace() - 2 ); // Write to compressed mem, not exceeding MAX_MSG_SIZE (+2 to reserve space for compressed size) 281 282 int uncompressedSize = 4; 283 lzwCompressor.WriteAgnostic< int >( reliable.ItemSequence( 0 ) ); 284 for ( int i = 0; i < reliable.Num(); i++ ) { 285 lzwCompressor.WriteAgnostic< uint16 >( reliable.ItemLength( i ) ); 286 lzwCompressor.Write( reliable.ItemData( i ), reliable.ItemLength( i ) ); 287 uncompressedSize += 2 + reliable.ItemLength( i ); 288 } 289 290 lzwCompressor.End(); 291 292 if ( lzwCompressor.IsOverflowed() ) { 293 idLib::Error( "reliable msg compressor overflow." ); 294 } 295 296 unsentMsg.WriteShort( lzwCompressor.Length() ); 297 unsentMsg.SetSize( unsentMsg.GetSize() + lzwCompressor.Length() ); 298 299 if ( net_showReliableCompression.GetBool() ) { 300 static int totalUncompressed = 0; 301 static int totalCompressed = 0; 302 303 totalUncompressed += uncompressedSize; 304 totalCompressed += lzwCompressor.Length(); 305 306 float ratio1 = (float)lzwCompressor.Length() / (float)uncompressedSize; 307 float ratio2 = (float)totalCompressed / (float)totalUncompressed; 308 309 idLib::Printf( "Uncompressed: %i, Compressed: %i, TotalUncompressed: %i, TotalCompressed: %i, (%2.2f / %2.2f )\n", uncompressedSize, lzwCompressor.Length(), totalUncompressed, totalCompressed, ratio1, ratio2 ); 310 } 311 } 312 } 313 314 // Fill up with actual msg 315 unsentMsg.WriteData( msg.GetReadData(), msg.GetSize() ); 316 317 if ( unsentMsg.GetSize() > MAX_PACKET_SIZE ) { 318 if ( isOOB ) { 319 idLib::Error( "oob msg's cannot fragment" ); 320 } 321 fragmentedSend = true; 322 } 323 324 return true; 325 } 326 327 /* 328 ================================================ 329 idPacketProcessor::GetSendFragment 330 ================================================ 331 */ 332 bool idPacketProcessor::GetSendFragment( const int time, sessionId_t sessionID, idBitMsg & outMsg ) { 333 lastSendTime = time; 334 335 if ( unsentMsg.GetRemainingData() <= 0 ) { 336 return false; // Nothing to send 337 } 338 339 outMsg.BeginWriting(); 340 341 342 idOuterPacketHeader outerHeader( sessionID ); 343 344 // Write outer packet header to the msg 345 outerHeader.WriteToMsg( outMsg ); 346 347 if ( !fragmentedSend ) { 348 // Simple case, no fragments to sent 349 outMsg.WriteData( unsentMsg.GetReadData(), unsentMsg.GetSize() ); 350 unsentMsg.SetSize( 0 ); 351 } else { 352 int currentSize = idMath::ClampInt( 0, MAX_PACKET_SIZE, unsentMsg.GetRemainingData() ); 353 assert( currentSize > 0 ); 354 assert( unsentMsg.GetRemainingData() - currentSize >= 0 ); 355 356 // See if we'll have more fragments once we subtract off how much we're about to write 357 bool moreFragments = ( unsentMsg.GetRemainingData() - currentSize > 0 ) ? true : false; 358 359 if ( !unsentMsg.GetReadCount() ) { // If this is the first read, then we know it's the first fragment 360 assert( moreFragments ); // If we have a first, we must have more or something went wrong 361 idInnerPacketHeader header( PACKET_TYPE_FRAGMENTED, FRAGMENT_START ); 362 header.WriteToMsg( outMsg ); 363 } else { 364 idInnerPacketHeader header( PACKET_TYPE_FRAGMENTED, moreFragments ? FRAGMENT_MIDDLE : FRAGMENT_END ); 365 header.WriteToMsg( outMsg ); 366 } 367 368 outMsg.WriteLong( fragmentSequence ); 369 outMsg.WriteData( unsentMsg.GetReadData() + unsentMsg.GetReadCount(), currentSize ); 370 unsentMsg.ReadData( NULL, currentSize ); 371 372 assert( moreFragments == unsentMsg.GetRemainingData() > 0 ); 373 fragmentedSend = moreFragments; 374 375 fragmentSequence++; // Advance sequence 376 377 fragmentAccumulator++; // update the counter for the net debug hud 378 } 379 380 381 // The caller needs to send this packet, so assume he did, and update rates 382 UpdateOutgoingRate( time, outMsg.GetSize() ); 383 384 return true; 385 } 386 387 /* 388 ================================================ 389 idPacketProcessor::ProcessIncoming 390 ================================================ 391 */ 392 int idPacketProcessor::ProcessIncoming( int time, sessionId_t expectedSessionID, idBitMsg & msg, idBitMsg & out, int & userData, const int peerNum ) { 393 assert( msg.GetSize() <= MAX_FINAL_PACKET_SIZE ); 394 395 UpdateIncomingRate( time, msg.GetSize() ); 396 397 398 idOuterPacketHeader outerHeader; 399 outerHeader.ReadFromMsg( msg ); 400 401 sessionId_t sessionID = outerHeader.GetSessionID(); 402 assert( sessionID == expectedSessionID ); 403 404 if ( !verify( sessionID != SESSION_ID_CONNECTIONLESS_PARTY && sessionID != SESSION_ID_CONNECTIONLESS_GAME && sessionID != SESSION_ID_CONNECTIONLESS_GAME_STATE ) ) { 405 idLib::Printf( "Expected non connectionless ID, but got a connectionless one\n" ); 406 return RETURN_TYPE_NONE; 407 } 408 409 if ( sessionID != expectedSessionID ) { 410 idLib::Printf( "Expected session id: %8x but got %8x instead\n", expectedSessionID, sessionID ); 411 return RETURN_TYPE_NONE; 412 } 413 414 int c,b; 415 msg.SaveReadState( c, b ); 416 417 idInnerPacketHeader header; 418 header.ReadFromMsg( msg ); 419 420 if ( header.Type() != PACKET_TYPE_FRAGMENTED ) { 421 // Non fragmented 422 msg.RestoreReadState( c, b ); // Reset since we took a byte to check the type 423 return FinalizeRead( msg, out, userData ); 424 } 425 426 // Decode fragmented packet 427 int readSequence = msg.ReadLong(); // Read sequence of fragment 428 429 if ( header.Value() == FRAGMENT_START ) { 430 msgWritePos = 0; // Reset msg reconstruction write pos 431 } else if ( fragmentSequence == -1 || readSequence != fragmentSequence + 1 ) { 432 droppedFrags++; 433 idLib::Printf( "Dropped Fragments - PeerNum: %i FragmentSeq: %i, ReadSeq: %i, Total: %i\n", peerNum, fragmentSequence, readSequence, droppedFrags ); 434 435 // If this is the middle or end, make sure we are reading in fragmentSequence 436 fragmentSequence = -1; 437 return RETURN_TYPE_NONE; // Out of sequence 438 } 439 fragmentSequence = readSequence; 440 assert( msg.GetRemainingData() > 0 ); 441 442 if ( !verify( msgWritePos + msg.GetRemainingData() < sizeof( msgBuffer ) ) ) { 443 idLib::Error( "ProcessIncoming: Fragmented msg buffer overflow." ); 444 } 445 446 memcpy( msgBuffer + msgWritePos, msg.GetReadData() + msg.GetReadCount(), msg.GetRemainingData() ); 447 msgWritePos += msg.GetRemainingData(); 448 449 if ( header.Value() == FRAGMENT_END ) { 450 // Done reconstructing the msg 451 idBitMsg msg( msgBuffer, sizeof( msgBuffer ) ); 452 msg.SetSize( msgWritePos ); 453 return FinalizeRead( msg, out, userData ); 454 } 455 456 if ( !verify( header.Value() == FRAGMENT_START || header.Value() == FRAGMENT_MIDDLE ) ) { 457 idLib::Printf( "ProcessIncoming: Invalid packet.\n" ); 458 } 459 460 // If we get here, this is part (either beginning or end) of a fragmented packet. 461 // We return RETURN_TYPE_NONE to let the caller know they don't need to do anything yet. 462 return RETURN_TYPE_NONE; 463 } 464 465 /* 466 ================================================ 467 idPacketProcessor::ProcessConnectionlessOutgoing 468 ================================================ 469 */ 470 bool idPacketProcessor::ProcessConnectionlessOutgoing( idBitMsg & msg, idBitMsg & out, int lobbyType, int userData ) { 471 sessionId_t sessionID = lobbyType + 1; 472 473 474 // Write outer header 475 idOuterPacketHeader outerHeader( sessionID ); 476 outerHeader.WriteToMsg( out ); 477 478 // Write inner header 479 idInnerPacketHeader header( PACKET_TYPE_OOB, userData ); 480 header.WriteToMsg( out ); 481 482 // Write msg 483 out.WriteData( msg.GetReadData(), msg.GetSize() ); 484 485 486 return true; 487 } 488 489 /* 490 ================================================ 491 idPacketProcessor::ProcessConnectionlessIncoming 492 ================================================ 493 */ 494 bool idPacketProcessor::ProcessConnectionlessIncoming( idBitMsg & msg, idBitMsg & out, int & userData ) { 495 496 idOuterPacketHeader outerHeader; 497 outerHeader.ReadFromMsg( msg ); 498 499 sessionId_t sessionID = outerHeader.GetSessionID(); 500 501 if ( sessionID != SESSION_ID_CONNECTIONLESS_PARTY && sessionID != SESSION_ID_CONNECTIONLESS_GAME && sessionID != SESSION_ID_CONNECTIONLESS_GAME_STATE ) { 502 // Not a connectionless msg (this can happen if a previously connected peer keeps sending data for whatever reason) 503 idLib::Printf( "ProcessConnectionlessIncoming: Invalid session ID - %d\n", sessionID ); 504 return false; 505 } 506 507 idInnerPacketHeader header; 508 header.ReadFromMsg( msg ); 509 510 if ( header.Type() != PACKET_TYPE_OOB ) { 511 idLib::Printf( "ProcessConnectionlessIncoming: header.Type() != PACKET_TYPE_OOB\n" ); 512 return false; // Only out-of-band packets supported for connectionless 513 } 514 515 userData = header.Value(); 516 517 out.BeginWriting(); 518 out.WriteData( msg.GetReadData() + msg.GetReadCount(), msg.GetRemainingData() ); 519 out.SetSize( msg.GetRemainingData() ); 520 521 return true; 522 } 523 524 /* 525 ================================================ 526 idPacketProcessor::GetSessionID 527 ================================================ 528 */ 529 idPacketProcessor::sessionId_t idPacketProcessor::GetSessionID( idBitMsg & msg ) { 530 sessionId_t sessionID; 531 int c,b; 532 msg.SaveReadState( c, b ); 533 // Read outer header 534 idOuterPacketHeader outerHeader; 535 outerHeader.ReadFromMsg( msg ); 536 537 // Get session ID 538 sessionID = outerHeader.GetSessionID(); 539 540 msg.RestoreReadState( c, b ); 541 return sessionID; 542 } 543 544 /* 545 ================================================ 546 idPacketProcessor::VerifyEmptyReliableQueue 547 ================================================ 548 */ 549 idCVar net_verifyReliableQueue( "net_verifyReliableQueue", "2", CVAR_INTEGER, "0: warn only, 1: error, 2: fixup, 3: fixup and verbose, 4: force test" ); 550 #define RELIABLE_VERBOSE if ( net_verifyReliableQueue.GetInteger() >= 3 ) idLib::Printf 551 void idPacketProcessor::VerifyEmptyReliableQueue( byte keepMsgBelowThis, byte replaceWithThisMsg ) { 552 if ( net_verifyReliableQueue.GetInteger() == 4 ) { 553 RELIABLE_VERBOSE( "pushing a fake game reliable\n" ); 554 const char * garbage = "garbage"; 555 QueueReliableMessage( keepMsgBelowThis + 4, (const byte *)garbage, 8 ); 556 QueueReliableMessage( replaceWithThisMsg, NULL, 0 ); 557 } 558 if ( reliable.Num() == 0 ) { 559 return; 560 } 561 if ( net_verifyReliableQueue.GetInteger() == 1 ) { 562 idLib::Error( "reliable queue is not empty: %d messages", reliable.Num() ); 563 return; 564 } 565 idLib::Warning( "reliable queue is not empty: %d messages", reliable.Num() ); 566 if ( net_verifyReliableQueue.GetInteger() == 0 ) { 567 return; 568 } 569 // drop some stuff that is potentially dangerous and should not transmit 570 idDataQueue< MAX_RELIABLE_QUEUE, MAX_MSG_SIZE > clean; 571 RELIABLE_VERBOSE( "rollback send sequence from %d to %d\n", reliableSequenceSend, reliable.ItemSequence( 0 ) ); 572 for ( int i = 0; i < reliable.Num(); i++ ) { 573 byte peek = reliable.ItemData( i )[0]; 574 if ( peek < keepMsgBelowThis ) { 575 RELIABLE_VERBOSE( "keeping %d\n", peek ); 576 clean.Append( reliable.ItemSequence( i ), reliable.ItemData( i ), reliable.ItemLength( i ) ); 577 } else { 578 // Replace with fake msg, so we retain itemsequence ordering. 579 // If we don't do this, it's possible we remove the last msg, then append a single msg before the next send, 580 // and the client may think he already received the msg, since his last reliableSequenceRecv could be greater than our 581 // reliableSequenceSend if he already received the group of reliables we are mucking with 582 clean.Append( reliable.ItemSequence( i ), &replaceWithThisMsg, 1 ); 583 RELIABLE_VERBOSE( "dropping %d\n", peek ); 584 } 585 } 586 587 assert( reliable.Num() == clean.Num() ); 588 589 reliable = clean; 590 }