bool CSteamNetworkConnectionBase::SNP_SendPacket( CConnectionTransport *pTransport, SendPacketContext_t &ctx )
{
	// Check calling conditions, and don't crash
	if ( !BStateIsActive() || m_senderState.m_mapInFlightPacketsByPktNum.empty() || !pTransport )
	{
		Assert( BStateIsActive() );
		Assert( !m_senderState.m_mapInFlightPacketsByPktNum.empty() );
		Assert( pTransport );
		return false;
	}

	SteamNetworkingMicroseconds usecNow = ctx.m_usecNow;

	// Get max size of plaintext we could send.
	// AES-GCM has a fixed size overhead, for the tag.
	// FIXME - but what we if we aren't using AES-GCM!
	int cbMaxPlaintextPayload = std::max( 0, ctx.m_cbMaxEncryptedPayload-k_cbSteamNetwokingSocketsEncrytionTagSize );
	cbMaxPlaintextPayload = std::min( cbMaxPlaintextPayload, m_cbMaxPlaintextPayloadSend );

	uint8 payload[ k_cbSteamNetworkingSocketsMaxPlaintextPayloadSend ];
	uint8 *pPayloadEnd = payload + cbMaxPlaintextPayload;
	uint8 *pPayloadPtr = payload;

	int nLogLevelPacketDecode = m_connectionConfig.m_LogLevel_PacketDecode.Get();
	SpewVerboseGroup( nLogLevelPacketDecode, "[%s] encode pkt %lld",
		GetDescription(),
		(long long)m_statsEndToEnd.m_nNextSendSequenceNumber );

	// Stop waiting frame
	pPayloadPtr = SNP_SerializeStopWaitingFrame( pPayloadPtr, pPayloadEnd, usecNow );
	if ( pPayloadPtr == nullptr )
		return false;

	// Get list of ack blocks we might want to serialize, and which
	// of those acks we really want to flush out right now.
	SNPAckSerializerHelper ackHelper;
	SNP_GatherAckBlocks( ackHelper, usecNow );

	#ifdef SNP_ENABLE_PACKETSENDLOG
		PacketSendLog *pLog = push_back_get_ptr( m_vecSendLog );
		pLog->m_usecTime = usecNow;
		pLog->m_cbPendingReliable = m_senderState.m_cbPendingReliable;
		pLog->m_cbPendingUnreliable = m_senderState.m_cbPendingUnreliable;
		pLog->m_nPacketGaps = len( m_receiverState.m_mapPacketGaps )-1;
		pLog->m_nAckBlocksNeeded = ackHelper.m_nBlocksNeedToAck;
		pLog->m_nPktNumNextPendingAck = m_receiverState.m_itPendingAck->first;
		pLog->m_usecNextPendingAckTime = m_receiverState.m_itPendingAck->second.m_usecWhenAckPrior;
		pLog->m_fltokens = m_senderState.m_flTokenBucket;
		pLog->m_nMaxPktRecv = m_statsEndToEnd.m_nMaxRecvPktNum;
		pLog->m_nMinPktNumToSendAcks = m_receiverState.m_nMinPktNumToSendAcks;
		pLog->m_nReliableSegmentsRetry = 0;
		pLog->m_nSegmentsSent = 0;
	#endif

	// How much space do we need to reserve for acks?
	int cbReserveForAcks = 0;
	if ( m_statsEndToEnd.m_nMaxRecvPktNum > 0 )
	{
		int cbPayloadRemainingForAcks = pPayloadEnd - pPayloadPtr;
		if ( cbPayloadRemainingForAcks >= SNPAckSerializerHelper::k_cbHeaderSize )
		{
			cbReserveForAcks = SNPAckSerializerHelper::k_cbHeaderSize;
			int n = 3; // Assume we want to send a handful
			n = std::max( n, ackHelper.m_nBlocksNeedToAck ); // But if we have blocks that need to be flushed now, try to fit all of them
			n = std::min( n, ackHelper.m_nBlocks ); // Cannot send more than we actually have
			while ( n > 0 )
			{
				--n;
				if ( ackHelper.m_arBlocks[n].m_cbTotalEncodedSize <= cbPayloadRemainingForAcks )
				{
					cbReserveForAcks = ackHelper.m_arBlocks[n].m_cbTotalEncodedSize;
					break;
				}
			}
		}
	}

	// Check if we are actually going to send data in this packet
	if (
		m_senderState.m_flTokenBucket < 0.0 // No bandwidth available.  (Presumably this is a relatively rare out-of-band connectivity check, etc)  FIXME should we use a different token bucket per transport?
		|| !BStateIsConnectedForWirePurposes() // not actually in a connection stats where we should be sending real data yet
		|| pTransport != m_pTransport // transport is not the selected transport
	) {

		// Serialize some acks, if we want to
		if ( cbReserveForAcks > 0 )
		{
			// But if we're going to send any acks, then try to send as many
			// as possible, not just the bare minimum.
			pPayloadPtr = SNP_SerializeAckBlocks( ackHelper, pPayloadPtr, pPayloadEnd, usecNow );
			if ( pPayloadPtr == nullptr )
				return false; // bug!  Abort

			// We don't need to serialize any more acks
			cbReserveForAcks = 0;
		}

		// Truncate the buffer, don't try to fit any data
		// !SPEED! - instead of doing this, we could just put all of the segment code below
		// in an else() block.
		pPayloadEnd = pPayloadPtr;
	}

	int64 nLastReliableStreamPosEnd = 0;
	int cbBytesRemainingForSegments = pPayloadEnd - pPayloadPtr - cbReserveForAcks;
	vstd::small_vector<EncodedSegment,8> vecSegments;

	// If we need to retry any reliable data, then try to put that in first.
	// Bail if we only have a tiny sliver of data left
	while ( !m_senderState.m_listReadyRetryReliableRange.empty() && cbBytesRemainingForSegments > 2 )
	{
		auto h = m_senderState.m_listReadyRetryReliableRange.begin();

		// Start a reliable segment
		EncodedSegment &seg = *push_back_get_ptr( vecSegments );
		seg.SetupReliable( h->second, h->first.m_nBegin, h->first.m_nEnd, nLastReliableStreamPosEnd );
		int cbSegTotalWithoutSizeField = seg.m_cbHdr + seg.m_cbSegSize;
		if ( cbSegTotalWithoutSizeField > cbBytesRemainingForSegments )
		{
			// This one won't fit.
			vecSegments.pop_back();

			// FIXME If there's a decent amount of space left in this packet, it might
			// be worthwhile to send what we can.  Right now, once we send a reliable range,
			// we always retry exactly that range.  The only complication would be when we
			// receive an ack, we would need to be aware that the acked ranges might not
			// exactly match up with the ranges that we sent.  Actually this shouldn't
			// be that big of a deal.  But for now let's always retry the exact ranges that
			// things got chopped up during the initial send.

			// This should only happen if we have already fit some data in, or
			// the caller asked us to see what we could squeeze into a smaller
			// packet, or we need to serialized a bunch of acks.  If this is an
			// opportunity to fill a normal packet and we fail on the first segment,
			// we will never make progress and we are hosed!
			AssertMsg2(
				nLastReliableStreamPosEnd > 0
				|| cbMaxPlaintextPayload < m_cbMaxPlaintextPayloadSend
				|| ( cbReserveForAcks > 15 && ackHelper.m_nBlocksNeedToAck > 8 ),
				"We cannot fit reliable segment, need %d bytes, only %d remaining", cbSegTotalWithoutSizeField, cbBytesRemainingForSegments
			);

			// Don't try to put more stuff in the packet, even if we have room.  We're
			// already having to retry, so this data is already delayed.  If we skip ahead
			// and put more into this packet, that's just extending the time until we can send
			// the next packet.
			break;
		}

		// If we only have a sliver left, then don't try to fit any more.
		cbBytesRemainingForSegments -= cbSegTotalWithoutSizeField;
		nLastReliableStreamPosEnd = h->first.m_nEnd;

		// Assume for now this won't be the last segment, in which case we will also need
		// the byte for the size field.
		// NOTE: This might cause cbPayloadBytesRemaining to go negative by one!  I know
		// that seems weird, but it actually keeps the logic below simpler.
		cbBytesRemainingForSegments -= 1;

		// Remove from retry list.  (We'll add to the in-flight list later)
		m_senderState.m_listReadyRetryReliableRange.erase( h );

		#ifdef SNP_ENABLE_PACKETSENDLOG
			++pLog->m_nReliableSegmentsRetry;
		#endif
	}

	// Did we retry everything we needed to?  If not, then don't try to send new stuff,
	// before we send those retries.
	if ( m_senderState.m_listReadyRetryReliableRange.empty() )
	{

		// OK, check the outgoing messages, and send as much stuff as we can cram in there
		int64 nLastMsgNum = 0;
		while ( cbBytesRemainingForSegments > 4 )
		{
			if ( m_senderState.m_messagesQueued.empty() )
			{
				m_senderState.m_cbCurrentSendMessageSent = 0;
				break;
			}
			CSteamNetworkingMessage *pSendMsg = m_senderState.m_messagesQueued.m_pFirst;
			Assert( m_senderState.m_cbCurrentSendMessageSent < pSendMsg->m_cbSize );

			// Start a new segment
			EncodedSegment &seg = *push_back_get_ptr( vecSegments );

			// Reliable?
			bool bLastSegment = false;
			if ( pSendMsg->SNPSend_IsReliable() )
			{

				// FIXME - Coalesce adjacent reliable messages ranges

				int64 nBegin = pSendMsg->SNPSend_ReliableStreamPos() + m_senderState.m_cbCurrentSendMessageSent;

				// How large would we like this segment to be,
				// ignoring how much space is left in the packet.
				// We limit the size of reliable segments, to make
				// sure that we don't make an excessively large
				// one and then have a hard time retrying it later.
				int cbDesiredSegSize = pSendMsg->m_cbSize - m_senderState.m_cbCurrentSendMessageSent;
				if ( cbDesiredSegSize > m_cbMaxReliableMessageSegment )
				{
					cbDesiredSegSize = m_cbMaxReliableMessageSegment;
					bLastSegment = true;
				}

				int64 nEnd = nBegin + cbDesiredSegSize;
				seg.SetupReliable( pSendMsg, nBegin, nEnd, nLastReliableStreamPosEnd );

				// If we encode subsequent 
				nLastReliableStreamPosEnd = nEnd;
			}
			else
			{
				seg.SetupUnreliable( pSendMsg, m_senderState.m_cbCurrentSendMessageSent, nLastMsgNum );
			}

			// Can't fit the whole thing?
			if ( bLastSegment || seg.m_cbHdr + seg.m_cbSegSize > cbBytesRemainingForSegments )
			{

				// Check if we have enough room to send anything worthwhile.
				// Don't send really tiny silver segments at the very end of a packet.  That sort of fragmentation
				// just makes it more likely for something to drop.  Our goal is to reduce the number of packets
				// just as much as the total number of bytes, so if we're going to have to send another packet
				// anyway, don't send a little sliver of a message at the beginning of a packet
				// We need to finish the header by this point if we're going to send anything
				int cbMinSegDataSizeToSend = std::min( 16, seg.m_cbSegSize );
				if ( seg.m_cbHdr + cbMinSegDataSizeToSend > cbBytesRemainingForSegments )
				{
					// Don't send this segment now.
					vecSegments.pop_back();
					break;
				}

				#ifdef SNP_ENABLE_PACKETSENDLOG
					++pLog->m_nSegmentsSent;
				#endif

				// Truncate, and leave the message in the queue
				seg.m_cbSegSize = std::min( seg.m_cbSegSize, cbBytesRemainingForSegments - seg.m_cbHdr );
				m_senderState.m_cbCurrentSendMessageSent += seg.m_cbSegSize;
				Assert( m_senderState.m_cbCurrentSendMessageSent < pSendMsg->m_cbSize );
				cbBytesRemainingForSegments -= seg.m_cbHdr + seg.m_cbSegSize;
				break;
			}

			// The whole message fit (perhaps exactly, without the size byte)
			// Reset send pointer for the next message
			Assert( m_senderState.m_cbCurrentSendMessageSent + seg.m_cbSegSize == pSendMsg->m_cbSize );
			m_senderState.m_cbCurrentSendMessageSent = 0;

			// Remove message from queue,w e have transfered ownership to the segment and will
			// dispose of the message when we serialize the segments
			m_senderState.m_messagesQueued.pop_front();

			// Consume payload bytes
			cbBytesRemainingForSegments -= seg.m_cbHdr + seg.m_cbSegSize;

			// Assume for now this won't be the last segment, in which case we will also need the byte for the size field.
			// NOTE: This might cause cbPayloadBytesRemaining to go negative by one!  I know that seems weird, but it actually
			// keeps the logic below simpler.
			cbBytesRemainingForSegments -= 1;

			// Update various accounting, depending on reliable or unreliable
			if ( pSendMsg->SNPSend_IsReliable() )
			{
				// Reliable segments advance the current message number.
				// NOTE: If we coalesce adjacent reliable segments, this will probably need to be adjusted
				if ( nLastMsgNum > 0 )
					++nLastMsgNum;

				// Go ahead and add us to the end of the list of unacked messages
				m_senderState.m_unackedReliableMessages.push_back( seg.m_pMsg );
			}
			else
			{
				nLastMsgNum = pSendMsg->m_nMessageNumber;

				// Set the "This is the last segment in this message" header bit
				seg.m_hdr[0] |= 0x20;
			}
		}
	}

	// Now we know how much space we need for the segments.  If we asked to reserve
	// space for acks, we should have at least that much.  But we might have more.
	// Serialize acks, as much as will fit.  If we are badly fragmented and we have
	// the space, it's better to keep sending acks over and over to try to clear
	// it out as fast as possible.
	if ( cbReserveForAcks > 0 )
	{

		// If we didn't use all the space for data, that's more we could use for acks
		int cbAvailForAcks = cbReserveForAcks;
		if ( cbBytesRemainingForSegments > 0 )
			cbAvailForAcks += cbBytesRemainingForSegments;
		uint8 *pAckEnd = pPayloadPtr + cbAvailForAcks;
		Assert( pAckEnd <= pPayloadEnd );

		uint8 *pAfterAcks = SNP_SerializeAckBlocks( ackHelper, pPayloadPtr, pAckEnd, usecNow );
		if ( pAfterAcks == nullptr )
			return false; // bug!  Abort

		int cbAckBytesWritten = pAfterAcks - pPayloadPtr;
		if ( cbAckBytesWritten > cbReserveForAcks )
		{
			// We used more space for acks than was strictly reserved.
			// Update space remaining for data segments.  We should have the room!
			cbBytesRemainingForSegments -= ( cbAckBytesWritten - cbReserveForAcks );
			Assert( cbBytesRemainingForSegments >= -1 ); // remember we might go over by one byte
		}
		else
		{
			Assert( cbAckBytesWritten == cbReserveForAcks ); // The code above reserves space very carefuly.  So if we reserve it, we should fill it!
		}

		pPayloadPtr = pAfterAcks;
	}

	// We are gonna send a packet.  Start filling out an entry so that when it's acked (or nacked)
	// we can know what to do.
	Assert( m_senderState.m_mapInFlightPacketsByPktNum.lower_bound( m_statsEndToEnd.m_nNextSendSequenceNumber ) == m_senderState.m_mapInFlightPacketsByPktNum.end() );
	std::pair<int64,SNPInFlightPacket_t> pairInsert( m_statsEndToEnd.m_nNextSendSequenceNumber, SNPInFlightPacket_t{ usecNow, false, pTransport, {} } );
	SNPInFlightPacket_t &inFlightPkt = pairInsert.second;

	// We might have gone over exactly one byte, because we counted the size byte of the last
	// segment, which doesn't actually need to be sent
	Assert( cbBytesRemainingForSegments >= 0 || ( cbBytesRemainingForSegments == -1 && vecSegments.size() > 0 ) );

	// OK, now go through and actually serialize the segments
	int nSegments = len( vecSegments );
	for ( int idx = 0 ; idx < nSegments ; ++idx )
	{
		EncodedSegment &seg = vecSegments[ idx ];

		// Check if this message is still sitting in the queue.  (If so, it has to be the first one!)
		bool bStillInQueue = ( seg.m_pMsg == m_senderState.m_messagesQueued.m_pFirst );

		// Finish the segment size byte
		if ( idx < nSegments-1 )
		{
			// Stash upper 3 bits into the header
			int nUpper3Bits = ( seg.m_cbSegSize>>8 );
			Assert( nUpper3Bits <= 4 ); // The values 5 and 6 are reserved and shouldn't be needed due to the MTU we support
			seg.m_hdr[0] |= nUpper3Bits;

			// And the lower 8 bits follow the other fields
			seg.m_hdr[ seg.m_cbHdr++ ] = uint8( seg.m_cbSegSize );
		}
		else
		{
			// Set "no explicit size field included, segment extends to end of packet"
			seg.m_hdr[0] |= 7;
		}

		// Double-check that we didn't overflow
		Assert( seg.m_cbHdr <= seg.k_cbMaxHdr );

		// Copy the header
		memcpy( pPayloadPtr, seg.m_hdr, seg.m_cbHdr ); pPayloadPtr += seg.m_cbHdr;
		Assert( pPayloadPtr+seg.m_cbSegSize <= pPayloadEnd );

		// Reliable?
		if ( seg.m_pMsg->SNPSend_IsReliable() )
		{
			// We should never encode an empty range of the stream, that is worthless.
			// (Even an empty reliable message requires some framing in the stream.)
			Assert( seg.m_cbSegSize > 0 );

			// Copy the unreliable segment into the packet.  Does the portion we are serializing
			// begin in the header?
			if ( seg.m_nOffset < seg.m_pMsg->m_cbSNPSendReliableHeader )
			{
				int cbCopyHdr = std::min( seg.m_cbSegSize, seg.m_pMsg->m_cbSNPSendReliableHeader - seg.m_nOffset );

				memcpy( pPayloadPtr, seg.m_pMsg->SNPSend_ReliableHeader() + seg.m_nOffset, cbCopyHdr );
				pPayloadPtr += cbCopyHdr;

				int cbCopyBody = seg.m_cbSegSize - cbCopyHdr;
				if ( cbCopyBody > 0 )
				{
					memcpy( pPayloadPtr, seg.m_pMsg->m_pData, cbCopyBody );
					pPayloadPtr += cbCopyBody;
				}
			}
			else
			{
				// This segment is entirely from the message body
				memcpy( pPayloadPtr, (char*)seg.m_pMsg->m_pData + seg.m_nOffset - seg.m_pMsg->m_cbSNPSendReliableHeader, seg.m_cbSegSize );
				pPayloadPtr += seg.m_cbSegSize;
			}


			// Remember that this range is in-flight
			SNPRange_t range;
			range.m_nBegin = seg.m_pMsg->SNPSend_ReliableStreamPos() + seg.m_nOffset;
			range.m_nEnd = range.m_nBegin + seg.m_cbSegSize;

			// Ranges of the reliable stream that have not been acked should either be
			// in flight, or queued for retry.  Make sure this range is not already in
			// either state.
			Assert( !HasOverlappingRange( range, m_senderState.m_listInFlightReliableRange ) );
			Assert( !HasOverlappingRange( range, m_senderState.m_listReadyRetryReliableRange ) );

			// Spew
			SpewDebugGroup( nLogLevelPacketDecode, "[%s]   encode pkt %lld reliable msg %lld offset %d+%d=%d range [%lld,%lld)\n",
				GetDescription(), (long long)m_statsEndToEnd.m_nNextSendSequenceNumber, (long long)seg.m_pMsg->m_nMessageNumber,
				seg.m_nOffset, seg.m_cbSegSize, seg.m_nOffset+seg.m_cbSegSize,
				(long long)range.m_nBegin, (long long)range.m_nEnd );

			// Add to table of in-flight reliable ranges
			m_senderState.m_listInFlightReliableRange[ range ] = seg.m_pMsg;

			// Remember that this packet contained that range
			inFlightPkt.m_vecReliableSegments.push_back( range );

			// Less reliable data pending
			m_senderState.m_cbPendingReliable -= seg.m_cbSegSize;
			Assert( m_senderState.m_cbPendingReliable >= 0 );
		}
		else
		{
			// We should only encode an empty segment if the message itself is empty
			Assert( seg.m_cbSegSize > 0 || ( seg.m_cbSegSize == 0 && seg.m_pMsg->m_cbSize == 0 ) );

			// Check some stuff
			Assert( bStillInQueue == ( seg.m_nOffset + seg.m_cbSegSize < seg.m_pMsg->m_cbSize ) ); // If we ended the message, we should have removed it from the queue
			Assert( bStillInQueue == ( ( seg.m_hdr[0] & 0x20 ) == 0 ) );
			Assert( bStillInQueue || seg.m_pMsg->m_links.m_pNext == nullptr ); // If not in the queue, we should be detached
			Assert( seg.m_pMsg->m_links.m_pPrev == nullptr ); // We should either be at the head of the queue, or detached

			// Copy the unreliable segment into the packet
			memcpy( pPayloadPtr, (char*)seg.m_pMsg->m_pData + seg.m_nOffset, seg.m_cbSegSize );
			pPayloadPtr += seg.m_cbSegSize;

			// Spew
			SpewDebugGroup( nLogLevelPacketDecode, "[%s]   encode pkt %lld unreliable msg %lld offset %d+%d=%d\n",
				GetDescription(), (long long)m_statsEndToEnd.m_nNextSendSequenceNumber, (long long)seg.m_pMsg->m_nMessageNumber,
				seg.m_nOffset, seg.m_cbSegSize, seg.m_nOffset+seg.m_cbSegSize );

			// Less unreliable data pending
			m_senderState.m_cbPendingUnreliable -= seg.m_cbSegSize;
			Assert( m_senderState.m_cbPendingUnreliable >= 0 );

			// Done with this message?  Clean up
			if ( !bStillInQueue )
				seg.m_pMsg->Release();
		}
	}

	// One last check for overflow
	Assert( pPayloadPtr <= pPayloadEnd );
	int cbPlainText = pPayloadPtr - payload;
	if ( cbPlainText > cbMaxPlaintextPayload )
	{
		AssertMsg1( false, "Payload exceeded max size of %d\n", cbMaxPlaintextPayload );
		return 0;
	}

	// OK, we have a plaintext payload.  Encrypt and send it.
	// What cipher are we using?
	int nBytesSent = 0;
	switch ( m_eNegotiatedCipher )
	{
		default:
			AssertMsg1( false, "Bogus cipher %d", m_eNegotiatedCipher );
			break;

		case k_ESteamNetworkingSocketsCipher_NULL:
		{

			// No encryption!
			// Ask current transport to deliver it
			nBytesSent = pTransport->SendEncryptedDataChunk( payload, cbPlainText, ctx );
		}
		break;

		case k_ESteamNetworkingSocketsCipher_AES_256_GCM:
		{

			Assert( m_bCryptKeysValid );

			// Adjust the IV by the packet number
			*(uint64 *)&m_cryptIVSend.m_buf += LittleQWord( m_statsEndToEnd.m_nNextSendSequenceNumber );

			// Encrypt the chunk
			uint8 arEncryptedChunk[ k_cbSteamNetworkingSocketsMaxEncryptedPayloadSend + 64 ]; // Should not need pad
			uint32 cbEncrypted = sizeof(arEncryptedChunk);
			DbgVerify( m_cryptContextSend.Encrypt(
				payload, cbPlainText, // plaintext
				m_cryptIVSend.m_buf, // IV
				arEncryptedChunk, &cbEncrypted, // output
				nullptr, 0 // no AAD
			) );

			//SpewMsg( "Send encrypt IV %llu + %02x%02x%02x%02x  encrypted %d %02x%02x%02x%02x\n",
			//	*(uint64 *)&m_cryptIVSend.m_buf,
			//	m_cryptIVSend.m_buf[8], m_cryptIVSend.m_buf[9], m_cryptIVSend.m_buf[10], m_cryptIVSend.m_buf[11],
			//	cbEncrypted,
			//	arEncryptedChunk[0], arEncryptedChunk[1], arEncryptedChunk[2],arEncryptedChunk[3]
			//);

			// Restore the IV to the base value
			*(uint64 *)&m_cryptIVSend.m_buf -= LittleQWord( m_statsEndToEnd.m_nNextSendSequenceNumber );

			Assert( (int)cbEncrypted >= cbPlainText );
			Assert( (int)cbEncrypted <= k_cbSteamNetworkingSocketsMaxEncryptedPayloadSend ); // confirm that pad above was not necessary and we never exceed k_nMaxSteamDatagramTransportPayload, even after encrypting

			// Ask current transport to deliver it
			nBytesSent = pTransport->SendEncryptedDataChunk( arEncryptedChunk, cbEncrypted, ctx );
		}
	}
	if ( nBytesSent <= 0 )
		return false;

	// We sent a packet.  Track it
	auto pairInsertResult = m_senderState.m_mapInFlightPacketsByPktNum.insert( pairInsert );
	Assert( pairInsertResult.second ); // We should have inserted a new element, not updated an existing element

	// If we sent any reliable data, we should expect a reply
	if ( !inFlightPkt.m_vecReliableSegments.empty() )
	{
		m_statsEndToEnd.TrackSentMessageExpectingSeqNumAck( usecNow, true );
		// FIXME - should let transport know
	}

	// If we aren't already tracking anything to timeout, then this is the next one.
	if ( m_senderState.m_itNextInFlightPacketToTimeout == m_senderState.m_mapInFlightPacketsByPktNum.end() )
		m_senderState.m_itNextInFlightPacketToTimeout = pairInsertResult.first;

	#ifdef SNP_ENABLE_PACKETSENDLOG
		pLog->m_cbSent = nBytesSent;
	#endif

	// We spent some tokens
	m_senderState.m_flTokenBucket -= (float)nBytesSent;
	return true;
}
bool CSteamNetworkConnectionBase::SNP_SendPacket( CConnectionTransport *pTransport, SendPacketContext_t &ctx )
{
	 
	if ( !BStateIsActive() || m_senderState.m_mapInFlightPacketsByPktNum.empty() || !pTransport )
	{
		Assert( BStateIsActive() );
		Assert( !m_senderState.m_mapInFlightPacketsByPktNum.empty() );
		Assert( pTransport );
		return false;
	}

	SteamNetworkingMicroseconds usecNow = ctx.m_usecNow;

	 
	 
	 
	int cbMaxPlaintextPayload = std::max( 0, ctx.m_cbMaxEncryptedPayload-k_cbSteamNetwokingSocketsEncrytionTagSize );
	cbMaxPlaintextPayload = std::min( cbMaxPlaintextPayload, m_cbMaxPlaintextPayloadSend );

	uint8 payload[ k_cbSteamNetworkingSocketsMaxPlaintextPayloadSend ];
	uint8 *pPayloadEnd = payload + cbMaxPlaintextPayload;
	uint8 *pPayloadPtr = payload;

	int nLogLevelPacketDecode = m_connectionConfig.m_LogLevel_PacketDecode.Get();
	SpewVerboseGroup( nLogLevelPacketDecode, "[%s] encode pkt %lld",
		GetDescription(),
		(long long)m_statsEndToEnd.m_nNextSendSequenceNumber );

	 
	pPayloadPtr = SNP_SerializeStopWaitingFrame( pPayloadPtr, pPayloadEnd, usecNow );
	if ( pPayloadPtr == nullptr )
		return false;

	 
	 
	SNPAckSerializerHelper ackHelper;
	SNP_GatherAckBlocks( ackHelper, usecNow );

	#ifdef SNP_ENABLE_PACKETSENDLOG
		PacketSendLog *pLog = push_back_get_ptr( m_vecSendLog );
		pLog->m_usecTime = usecNow;
		pLog->m_cbPendingReliable = m_senderState.m_cbPendingReliable;
		pLog->m_cbPendingUnreliable = m_senderState.m_cbPendingUnreliable;
		pLog->m_nPacketGaps = len( m_receiverState.m_mapPacketGaps )-1;
		pLog->m_nAckBlocksNeeded = ackHelper.m_nBlocksNeedToAck;
		pLog->m_nPktNumNextPendingAck = m_receiverState.m_itPendingAck->first;
		pLog->m_usecNextPendingAckTime = m_receiverState.m_itPendingAck->second.m_usecWhenAckPrior;
		pLog->m_fltokens = m_senderState.m_flTokenBucket;
		pLog->m_nMaxPktRecv = m_statsEndToEnd.m_nMaxRecvPktNum;
		pLog->m_nMinPktNumToSendAcks = m_receiverState.m_nMinPktNumToSendAcks;
		pLog->m_nReliableSegmentsRetry = 0;
		pLog->m_nSegmentsSent = 0;
	#endif

	 
	int cbReserveForAcks = 0;
	if ( m_statsEndToEnd.m_nMaxRecvPktNum > 0 )
	{
		int cbPayloadRemainingForAcks = pPayloadEnd - pPayloadPtr;
		if ( cbPayloadRemainingForAcks >= SNPAckSerializerHelper::k_cbHeaderSize )
		{
			cbReserveForAcks = SNPAckSerializerHelper::k_cbHeaderSize;
			int n = 3;  
			n = std::max( n, ackHelper.m_nBlocksNeedToAck );  
			n = std::min( n, ackHelper.m_nBlocks );  
			while ( n > 0 )
			{
				--n;
				if ( ackHelper.m_arBlocks[n].m_cbTotalEncodedSize <= cbPayloadRemainingForAcks )
				{
					cbReserveForAcks = ackHelper.m_arBlocks[n].m_cbTotalEncodedSize;
					break;
				}
			}
		}
	}

	 
	if (
		m_senderState.m_flTokenBucket < 0.0  
		|| !BStateIsConnectedForWirePurposes()  
		|| pTransport != m_pTransport  
	) {

		 
		if ( cbReserveForAcks > 0 )
		{
			 
			 
			pPayloadPtr = SNP_SerializeAckBlocks( ackHelper, pPayloadPtr, pPayloadEnd, usecNow );
			if ( pPayloadPtr == nullptr )
				return false;  

			 
			cbReserveForAcks = 0;
		}

		 
		 
		 
		pPayloadEnd = pPayloadPtr;
	}

	int64 nLastReliableStreamPosEnd = 0;
	int cbBytesRemainingForSegments = pPayloadEnd - pPayloadPtr - cbReserveForAcks;
	vstd::small_vector<EncodedSegment,8> vecSegments;

	 
	 
	while ( !m_senderState.m_listReadyRetryReliableRange.empty() && cbBytesRemainingForSegments > 2 )
	{
		auto h = m_senderState.m_listReadyRetryReliableRange.begin();

		 
		EncodedSegment &seg = *push_back_get_ptr( vecSegments );
		seg.SetupReliable( h->second, h->first.m_nBegin, h->first.m_nEnd, nLastReliableStreamPosEnd );
		int cbSegTotalWithoutSizeField = seg.m_cbHdr + seg.m_cbSegSize;
		if ( cbSegTotalWithoutSizeField > cbBytesRemainingForSegments )
		{
			 
			vecSegments.pop_back();

			 
			 
			 
			 
			 
			 
			 

			 
			 
			 
			 
			 
			AssertMsg2(
				nLastReliableStreamPosEnd > 0
				|| cbMaxPlaintextPayload < m_cbMaxPlaintextPayloadSend
				|| ( cbReserveForAcks > 15 && ackHelper.m_nBlocksNeedToAck > 8 ),
				"We cannot fit reliable segment, need %d bytes, only %d remaining", cbSegTotalWithoutSizeField, cbBytesRemainingForSegments
			);

			 
			 
			 
			 
			break;
		}

		 
		cbBytesRemainingForSegments -= cbSegTotalWithoutSizeField;
		nLastReliableStreamPosEnd = h->first.m_nEnd;

		 
		 
		 
		 
		cbBytesRemainingForSegments -= 1;

		 
		m_senderState.m_listReadyRetryReliableRange.erase( h );

		#ifdef SNP_ENABLE_PACKETSENDLOG
			++pLog->m_nReliableSegmentsRetry;
		#endif
	}

	 
	 
	if ( m_senderState.m_listReadyRetryReliableRange.empty() )
	{

		 
		int64 nLastMsgNum = 0;
		while ( cbBytesRemainingForSegments > 4 )
		{
			if ( m_senderState.m_messagesQueued.empty() )
			{
				m_senderState.m_cbCurrentSendMessageSent = 0;
				break;
			}
			CSteamNetworkingMessage *pSendMsg = m_senderState.m_messagesQueued.m_pFirst;
			Assert( m_senderState.m_cbCurrentSendMessageSent < pSendMsg->m_cbSize );

			 
			EncodedSegment &seg = *push_back_get_ptr( vecSegments );

			 
			bool bLastSegment = false;
			if ( pSendMsg->SNPSend_IsReliable() )
			{

				 

				int64 nBegin = pSendMsg->SNPSend_ReliableStreamPos() + m_senderState.m_cbCurrentSendMessageSent;

				 
				 
				 
				 
				 
				int cbDesiredSegSize = pSendMsg->m_cbSize - m_senderState.m_cbCurrentSendMessageSent;
				if ( cbDesiredSegSize > m_cbMaxReliableMessageSegment )
				{
					cbDesiredSegSize = m_cbMaxReliableMessageSegment;
					bLastSegment = true;
				}

				int64 nEnd = nBegin + cbDesiredSegSize;
				seg.SetupReliable( pSendMsg, nBegin, nEnd, nLastReliableStreamPosEnd );

				 
				nLastReliableStreamPosEnd = nEnd;
			}
			else
			{
				seg.SetupUnreliable( pSendMsg, m_senderState.m_cbCurrentSendMessageSent, nLastMsgNum );
			}

			 
			if ( bLastSegment || seg.m_cbHdr + seg.m_cbSegSize > cbBytesRemainingForSegments )
			{

				 
				 
				 
				 
				 
				 
				int cbMinSegDataSizeToSend = std::min( 16, seg.m_cbSegSize );
				if ( seg.m_cbHdr + cbMinSegDataSizeToSend > cbBytesRemainingForSegments )
				{
					 
					vecSegments.pop_back();
					break;
				}

				#ifdef SNP_ENABLE_PACKETSENDLOG
					++pLog->m_nSegmentsSent;
				#endif

				 
				seg.m_cbSegSize = std::min( seg.m_cbSegSize, cbBytesRemainingForSegments - seg.m_cbHdr );
				m_senderState.m_cbCurrentSendMessageSent += seg.m_cbSegSize;
				Assert( m_senderState.m_cbCurrentSendMessageSent < pSendMsg->m_cbSize );
				cbBytesRemainingForSegments -= seg.m_cbHdr + seg.m_cbSegSize;
				break;
			}

			 
			 
			Assert( m_senderState.m_cbCurrentSendMessageSent + seg.m_cbSegSize == pSendMsg->m_cbSize );
			m_senderState.m_cbCurrentSendMessageSent = 0;

			 
			 
			m_senderState.m_messagesQueued.pop_front();

			 
			cbBytesRemainingForSegments -= seg.m_cbHdr + seg.m_cbSegSize;

			 
			 
			 
			cbBytesRemainingForSegments -= 1;

			 
			if ( pSendMsg->SNPSend_IsReliable() )
			{
				 
				 
				if ( nLastMsgNum > 0 )
					++nLastMsgNum;

				 
				m_senderState.m_unackedReliableMessages.push_back( seg.m_pMsg );
			}
			else
			{
				nLastMsgNum = pSendMsg->m_nMessageNumber;

				 
				seg.m_hdr[0] |= 0x20;
			}
		}
	}

	 
	 
	 
	 
	 
	if ( cbReserveForAcks > 0 )
	{

		 
		int cbAvailForAcks = cbReserveForAcks;
		if ( cbBytesRemainingForSegments > 0 )
			cbAvailForAcks += cbBytesRemainingForSegments;
		uint8 *pAckEnd = pPayloadPtr + cbAvailForAcks;
		Assert( pAckEnd <= pPayloadEnd );

		uint8 *pAfterAcks = SNP_SerializeAckBlocks( ackHelper, pPayloadPtr, pAckEnd, usecNow );
		if ( pAfterAcks == nullptr )
			return false;  

		int cbAckBytesWritten = pAfterAcks - pPayloadPtr;
		if ( cbAckBytesWritten > cbReserveForAcks )
		{
			 
			 
			cbBytesRemainingForSegments -= ( cbAckBytesWritten - cbReserveForAcks );
			Assert( cbBytesRemainingForSegments >= -1 );  
		}
		else
		{
			Assert( cbAckBytesWritten == cbReserveForAcks );  
		}

		pPayloadPtr = pAfterAcks;
	}

	 
	 
	Assert( m_senderState.m_mapInFlightPacketsByPktNum.lower_bound( m_statsEndToEnd.m_nNextSendSequenceNumber ) == m_senderState.m_mapInFlightPacketsByPktNum.end() );
	std::pair<int64,SNPInFlightPacket_t> pairInsert( m_statsEndToEnd.m_nNextSendSequenceNumber, SNPInFlightPacket_t{ usecNow, false, pTransport, {} } );
	SNPInFlightPacket_t &inFlightPkt = pairInsert.second;

	 
	 
	Assert( cbBytesRemainingForSegments >= 0 || ( cbBytesRemainingForSegments == -1 && vecSegments.size() > 0 ) );

	 
	int nSegments = len( vecSegments );
	for ( int idx = 0 ; idx < nSegments ; ++idx )
	{
		EncodedSegment &seg = vecSegments[ idx ];

		 
		bool bStillInQueue = ( seg.m_pMsg == m_senderState.m_messagesQueued.m_pFirst );

		 
		if ( idx < nSegments-1 )
		{
			 
			int nUpper3Bits = ( seg.m_cbSegSize>>8 );
			Assert( nUpper3Bits <= 4 );  
			seg.m_hdr[0] |= nUpper3Bits;

			 
			seg.m_hdr[ seg.m_cbHdr++ ] = uint8( seg.m_cbSegSize );
		}
		else
		{
			 
			seg.m_hdr[0] |= 7;
		}

		 
		Assert( seg.m_cbHdr <= seg.k_cbMaxHdr );

		 
		memcpy( pPayloadPtr, seg.m_hdr, seg.m_cbHdr ); pPayloadPtr += seg.m_cbHdr;
		Assert( pPayloadPtr+seg.m_cbSegSize <= pPayloadEnd );

		 
		if ( seg.m_pMsg->SNPSend_IsReliable() )
		{
			 
			 
			Assert( seg.m_cbSegSize > 0 );

			 
			 
			if ( seg.m_nOffset < seg.m_pMsg->m_cbSNPSendReliableHeader )
			{
				int cbCopyHdr = std::min( seg.m_cbSegSize, seg.m_pMsg->m_cbSNPSendReliableHeader - seg.m_nOffset );

				memcpy( pPayloadPtr, seg.m_pMsg->SNPSend_ReliableHeader() + seg.m_nOffset, cbCopyHdr );
				pPayloadPtr += cbCopyHdr;

				int cbCopyBody = seg.m_cbSegSize - cbCopyHdr;
				if ( cbCopyBody > 0 )
				{
					memcpy( pPayloadPtr, seg.m_pMsg->m_pData, cbCopyBody );
					pPayloadPtr += cbCopyBody;
				}
			}
			else
			{
				 
				memcpy( pPayloadPtr, (char*)seg.m_pMsg->m_pData + seg.m_nOffset - seg.m_pMsg->m_cbSNPSendReliableHeader, seg.m_cbSegSize );
				pPayloadPtr += seg.m_cbSegSize;
			}


			 
			SNPRange_t range;
			range.m_nBegin = seg.m_pMsg->SNPSend_ReliableStreamPos() + seg.m_nOffset;
			range.m_nEnd = range.m_nBegin + seg.m_cbSegSize;

			 
			 
			 
			Assert( !HasOverlappingRange( range, m_senderState.m_listInFlightReliableRange ) );
			Assert( !HasOverlappingRange( range, m_senderState.m_listReadyRetryReliableRange ) );

			 
			SpewDebugGroup( nLogLevelPacketDecode, "[%s]   encode pkt %lld reliable msg %lld offset %d+%d=%d range [%lld,%lld)\n",
				GetDescription(), (long long)m_statsEndToEnd.m_nNextSendSequenceNumber, (long long)seg.m_pMsg->m_nMessageNumber,
				seg.m_nOffset, seg.m_cbSegSize, seg.m_nOffset+seg.m_cbSegSize,
				(long long)range.m_nBegin, (long long)range.m_nEnd );

			 
			m_senderState.m_listInFlightReliableRange[ range ] = seg.m_pMsg;

			 
			inFlightPkt.m_vecReliableSegments.push_back( range );

			 
			m_senderState.m_cbPendingReliable -= seg.m_cbSegSize;
			Assert( m_senderState.m_cbPendingReliable >= 0 );
		}
		else
		{
			 
			Assert( seg.m_cbSegSize > 0 || ( seg.m_cbSegSize == 0 && seg.m_pMsg->m_cbSize == 0 ) );

			 
			Assert( bStillInQueue == ( seg.m_nOffset + seg.m_cbSegSize < seg.m_pMsg->m_cbSize ) );  
			Assert( bStillInQueue == ( ( seg.m_hdr[0] & 0x20 ) == 0 ) );
			Assert( bStillInQueue || seg.m_pMsg->m_links.m_pNext == nullptr );  
			Assert( seg.m_pMsg->m_links.m_pPrev == nullptr );  

			 
			memcpy( pPayloadPtr, (char*)seg.m_pMsg->m_pData + seg.m_nOffset, seg.m_cbSegSize );
			pPayloadPtr += seg.m_cbSegSize;

			 
			SpewDebugGroup( nLogLevelPacketDecode, "[%s]   encode pkt %lld unreliable msg %lld offset %d+%d=%d\n",
				GetDescription(), (long long)m_statsEndToEnd.m_nNextSendSequenceNumber, (long long)seg.m_pMsg->m_nMessageNumber,
				seg.m_nOffset, seg.m_cbSegSize, seg.m_nOffset+seg.m_cbSegSize );

			 
			m_senderState.m_cbPendingUnreliable -= seg.m_cbSegSize;
			Assert( m_senderState.m_cbPendingUnreliable >= 0 );

			 
			if ( !bStillInQueue )
				seg.m_pMsg->Release();
		}
	}

	 
	Assert( pPayloadPtr <= pPayloadEnd );
	int cbPlainText = pPayloadPtr - payload;
	if ( cbPlainText > cbMaxPlaintextPayload )
	{
		AssertMsg1( false, "Payload exceeded max size of %d\n", cbMaxPlaintextPayload );
		return 0;
	}

	 
	 
	int nBytesSent = 0;
	switch ( m_eNegotiatedCipher )
	{
		default:
			AssertMsg1( false, "Bogus cipher %d", m_eNegotiatedCipher );
			break;

		case k_ESteamNetworkingSocketsCipher_NULL:
		{

			 
			 
			nBytesSent = pTransport->SendEncryptedDataChunk( payload, cbPlainText, ctx );
		}
		break;

		case k_ESteamNetworkingSocketsCipher_AES_256_GCM:
		{

			Assert( m_bCryptKeysValid );

			 
			*(uint64 *)&m_cryptIVSend.m_buf += LittleQWord( m_statsEndToEnd.m_nNextSendSequenceNumber );

			 
			uint8 arEncryptedChunk[ k_cbSteamNetworkingSocketsMaxEncryptedPayloadSend + 64 ];  
			uint32 cbEncrypted = sizeof(arEncryptedChunk);
			DbgVerify( m_cryptContextSend.Encrypt(
				payload, cbPlainText,  
				m_cryptIVSend.m_buf,  
				arEncryptedChunk, &cbEncrypted,  
				nullptr, 0  
			) );

			 
			 
			 
			 
			 
			 

			 
			*(uint64 *)&m_cryptIVSend.m_buf -= LittleQWord( m_statsEndToEnd.m_nNextSendSequenceNumber );

			Assert( (int)cbEncrypted >= cbPlainText );
			Assert( (int)cbEncrypted <= k_cbSteamNetworkingSocketsMaxEncryptedPayloadSend );  

			 
			nBytesSent = pTransport->SendEncryptedDataChunk( arEncryptedChunk, cbEncrypted, ctx );
		}
	}
	if ( nBytesSent <= 0 )
		return false;

	 
	auto pairInsertResult = m_senderState.m_mapInFlightPacketsByPktNum.insert( pairInsert );
	Assert( pairInsertResult.second );  

	 
	if ( !inFlightPkt.m_vecReliableSegments.empty() )
	{
		m_statsEndToEnd.TrackSentMessageExpectingSeqNumAck( usecNow, true );
		 
	}

	 
	if ( m_senderState.m_itNextInFlightPacketToTimeout == m_senderState.m_mapInFlightPacketsByPktNum.end() )
		m_senderState.m_itNextInFlightPacketToTimeout = pairInsertResult.first;

	#ifdef SNP_ENABLE_PACKETSENDLOG
		pLog->m_cbSent = nBytesSent;
	#endif

	 
	m_senderState.m_flTokenBucket -= (float)nBytesSent;
	return true;
}