// on user thread private void SendFragmentedMessage(NetOutgoingMessage msg, IList <NetConnection> recipients, NetDeliveryMethod method, int sequenceChannel) { // Note: this group id is PER SENDING/NetPeer; ie. same id is sent to all recipients; // this should be ok however; as long as recipients differentiate between same id but different sender int group = Interlocked.Increment(ref m_lastUsedFragmentGroup); if (group >= NetConstants.MaxFragmentationGroups) { // @TODO: not thread safe; but in practice probably not an issue m_lastUsedFragmentGroup = 1; group = 1; } msg.m_fragmentGroup = group; // do not send msg; but set fragmentgroup in case user tries to recycle it immediately // create fragmentation specifics int totalBytes = msg.LengthBytes; // determine minimum mtu for all recipients int mtu = GetMTU(recipients); int bytesPerChunk = NetFragmentationHelper.GetBestChunkSize(group, totalBytes, mtu); int numChunks = totalBytes / bytesPerChunk; if (numChunks * bytesPerChunk < totalBytes) { numChunks++; } int bitsPerChunk = bytesPerChunk * 8; int bitsLeft = msg.LengthBits; for (int i = 0; i < numChunks; i++) { NetOutgoingMessage chunk = CreateMessage(mtu); chunk.m_bitLength = (bitsLeft > bitsPerChunk ? bitsPerChunk : bitsLeft); chunk.m_data = msg.m_data; chunk.m_fragmentGroup = group; chunk.m_fragmentGroupTotalBits = totalBytes * 8; chunk.m_fragmentChunkByteSize = bytesPerChunk; chunk.m_fragmentChunkNumber = i; NetException.Assert(chunk.m_bitLength != 0); NetException.Assert(chunk.GetEncodedSize() < mtu); Interlocked.Add(ref chunk.m_recyclingCount, recipients.Count); foreach (NetConnection recipient in recipients) { recipient.EnqueueMessage(chunk, method, sequenceChannel); } bitsLeft -= bitsPerChunk; } return; }
internal int Encode(Span <byte> intoBuffer, int ptr, int sequenceNumber) { // 8 bits - NetMessageType // 1 bit - Fragment? // 15 bits - Sequence number // 16 bits - Payload length in bits intoBuffer[ptr++] = (byte)m_messageType; byte low = (byte)((sequenceNumber << 1) | (m_fragmentGroup == 0 ? 0 : 1)); intoBuffer[ptr++] = low; intoBuffer[ptr++] = (byte)(sequenceNumber >> 7); if (m_fragmentGroup == 0) { intoBuffer[ptr++] = (byte)m_bitLength; intoBuffer[ptr++] = (byte)(m_bitLength >> 8); int byteLen = NetUtility.BytesToHoldBits(m_bitLength); if (byteLen > 0) { m_data.Slice(0, byteLen) .CopyTo(intoBuffer.Slice(ptr, byteLen)); //Buffer.BlockCopy(m_data, 0, intoBuffer, ptr, byteLen); ptr += byteLen; } } else { int wasPtr = ptr; intoBuffer[ptr++] = (byte)m_bitLength; intoBuffer[ptr++] = (byte)(m_bitLength >> 8); // // write fragmentation header // ptr = NetFragmentationHelper.WriteHeader(intoBuffer, ptr, m_fragmentGroup, m_fragmentGroupTotalBits, m_fragmentChunkByteSize, m_fragmentChunkNumber); int hdrLen = ptr - wasPtr - 2; // update length int realBitLength = m_bitLength + (hdrLen * 8); intoBuffer[wasPtr] = (byte)realBitLength; intoBuffer[wasPtr + 1] = (byte)(realBitLength >> 8); int byteLen = NetUtility.BytesToHoldBits(m_bitLength); if (byteLen > 0) { m_data.Slice(m_fragmentChunkNumber * m_fragmentChunkByteSize, byteLen) .CopyTo(intoBuffer.Slice(ptr, byteLen)); //Buffer.BlockCopy(m_data, (int)(m_fragmentChunkNumber * m_fragmentChunkByteSize), intoBuffer, ptr, byteLen); ptr += byteLen; } } NetException.Assert(ptr > 0); return(ptr); }
// on user thread private void SendFragmentedMessage(NetOutgoingMessage msg, IList <NetConnection> recipients, NetDeliveryMethod method, int sequenceChannel) { int group = Interlocked.Increment(ref m_lastUsedFragmentGroup); if (group >= NetConstants.MaxFragmentationGroups) { // TODO: not thread safe; but in practice probably not an issue m_lastUsedFragmentGroup = 1; group = 1; } msg.m_fragmentGroup = group; // do not send msg; but set fragmentgroup in case user tries to recycle it immediately // create fragmentation specifics int totalBytes = msg.LengthBytes; int mtu = m_configuration.MaximumTransmissionUnit; int bytesPerChunk = NetFragmentationHelper.GetBestChunkSize(group, totalBytes, mtu); int numChunks = totalBytes / bytesPerChunk; if (numChunks * bytesPerChunk < totalBytes) { numChunks++; } int bitsPerChunk = bytesPerChunk * 8; int bitsLeft = msg.LengthBits; for (int i = 0; i < numChunks; i++) { NetOutgoingMessage chunk = CreateMessage(mtu); chunk.m_bitLength = (bitsLeft > bitsPerChunk ? bitsPerChunk : bitsLeft); chunk.m_data = msg.m_data; chunk.m_fragmentGroup = group; chunk.m_fragmentGroupTotalBits = totalBytes * 8; chunk.m_fragmentChunkByteSize = bytesPerChunk; chunk.m_fragmentChunkNumber = i; NetException.Assert(chunk.m_bitLength != 0); NetException.Assert(chunk.GetEncodedSize() < mtu); Interlocked.Add(ref chunk.m_recyclingCount, recipients.Count); foreach (NetConnection recipient in recipients) { recipient.EnqueueMessage(chunk, method, sequenceChannel); } bitsLeft -= bitsPerChunk; } return; }
internal int GetEncodedSize() { int retval = NetConstants.UnfragmentedMessageHeaderSize; // regular headers if (m_fragmentGroup != 0) { retval += NetFragmentationHelper.GetFragmentationHeaderSize(m_fragmentGroup, m_fragmentGroupTotalBits / 8, m_fragmentChunkByteSize, m_fragmentChunkNumber); } retval += LengthBytes; return(retval); }
private void HandleReleasedFragment(NetIncomingMessage im) { VerifyNetworkThread(); // // read fragmentation header and combine fragments // int group; int totalBits; int chunkByteSize; int chunkNumber; int ptr = NetFragmentationHelper.ReadHeader( im.m_data, 0, out group, out totalBits, out chunkByteSize, out chunkNumber ); NetException.Assert(im.LengthBytes > ptr); NetException.Assert(group > 0); NetException.Assert(totalBits > 0); NetException.Assert(chunkByteSize > 0); int totalBytes = NetUtility.BytesToHoldBits((int)totalBits); int totalNumChunks = totalBytes / chunkByteSize; if (totalNumChunks * chunkByteSize < totalBytes) { totalNumChunks++; } NetException.Assert(chunkNumber < totalNumChunks); if (chunkNumber >= totalNumChunks) { LogWarning("Index out of bounds for chunk " + chunkNumber + " (total chunks " + totalNumChunks + ")"); return; } Dictionary <int, ReceivedFragmentGroup> groups; if (!m_receivedFragmentGroups.TryGetValue(im.SenderConnection, out groups)) { groups = new Dictionary <int, ReceivedFragmentGroup>(); m_receivedFragmentGroups[im.SenderConnection] = groups; } ReceivedFragmentGroup info; if (!groups.TryGetValue(group, out info)) { info = new ReceivedFragmentGroup(); info.Data = new byte[totalBytes]; info.ReceivedChunks = new NetBitVector(totalNumChunks); groups[group] = info; } info.ReceivedChunks[chunkNumber] = true; //info.LastReceived = (float)NetTime.Now; // copy to data int offset = (chunkNumber * chunkByteSize); Buffer.BlockCopy(im.m_data, ptr, info.Data, offset, im.LengthBytes - ptr); int cnt = info.ReceivedChunks.Count(); //LogVerbose("Found fragment #" + chunkNumber + " in group " + group + " offset " + offset + " of total bits " + totalBits + " (total chunks done " + cnt + ")"); LogVerbose("Received fragment " + chunkNumber + " of " + totalNumChunks + " (" + cnt + " chunks received)"); if (info.ReceivedChunks.Count() == totalNumChunks) { // Done! Transform this incoming message im.m_data = info.Data; im.m_bitLength = (int)totalBits; im.m_isFragment = false; LogVerbose("Fragment group #" + group + " fully received in " + totalNumChunks + " chunks (" + totalBits + " bits)"); groups.Remove(group); ReleaseMessage(im); } else { // data has been copied; recycle this incoming message Recycle(im); } return; }
internal int Encode(byte[] intoBuffer, int ptr, int sequenceNumber) { // NOTE: I had to change the header format so it won't collide with multiplexed STUN on the same socket // and I made it big-endian for consistency's sake. -AR // // <- MSB LSB -> // 0 1 2 3 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 // V-+-+-+-+-+-+-+-V-+-+-+-+-+-+-+-V-+-+-+-+-+-+-+-V-+-+-+-+-+-+-+-V // |1|F| Sequence Number |NetMessageType | Length bits... // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // ... | // +-+-+-+-+-+-+-+-+ // // IMPORTANT: The packet format is also encoded elsewhere, to send NetMessageType.Acknowledge (see NetConnection.cs) // intoBuffer[ptr++] = (byte)(((sequenceNumber >> 8) & 0x3F) | (m_fragmentGroup == 0 ? 0x80 : 0xC0)); intoBuffer[ptr++] = (byte)sequenceNumber; intoBuffer[ptr++] = (byte)m_messageType; if (m_fragmentGroup == 0) { intoBuffer[ptr++] = (byte)(m_bitLength >> 8); intoBuffer[ptr++] = (byte)m_bitLength; int byteLen = NetUtility.BytesToHoldBits(m_bitLength); if (byteLen > 0) { Buffer.BlockCopy(m_data, 0, intoBuffer, ptr, byteLen); ptr += byteLen; } } else { int wasPtr = ptr; intoBuffer[ptr++] = (byte)(m_bitLength >> 8); intoBuffer[ptr++] = (byte)m_bitLength; // // write fragmentation header // ptr = NetFragmentationHelper.WriteHeader(intoBuffer, ptr, m_fragmentGroup, m_fragmentGroupTotalBits, m_fragmentChunkByteSize, m_fragmentChunkNumber); int hdrLen = ptr - wasPtr - 2; // update length int realBitLength = m_bitLength + (hdrLen * 8); intoBuffer[wasPtr] = (byte)(realBitLength >> 8); intoBuffer[wasPtr + 1] = (byte)realBitLength; int byteLen = NetUtility.BytesToHoldBits(m_bitLength); if (byteLen > 0) { Buffer.BlockCopy(m_data, (int)(m_fragmentChunkNumber * m_fragmentChunkByteSize), intoBuffer, ptr, byteLen); ptr += byteLen; } } NetException.Assert(ptr > 0); return(ptr); }
// on user thread // the message must not be sent already private NetSendResult SendFragmentedMessage( NetOutgoingMessage message, IEnumerable <NetConnection?> recipients, NetDeliveryMethod method, int sequenceChannel) { // determine minimum mtu for all recipients int mtu = GetMTU(recipients, out int recipientCount); if (recipientCount == 0) { Recycle(message); return(NetSendResult.NoRecipients); } int group = GetNextFragmentGroup(); // do not send msg; but set fragmentgroup in case user tries to recycle it immediately message._fragmentGroup = group << 2 | 1; // create fragmentation specifics int totalBytes = message.ByteLength; int bytesPerChunk = NetFragmentationHelper.GetBestChunkSize(group, totalBytes, mtu); int numChunks = totalBytes / bytesPerChunk; if (numChunks * bytesPerChunk < totalBytes) { numChunks++; } var retval = NetSendResult.Sent; int bitsPerChunk = bytesPerChunk * 8; int bitsLeft = message.BitLength; byte[] buffer = message.GetBuffer(); for (int i = 0; i < numChunks; i++) { NetOutgoingMessage chunk = CreateMessage(); chunk.SetBuffer(buffer, false); chunk.BitLength = Math.Min(bitsLeft, bitsPerChunk); chunk._fragmentGroup = group << 2 | 1; chunk._fragmentGroupTotalBits = totalBytes * 8; chunk._fragmentChunkByteSize = bytesPerChunk; chunk._fragmentChunkNumber = i; LidgrenException.Assert(chunk.BitLength != 0); LidgrenException.Assert(chunk.GetEncodedSize() <= mtu); Interlocked.Add(ref chunk._recyclingCount, recipientCount); foreach (NetConnection?recipient in recipients.AsListEnumerator()) { if (recipient == null) { continue; } NetSendResult result = recipient.EnqueueMessage(chunk, method, sequenceChannel).Result; if (result > retval) { retval = result; // return "worst" result } } bitsLeft -= bitsPerChunk; } return(retval); }
// on user thread private async ValueTask <NetSendResult> SendFragmentedMessageAsync( PipeReader reader, NetConnection recipient, int sequenceChannel, CancellationToken cancellationToken) { int group = GetNextFragmentGroup(); (NetSendResult Result, NetSenderChannel?) SendChunk(NetOutgoingMessage chunk) { return(recipient.EnqueueMessage(chunk, NetDeliveryMethod.ReliableOrdered, sequenceChannel)); } NetSendResult finalResult; Exception? exception = null; try { ReadResult readResult; do { readResult = await reader.ReadAsync(cancellationToken).ConfigureAwait(false); if (readResult.IsCanceled) { NetOutgoingMessage cancelChunk = CreateStreamChunk(0, group, NetStreamFragmentType.Cancelled); finalResult = SendChunk(cancelChunk).Result; break; } ReadOnlySequence <byte> buffer = readResult.Buffer; int mtu = recipient.CurrentMTU; int bytesPerChunk = NetFragmentationHelper.GetBestChunkSize(group, (int)buffer.Length, mtu); while (buffer.Length > 0) { long chunkLength = Math.Min(buffer.Length, bytesPerChunk); NetOutgoingMessage chunk = CreateStreamChunk((int)chunkLength, group, NetStreamFragmentType.Data); foreach (ReadOnlyMemory <byte> memory in buffer.Slice(0, chunkLength)) { chunk.Write(memory.Span); } buffer = buffer.Slice(chunkLength); LidgrenException.Assert(chunk.GetEncodedSize() <= mtu); Interlocked.Add(ref chunk._recyclingCount, 1); var(result, channel) = SendChunk(chunk); if (result == NetSendResult.Queued) { await channel !.WaitForIdleAsync(millisecondsTimeout: 10000, cancellationToken); } else if (result != NetSendResult.Sent) { NetOutgoingMessage cancelChunk = CreateStreamChunk(0, group, NetStreamFragmentType.Cancelled); finalResult = SendChunk(cancelChunk).Result; reader.CancelPendingRead(); break; } } reader.AdvanceTo(readResult.Buffer.End); }while (!readResult.IsCompleted); NetOutgoingMessage endChunk = CreateStreamChunk(0, group, NetStreamFragmentType.EndOfStream); finalResult = SendChunk(endChunk).Result; } catch (Exception ex) { exception = ex; NetOutgoingMessage errorChunk = CreateStreamChunk(0, group, NetStreamFragmentType.ServerError); finalResult = SendChunk(errorChunk).Result; } await reader.CompleteAsync(exception).ConfigureAwait(false); return(finalResult); }