summary refs log tree commit diff
path: root/crypto
diff options
context:
space:
mode:
authorPeter Dettman <peter.dettman@bouncycastle.org>2022-11-05 23:04:19 +0700
committerPeter Dettman <peter.dettman@bouncycastle.org>2022-11-05 23:04:19 +0700
commit4ff3ad2363c111d8723821687b14c1189ecac675 (patch)
tree0c8683c876c827dcf2457ba425af85c74c4e582c /crypto
parentAdd TlsPeer.IgnoreCorruptDtlsRecords (diff)
downloadBouncyCastle.NET-ed25519-4ff3ad2363c111d8723821687b14c1189ecac675.tar.xz
Add Span-based variants in DTLS
Diffstat (limited to 'crypto')
-rw-r--r--crypto/src/tls/DatagramReceiver.cs5
-rw-r--r--crypto/src/tls/DatagramSender.cs5
-rw-r--r--crypto/src/tls/DtlsRecordLayer.cs167
-rw-r--r--crypto/src/tls/DtlsTransport.cs100
-rw-r--r--crypto/src/tls/TlsUtilities.cs9
-rw-r--r--crypto/test/src/tls/test/LoggingDatagramTransport.cs62
-rw-r--r--crypto/test/src/tls/test/MockDatagramAssociation.cs59
-rw-r--r--crypto/test/src/tls/test/UnreliableDatagramTransport.cs47
8 files changed, 444 insertions, 10 deletions
diff --git a/crypto/src/tls/DatagramReceiver.cs b/crypto/src/tls/DatagramReceiver.cs
index 5ab605ac4..a689515f6 100644
--- a/crypto/src/tls/DatagramReceiver.cs
+++ b/crypto/src/tls/DatagramReceiver.cs
@@ -10,5 +10,10 @@ namespace Org.BouncyCastle.Tls
 
         /// <exception cref="IOException"/>
         int Receive(byte[] buf, int off, int len, int waitMillis);
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        /// <exception cref="IOException"/>
+        int Receive(Span<byte> buffer, int waitMillis);
+#endif
     }
 }
diff --git a/crypto/src/tls/DatagramSender.cs b/crypto/src/tls/DatagramSender.cs
index bf14c18fe..c2a987b51 100644
--- a/crypto/src/tls/DatagramSender.cs
+++ b/crypto/src/tls/DatagramSender.cs
@@ -10,5 +10,10 @@ namespace Org.BouncyCastle.Tls
 
         /// <exception cref="IOException"/>
         void Send(byte[] buf, int off, int len);
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        /// <exception cref="IOException"/>
+        void Send(ReadOnlySpan<byte> buffer);
+#endif
     }
 }
diff --git a/crypto/src/tls/DtlsRecordLayer.cs b/crypto/src/tls/DtlsRecordLayer.cs
index 7ec77c5da..bab6892b7 100644
--- a/crypto/src/tls/DtlsRecordLayer.cs
+++ b/crypto/src/tls/DtlsRecordLayer.cs
@@ -242,6 +242,9 @@ namespace Org.BouncyCastle.Tls
         /// <exception cref="IOException"/>
         public virtual int Receive(byte[] buf, int off, int len, int waitMillis)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return Receive(buf.AsSpan(off, len), waitMillis);
+#else
             long currentTimeMillis = DateTimeUtilities.CurrentUnixMs();
 
             Timeout timeout = Timeout.ForWaitMillis(waitMillis, currentTimeMillis);
@@ -305,11 +308,85 @@ namespace Org.BouncyCastle.Tls
             }
 
             return -1;
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        /// <exception cref="IOException"/>
+        public virtual int Receive(Span<byte> buffer, int waitMillis)
+        {
+            long currentTimeMillis = DateTimeUtilities.CurrentUnixMs();
+
+            Timeout timeout = Timeout.ForWaitMillis(waitMillis, currentTimeMillis);
+            byte[] record = null;
+
+            while (waitMillis >= 0)
+            {
+                if (null != m_retransmitTimeout && m_retransmitTimeout.RemainingMillis(currentTimeMillis) < 1)
+                {
+                    m_retransmit = null;
+                    m_retransmitEpoch = null;
+                    m_retransmitTimeout = null;
+                }
+
+                if (Timeout.HasExpired(m_heartbeatTimeout, currentTimeMillis))
+                {
+                    if (null != m_heartbeatInFlight)
+                        throw new TlsTimeoutException("Heartbeat timed out");
+
+                    this.m_heartbeatInFlight = HeartbeatMessage.Create(m_context,
+                        HeartbeatMessageType.heartbeat_request, m_heartbeat.GeneratePayload());
+                    this.m_heartbeatTimeout = new Timeout(m_heartbeat.TimeoutMillis, currentTimeMillis);
+
+                    this.m_heartbeatResendMillis = DtlsReliableHandshake.INITIAL_RESEND_MILLIS;
+                    this.m_heartbeatResendTimeout = new Timeout(m_heartbeatResendMillis, currentTimeMillis);
+
+                    SendHeartbeatMessage(m_heartbeatInFlight);
+                }
+                else if (Timeout.HasExpired(m_heartbeatResendTimeout, currentTimeMillis))
+                {
+                    this.m_heartbeatResendMillis = DtlsReliableHandshake.BackOff(m_heartbeatResendMillis);
+                    this.m_heartbeatResendTimeout = new Timeout(m_heartbeatResendMillis, currentTimeMillis);
+
+                    SendHeartbeatMessage(m_heartbeatInFlight);
+                }
+
+                waitMillis = Timeout.ConstrainWaitMillis(waitMillis, m_heartbeatTimeout, currentTimeMillis);
+                waitMillis = Timeout.ConstrainWaitMillis(waitMillis, m_heartbeatResendTimeout, currentTimeMillis);
+
+                // NOTE: Guard against bad logic giving a negative value 
+                if (waitMillis < 0)
+                {
+                    waitMillis = 1;
+                }
+
+                int receiveLimit = System.Math.Min(buffer.Length, GetReceiveLimit()) + RECORD_HEADER_LENGTH;
+                if (null == record || record.Length < receiveLimit)
+                {
+                    record = new byte[receiveLimit];
+                }
+
+                int received = ReceiveRecord(record, 0, receiveLimit, waitMillis);
+                int processed = ProcessRecord(received, record, buffer);
+                if (processed >= 0)
+                {
+                    return processed;
+                }
+
+                currentTimeMillis = DateTimeUtilities.CurrentUnixMs();
+                waitMillis = Timeout.GetWaitMillis(timeout, currentTimeMillis);
+            }
+
+            return -1;
+        }
+#endif
+
         /// <exception cref="IOException"/>
         public virtual void Send(byte[] buf, int off, int len)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Send(buf.AsSpan(off, len));
+#else
             short contentType = ContentType.application_data;
 
             if (m_inHandshake || m_writeEpoch == m_retransmitEpoch)
@@ -338,7 +415,7 @@ namespace Org.BouncyCastle.Tls
                     // Implicitly send change_cipher_spec and change to pending cipher state
 
                     // TODO Send change_cipher_spec and finished records in single datagram?
-                    byte[] data = new byte[]{ 1 };
+                    byte[] data = new byte[1]{ 1 };
                     SendRecord(ContentType.change_cipher_spec, data, 0, data.Length);
 
                     this.m_writeEpoch = nextEpoch;
@@ -346,7 +423,51 @@ namespace Org.BouncyCastle.Tls
             }
 
             SendRecord(contentType, buf, off, len);
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        /// <exception cref="IOException"/>
+        public virtual void Send(ReadOnlySpan<byte> buffer)
+        {
+            short contentType = ContentType.application_data;
+
+            if (m_inHandshake || m_writeEpoch == m_retransmitEpoch)
+            {
+                contentType = ContentType.handshake;
+
+                short handshakeType = TlsUtilities.ReadUint8(buffer);
+                if (handshakeType == HandshakeType.finished)
+                {
+                    DtlsEpoch nextEpoch = null;
+                    if (m_inHandshake)
+                    {
+                        nextEpoch = m_pendingEpoch;
+                    }
+                    else if (m_writeEpoch == m_retransmitEpoch)
+                    {
+                        nextEpoch = m_currentEpoch;
+                    }
+
+                    if (nextEpoch == null)
+                    {
+                        // TODO
+                        throw new InvalidOperationException();
+                    }
+
+                    // Implicitly send change_cipher_spec and change to pending cipher state
+
+                    // TODO Send change_cipher_spec and finished records in single datagram?
+                    ReadOnlySpan<byte> data = stackalloc byte[1]{ 1 };
+                    SendRecord(ContentType.change_cipher_spec, data);
+
+                    this.m_writeEpoch = nextEpoch;
+                }
+            }
+
+            SendRecord(contentType, buffer);
         }
+#endif
 
         /// <exception cref="IOException"/>
         public virtual void Close()
@@ -432,11 +553,13 @@ namespace Org.BouncyCastle.Tls
         {
             m_peer.NotifyAlertRaised(alertLevel, alertDescription, message, cause);
 
-            byte[] error = new byte[2];
-            error[0] = (byte)alertLevel;
-            error[1] = (byte)alertDescription;
-
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            ReadOnlySpan<byte> error = stackalloc byte[2]{ (byte)alertLevel, (byte)alertDescription };
+            SendRecord(ContentType.alert, error);
+#else
+            byte[] error = new byte[2]{ (byte)alertLevel, (byte)alertDescription };
             SendRecord(ContentType.alert, error, 0, 2);
+#endif
         }
 
         /// <exception cref="IOException"/>
@@ -467,7 +590,11 @@ namespace Org.BouncyCastle.Tls
 
         // TODO Include 'currentTimeMillis' as an argument, use with Timeout, resetHeartbeat
         /// <exception cref="IOException"/>
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private int ProcessRecord(int received, byte[] record, Span<byte> buffer)
+#else
         private int ProcessRecord(int received, byte[] record, byte[] buf, int off)
+#endif
         {
             // NOTE: received < 0 (timeout) is covered by this first case
             if (received < RECORD_HEADER_LENGTH)
@@ -700,7 +827,11 @@ namespace Org.BouncyCastle.Tls
                 this.m_retransmitTimeout = null;
             }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            decoded.buf.AsSpan(decoded.off, decoded.len).CopyTo(buffer);
+#else
             Array.Copy(decoded.buf, decoded.off, buf, off, decoded.len);
+#endif
             return decoded.len;
         }
 
@@ -712,9 +843,7 @@ namespace Org.BouncyCastle.Tls
                 int length = 0;
                 if (m_recordQueue.Available >= RECORD_HEADER_LENGTH)
                 {
-                    byte[] lengthBytes = new byte[2];
-                    m_recordQueue.Read(lengthBytes, 0, 2, 11);
-                    length = TlsUtilities.ReadUint16(lengthBytes, 0);
+                    length = m_recordQueue.ReadUint16(11);
                 }
 
                 int received = System.Math.Min(m_recordQueue.Available, RECORD_HEADER_LENGTH + length);
@@ -754,9 +883,16 @@ namespace Org.BouncyCastle.Tls
         {
             MemoryStream output = new MemoryStream();
             heartbeatMessage.Encode(output);
-            byte[] buf = output.ToArray();
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            if (!output.TryGetBuffer(out var buffer))
+                throw new InvalidOperationException();
+
+            SendRecord(ContentType.heartbeat, buffer);
+#else
+            byte[] buf = output.ToArray();
             SendRecord(ContentType.heartbeat, buf, 0, buf.Length);
+#endif
         }
 
         /*
@@ -766,12 +902,20 @@ namespace Org.BouncyCastle.Tls
          * be possible reordering of records (which might surprise a reliable transport implementation).
          */
         /// <exception cref="IOException"/>
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void SendRecord(short contentType, ReadOnlySpan<byte> buffer)
+#else
         private void SendRecord(short contentType, byte[] buf, int off, int len)
+#endif
         {
             // Never send anything until a valid ClientHello has been received
             if (m_writeVersion == null)
                 return;
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            int len = buffer.Length;
+#endif
+
             if (len > m_plaintextLimit)
                 throw new TlsFatalAlert(AlertDescription.internal_error);
 
@@ -789,8 +933,13 @@ namespace Org.BouncyCastle.Tls
                 long macSequenceNumber = GetMacSequenceNumber(recordEpoch, recordSequenceNumber);
                 ProtocolVersion recordVersion = m_writeVersion;
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                TlsEncodeResult encoded = m_writeEpoch.Cipher.EncodePlaintext(macSequenceNumber, contentType,
+                    recordVersion, RECORD_HEADER_LENGTH, buffer);
+#else
                 TlsEncodeResult encoded = m_writeEpoch.Cipher.EncodePlaintext(macSequenceNumber, contentType,
                     recordVersion, RECORD_HEADER_LENGTH, buf, off, len);
+#endif
 
                 int ciphertextLength = encoded.len - RECORD_HEADER_LENGTH;
                 TlsUtilities.CheckUint16(ciphertextLength);
diff --git a/crypto/src/tls/DtlsTransport.cs b/crypto/src/tls/DtlsTransport.cs
index 6d481702f..033e0af0b 100644
--- a/crypto/src/tls/DtlsTransport.cs
+++ b/crypto/src/tls/DtlsTransport.cs
@@ -37,6 +37,10 @@ namespace Org.BouncyCastle.Tls
                 throw new ArgumentException("invalid offset: " + off, "off");
             if (len < 0 || len > buf.Length - off)
                 throw new ArgumentException("invalid length: " + len, "len");
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return Receive(buf.AsSpan(off, len), waitMillis);
+#else
             if (waitMillis < 0)
                 throw new ArgumentException("cannot be negative", "waitMillis");
 
@@ -79,8 +83,58 @@ namespace Org.BouncyCastle.Tls
                 m_recordLayer.Fail(AlertDescription.internal_error);
                 throw new TlsFatalAlert(AlertDescription.internal_error, e);
             }
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        /// <exception cref="IOException"/>
+        public virtual int Receive(Span<byte> buffer, int waitMillis)
+        {
+            if (waitMillis < 0)
+                throw new ArgumentException("cannot be negative", nameof(waitMillis));
+
+            try
+            {
+                return m_recordLayer.Receive(buffer, waitMillis);
+            }
+            catch (TlsFatalAlert fatalAlert)
+            {
+                if (m_ignoreCorruptRecords && AlertDescription.bad_record_mac == fatalAlert.AlertDescription)
+                    return -1;
+
+                m_recordLayer.Fail(fatalAlert.AlertDescription);
+                throw fatalAlert;
+            }
+            catch (TlsTimeoutException e)
+            {
+                throw e;
+            }
+            catch (SocketException e)
+            {
+                if (TlsUtilities.IsTimeout(e))
+                    throw e;
+
+                m_recordLayer.Fail(AlertDescription.internal_error);
+                throw new TlsFatalAlert(AlertDescription.internal_error, e);
+            }
+            // TODO[tls-port] Can we support interrupted IO on .NET?
+            //catch (InterruptedIOException e)
+            //{
+            //    throw e;
+            //}
+            catch (IOException e)
+            {
+                m_recordLayer.Fail(AlertDescription.internal_error);
+                throw e;
+            }
+            catch (Exception e)
+            {
+                m_recordLayer.Fail(AlertDescription.internal_error);
+                throw new TlsFatalAlert(AlertDescription.internal_error, e);
+            }
+        }
+#endif
+
         /// <exception cref="IOException"/>
         public virtual void Send(byte[] buf, int off, int len)
         {
@@ -91,6 +145,9 @@ namespace Org.BouncyCastle.Tls
             if (len < 0 || len > buf.Length - off)
                 throw new ArgumentException("invalid length: " + len, "len");
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Send(buf.AsSpan(off, len));
+#else
             try
             {
                 m_recordLayer.Send(buf, off, len);
@@ -127,7 +184,50 @@ namespace Org.BouncyCastle.Tls
                 m_recordLayer.Fail(AlertDescription.internal_error);
                 throw new TlsFatalAlert(AlertDescription.internal_error, e);
             }
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void Send(ReadOnlySpan<byte> buffer)
+        {
+            try
+            {
+                m_recordLayer.Send(buffer);
+            }
+            catch (TlsFatalAlert fatalAlert)
+            {
+                m_recordLayer.Fail(fatalAlert.AlertDescription);
+                throw fatalAlert;
+            }
+            catch (TlsTimeoutException e)
+            {
+                throw e;
+            }
+            catch (SocketException e)
+            {
+                if (TlsUtilities.IsTimeout(e))
+                    throw e;
+
+                m_recordLayer.Fail(AlertDescription.internal_error);
+                throw new TlsFatalAlert(AlertDescription.internal_error, e);
+            }
+            // TODO[tls-port] Can we support interrupted IO on .NET?
+            //catch (InterruptedIOException e)
+            //{
+            //    throw e;
+            //}
+            catch (IOException e)
+            {
+                m_recordLayer.Fail(AlertDescription.internal_error);
+                throw e;
+            }
+            catch (Exception e)
+            {
+                m_recordLayer.Fail(AlertDescription.internal_error);
+                throw new TlsFatalAlert(AlertDescription.internal_error, e);
+            }
         }
+#endif
 
         /// <exception cref="IOException"/>
         public virtual void Close()
diff --git a/crypto/src/tls/TlsUtilities.cs b/crypto/src/tls/TlsUtilities.cs
index f12198082..463928ba6 100644
--- a/crypto/src/tls/TlsUtilities.cs
+++ b/crypto/src/tls/TlsUtilities.cs
@@ -747,9 +747,16 @@ namespace Org.BouncyCastle.Tls
 
         public static short ReadUint8(byte[] buf, int offset)
         {
-            return (short)(buf[offset] & 0xff);
+            return (short)buf[offset];
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static short ReadUint8(ReadOnlySpan<byte> buffer)
+        {
+            return (short)buffer[0];
+        }
+#endif
+
         public static int ReadUint16(Stream input)
         {
             int i1 = input.ReadByte();
diff --git a/crypto/test/src/tls/test/LoggingDatagramTransport.cs b/crypto/test/src/tls/test/LoggingDatagramTransport.cs
index f675b72fc..0ad15e065 100644
--- a/crypto/test/src/tls/test/LoggingDatagramTransport.cs
+++ b/crypto/test/src/tls/test/LoggingDatagramTransport.cs
@@ -34,25 +34,86 @@ namespace Org.BouncyCastle.Tls.Tests
 
         public virtual int Receive(byte[] buf, int off, int len, int waitMillis)
         {
+//#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+#if NET6_0_OR_GREATER
+            return Receive(buf.AsSpan(off, len), waitMillis);
+#else
             int length = m_transport.Receive(buf, off, len, waitMillis);
             if (length >= 0)
             {
                 DumpDatagram("Received", buf, off, length);
             }
             return length;
+#endif
         }
 
+//#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+#if NET6_0_OR_GREATER
+        public virtual int Receive(Span<byte> buffer, int waitMillis)
+        {
+            int length = m_transport.Receive(buffer, waitMillis);
+            if (length >= 0)
+            {
+                DumpDatagram("Received", buffer[..length]);
+            }
+            return length;
+        }
+#endif
+
         public virtual void Send(byte[] buf, int off, int len)
         {
+//#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+#if NET6_0_OR_GREATER
+            Send(buf.AsSpan(off, len));
+#else
             DumpDatagram("Sending", buf, off, len);
             m_transport.Send(buf, off, len);
+#endif
         }
 
+//#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+#if NET6_0_OR_GREATER
+        public virtual void Send(ReadOnlySpan<byte> buffer)
+        {
+            DumpDatagram("Sending", buffer);
+            m_transport.Send(buffer);
+        }
+#endif
+
         public virtual void Close()
         {
             m_transport.Close();
         }
 
+//#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+#if NET6_0_OR_GREATER
+        private void DumpDatagram(string verb, ReadOnlySpan<byte> buffer)
+        {
+            int len = buffer.Length;
+            long timestamp = DateTimeUtilities.CurrentUnixMs() - m_launchTimestamp;
+            StringBuilder sb = new StringBuilder("(+" + timestamp + "ms) " + verb + " " + len + " byte datagram:");
+            for (int pos = 0; pos < len; ++pos)
+            {
+                if (pos % 16 == 0)
+                {
+                    sb.Append(Environment.NewLine);
+                    sb.Append("    ");
+                }
+                else if (pos % 16 == 8)
+                {
+                    sb.Append('-');
+                }
+                else
+                {
+                    sb.Append(' ');
+                }
+                int val = buffer[pos] & 0xFF;
+                sb.Append(HEX_CHARS[val >> 4]);
+                sb.Append(HEX_CHARS[val & 0xF]);
+            }
+            Dump(sb.ToString());
+        }
+#else
         private void DumpDatagram(string verb, byte[] buf, int off, int len)
         {
             long timestamp = DateTimeUtilities.CurrentUnixMs() - m_launchTimestamp;
@@ -78,6 +139,7 @@ namespace Org.BouncyCastle.Tls.Tests
             }
             Dump(sb.ToString());
         }
+#endif
 
         private void Dump(string s)
         {
diff --git a/crypto/test/src/tls/test/MockDatagramAssociation.cs b/crypto/test/src/tls/test/MockDatagramAssociation.cs
index ef317c7b6..3612bec40 100644
--- a/crypto/test/src/tls/test/MockDatagramAssociation.cs
+++ b/crypto/test/src/tls/test/MockDatagramAssociation.cs
@@ -58,6 +58,10 @@ namespace Org.BouncyCastle.Tls.Tests
 
             public virtual int Receive(byte[] buf, int off, int len, int waitMillis)
             {
+//#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+#if NET6_0_OR_GREATER
+                return Receive(buf.AsSpan(off, len), waitMillis);
+#else
                 lock (m_receiveQueue)
                 {
                     if (m_receiveQueue.Count < 1)
@@ -81,10 +85,45 @@ namespace Org.BouncyCastle.Tls.Tests
                     Array.Copy(packet, 0, buf, off, copyLength);
                     return copyLength;
                 }
+#endif
             }
 
+//#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+#if NET6_0_OR_GREATER
+            public virtual int Receive(Span<byte> buffer, int waitMillis)
+            {
+                lock (m_receiveQueue)
+                {
+                    if (m_receiveQueue.Count < 1)
+                    {
+                        try
+                        {
+                            Monitor.Wait(m_receiveQueue, waitMillis);
+                        }
+                        catch (ThreadInterruptedException)
+                        {
+                            // TODO Keep waiting until full wait expired?
+                        }
+
+                        if (m_receiveQueue.Count < 1)
+                            return -1;
+                    }
+
+                    byte[] packet = m_receiveQueue[0];
+                    m_receiveQueue.RemoveAt(0);
+                    int copyLength = System.Math.Min(buffer.Length, packet.Length);
+                    packet.AsSpan(0, copyLength).CopyTo(buffer);
+                    return copyLength;
+                }
+            }
+#endif
+
             public virtual void Send(byte[] buf, int off, int len)
             {
+//#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+#if NET6_0_OR_GREATER
+                Send(buf.AsSpan(off, len));
+#else
                 if (len > m_outer.m_mtu)
                 {
                     // TODO Simulate rejection?
@@ -97,7 +136,27 @@ namespace Org.BouncyCastle.Tls.Tests
                     m_sendQueue.Add(packet);
                     Monitor.PulseAll(m_sendQueue);
                 }
+#endif
+            }
+
+//#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+#if NET6_0_OR_GREATER
+            public virtual void Send(ReadOnlySpan<byte> buffer)
+            {
+                if (buffer.Length > m_outer.m_mtu)
+                {
+                    // TODO Simulate rejection?
+                }
+
+                byte[] packet = buffer.ToArray();
+
+                lock (m_sendQueue)
+                {
+                    m_sendQueue.Add(packet);
+                    Monitor.PulseAll(m_sendQueue);
+                }
             }
+#endif
 
             public virtual void Close()
             {
diff --git a/crypto/test/src/tls/test/UnreliableDatagramTransport.cs b/crypto/test/src/tls/test/UnreliableDatagramTransport.cs
index bdbfd6e67..7769db9d1 100644
--- a/crypto/test/src/tls/test/UnreliableDatagramTransport.cs
+++ b/crypto/test/src/tls/test/UnreliableDatagramTransport.cs
@@ -37,6 +37,10 @@ namespace Org.BouncyCastle.Tls.Tests
 
         public virtual int Receive(byte[] buf, int off, int len, int waitMillis)
         {
+//#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+#if NET6_0_OR_GREATER
+            return Receive(buf.AsSpan(off, len), waitMillis);
+#else
             long endMillis = DateTimeUtilities.CurrentUnixMs() + waitMillis;
             for (;;)
             {
@@ -52,10 +56,37 @@ namespace Org.BouncyCastle.Tls.Tests
 
                 waitMillis = (int)(endMillis - now);
             }
+#endif
         }
 
+//#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+#if NET6_0_OR_GREATER
+        public virtual int Receive(Span<byte> buffer, int waitMillis)
+        {
+            long endMillis = DateTimeUtilities.CurrentUnixMs() + waitMillis;
+            for (;;)
+            {
+                int length = m_transport.Receive(buffer, waitMillis);
+                if (length < 0 || !LostPacket(m_percentPacketLossReceiving))
+                    return length;
+
+                Console.WriteLine("PACKET LOSS (" + length + " byte packet not received)");
+
+                long now = DateTimeUtilities.CurrentUnixMs();
+                if (now >= endMillis)
+                    return -1;
+
+                waitMillis = (int)(endMillis - now);
+            }
+        }
+#endif
+
         public virtual void Send(byte[] buf, int off, int len)
         {
+//#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+#if NET6_0_OR_GREATER
+            Send(buf.AsSpan(off, len));
+#else
             if (LostPacket(m_percentPacketLossSending))
             {
                 Console.WriteLine("PACKET LOSS (" + len + " byte packet not sent)");
@@ -64,7 +95,23 @@ namespace Org.BouncyCastle.Tls.Tests
             {
                 m_transport.Send(buf, off, len);
             }
+#endif
+        }
+
+//#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+#if NET6_0_OR_GREATER
+        public virtual void Send(ReadOnlySpan<byte> buffer)
+        {
+            if (LostPacket(m_percentPacketLossSending))
+            {
+                Console.WriteLine("PACKET LOSS (" + buffer.Length + " byte packet not sent)");
+            }
+            else
+            {
+                m_transport.Send(buffer);
+            }
         }
+#endif
 
         public virtual void Close()
         {