Add System.Net.Quic.dll assembly with basic object model and mock implementation...
authorGeoff Kizer <geoffrek@microsoft.com>
Sun, 3 Nov 2019 20:01:32 +0000 (12:01 -0800)
committerStephen Toub <stoub@microsoft.com>
Sun, 3 Nov 2019 20:01:32 +0000 (15:01 -0500)
* add stub System.Net.Quic assembly

* mock QUIC implementation

* QUIC: Remove ConnectAsync on QuicStream (dotnet/corefx#42203)

remove explicit connect on QuicStream and related fixes

* rename Create*Stream to Open*Stream

Commit migrated from https://github.com/dotnet/corefx/commit/b382d0b42ed1d806a886d2293c61055a1e8a8025

15 files changed:
src/libraries/System.Net.Quic/Directory.Build.props [new file with mode: 0644]
src/libraries/System.Net.Quic/System.Net.Quic.sln [new file with mode: 0644]
src/libraries/System.Net.Quic/ref/Configurations.props [new file with mode: 0644]
src/libraries/System.Net.Quic/ref/System.Net.Quic.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/ref/System.Net.Quic.csproj [new file with mode: 0644]
src/libraries/System.Net.Quic/src/Configurations.props [new file with mode: 0644]
src/libraries/System.Net.Quic/src/Resources/Strings.resx [new file with mode: 0644]
src/libraries/System.Net.Quic/src/System.Net.Quic.csproj [new file with mode: 0644]
src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListener.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/src/System/Net/Quic/QuicStream.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/tests/FunctionalTests/Configurations.props [new file with mode: 0644]
src/libraries/System.Net.Quic/tests/FunctionalTests/QuicConnectionTests.cs [new file with mode: 0644]
src/libraries/System.Net.Quic/tests/FunctionalTests/System.Net.Quic.Tests.csproj [new file with mode: 0644]
src/libraries/pkg/Microsoft.Private.PackageBaseline/packageIndex.json

diff --git a/src/libraries/System.Net.Quic/Directory.Build.props b/src/libraries/System.Net.Quic/Directory.Build.props
new file mode 100644 (file)
index 0000000..42479cd
--- /dev/null
@@ -0,0 +1,8 @@
+<Project>
+  <Import Project="..\Directory.Build.props" />
+  <PropertyGroup>
+    <AssemblyVersion>4.2.1.0</AssemblyVersion>
+    <StrongNameKeyId>Microsoft</StrongNameKeyId>
+    <IsNETCoreApp>true</IsNETCoreApp>
+  </PropertyGroup>
+</Project>
\ No newline at end of file
diff --git a/src/libraries/System.Net.Quic/System.Net.Quic.sln b/src/libraries/System.Net.Quic/System.Net.Quic.sln
new file mode 100644 (file)
index 0000000..42dd1bd
--- /dev/null
@@ -0,0 +1,53 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.27213.1
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Net.Quic.Tests", "tests\FunctionalTests\System.Net.Quic.Tests.csproj", "{8CBA022C-635F-4C8D-9D29-CD8AAC68C8E6}"
+       ProjectSection(ProjectDependencies) = postProject
+               {43311AFB-D7C4-4E5A-B1DE-855407F90D1B} = {43311AFB-D7C4-4E5A-B1DE-855407F90D1B}
+       EndProjectSection
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Net.Quic", "src\System.Net.Quic.csproj", "{43311AFB-D7C4-4E5A-B1DE-855407F90D1B}"
+       ProjectSection(ProjectDependencies) = postProject
+               {834E3534-6A11-4A8D-923F-35C1E71CCEC3} = {834E3534-6A11-4A8D-923F-35C1E71CCEC3}
+       EndProjectSection
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Net.Quic", "ref\System.Net.Quic.csproj", "{834E3534-6A11-4A8D-923F-35C1E71CCEC3}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{1A2F9F4A-A032-433E-B914-ADD5992BB178}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{E107E9C1-E893-4E87-987E-04EF0DCEAEFD}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "ref", "{2E666815-2EDB-464B-9DF6-380BF4789AD4}"
+EndProject
+Global
+       GlobalSection(SolutionConfigurationPlatforms) = preSolution
+               Debug|Any CPU = Debug|Any CPU
+               Release|Any CPU = Release|Any CPU
+       EndGlobalSection
+       GlobalSection(ProjectConfigurationPlatforms) = postSolution
+               {8CBA022C-635F-4C8D-9D29-CD8AAC68C8E6}.Debug|Any CPU.ActiveCfg = netcoreapp-Windows_NT-Debug|Any CPU
+               {8CBA022C-635F-4C8D-9D29-CD8AAC68C8E6}.Debug|Any CPU.Build.0 = netcoreapp-Windows_NT-Debug|Any CPU
+               {8CBA022C-635F-4C8D-9D29-CD8AAC68C8E6}.Release|Any CPU.ActiveCfg = netcoreapp-Windows_NT-Release|Any CPU
+               {8CBA022C-635F-4C8D-9D29-CD8AAC68C8E6}.Release|Any CPU.Build.0 = netcoreapp-Windows_NT-Release|Any CPU
+               {43311AFB-D7C4-4E5A-B1DE-855407F90D1B}.Debug|Any CPU.ActiveCfg = netcoreapp-Windows_NT-Debug|Any CPU
+               {43311AFB-D7C4-4E5A-B1DE-855407F90D1B}.Debug|Any CPU.Build.0 = netcoreapp-Windows_NT-Debug|Any CPU
+               {43311AFB-D7C4-4E5A-B1DE-855407F90D1B}.Release|Any CPU.ActiveCfg = netcoreapp-Windows_NT-Release|Any CPU
+               {43311AFB-D7C4-4E5A-B1DE-855407F90D1B}.Release|Any CPU.Build.0 = netcoreapp-Windows_NT-Release|Any CPU
+               {834E3534-6A11-4A8D-923F-35C1E71CCEC3}.Debug|Any CPU.ActiveCfg = netcoreapp-Debug|Any CPU
+               {834E3534-6A11-4A8D-923F-35C1E71CCEC3}.Debug|Any CPU.Build.0 = netcoreapp-Debug|Any CPU
+               {834E3534-6A11-4A8D-923F-35C1E71CCEC3}.Release|Any CPU.ActiveCfg = netcoreapp-Release|Any CPU
+               {834E3534-6A11-4A8D-923F-35C1E71CCEC3}.Release|Any CPU.Build.0 = netcoreapp-Release|Any CPU
+       EndGlobalSection
+       GlobalSection(SolutionProperties) = preSolution
+               HideSolutionNode = FALSE
+       EndGlobalSection
+       GlobalSection(NestedProjects) = preSolution
+               {8CBA022C-635F-4C8D-9D29-CD8AAC68C8E6} = {1A2F9F4A-A032-433E-B914-ADD5992BB178}
+               {43311AFB-D7C4-4E5A-B1DE-855407F90D1B} = {E107E9C1-E893-4E87-987E-04EF0DCEAEFD}
+               {834E3534-6A11-4A8D-923F-35C1E71CCEC3} = {2E666815-2EDB-464B-9DF6-380BF4789AD4}
+       EndGlobalSection
+       GlobalSection(ExtensibilityGlobals) = postSolution
+               SolutionGuid = {6E33D5E2-B9BF-49D1-99FA-3C9AC3412636}
+       EndGlobalSection
+EndGlobal
diff --git a/src/libraries/System.Net.Quic/ref/Configurations.props b/src/libraries/System.Net.Quic/ref/Configurations.props
new file mode 100644 (file)
index 0000000..55d2de1
--- /dev/null
@@ -0,0 +1,7 @@
+<Project>
+  <PropertyGroup>
+    <BuildConfigurations>
+      netcoreapp;
+    </BuildConfigurations>
+  </PropertyGroup>
+</Project>
\ No newline at end of file
diff --git a/src/libraries/System.Net.Quic/ref/System.Net.Quic.cs b/src/libraries/System.Net.Quic/ref/System.Net.Quic.cs
new file mode 100644 (file)
index 0000000..6fe9208
--- /dev/null
@@ -0,0 +1,50 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+// ------------------------------------------------------------------------------
+// Changes to this file must follow the http://aka.ms/api-review process.
+// ------------------------------------------------------------------------------
+
+using System.Threading;
+
+namespace System.Net.Quic
+{
+    public sealed class QuicConnection : System.IDisposable
+    {
+        public QuicConnection(IPEndPoint remoteEndPoint, System.Net.Security.SslClientAuthenticationOptions sslClientAuthenticationOptions, IPEndPoint localEndPoint = null, bool mock = false) { }
+        public System.Threading.Tasks.ValueTask ConnectAsync(System.Threading.CancellationToken cancellationToken = default) { throw null; }
+        public bool Connected => throw null;
+        public IPEndPoint LocalEndPoint => throw null;
+        public IPEndPoint RemoteEndPoint => throw null;
+        public QuicStream OpenUnidirectionalStream() => throw null;
+        public QuicStream OpenBidirectionalStream() => throw null;
+        public System.Threading.Tasks.ValueTask<QuicStream> AcceptStreamAsync(System.Threading.CancellationToken cancellationToken = default) => throw null;
+        public void Close() => throw null;
+        public void Dispose() => throw null;
+    }
+    public sealed class QuicListener : IDisposable
+    {
+        public QuicListener(IPEndPoint listenEndPoint, System.Net.Security.SslServerAuthenticationOptions sslServerAuthenticationOptions, bool mock = false) { }
+        public IPEndPoint ListenEndPoint => throw null;
+        public System.Threading.Tasks.ValueTask<QuicConnection> AcceptConnectionAsync(System.Threading.CancellationToken cancellationToken = default) => throw null;
+        public void Close() => throw null;
+        public void Dispose() => throw null;
+    }
+    public sealed class QuicStream : System.IO.Stream
+    {
+        internal QuicStream() { }
+        public override bool CanSeek => throw null;
+        public override long Length => throw null;
+        public override long Seek(long offset, System.IO.SeekOrigin origin) => throw null;
+        public override void SetLength(long value) => throw null;
+        public override long Position { get => throw null; set => throw null; }
+        public override bool CanRead => throw null;
+        public override bool CanWrite => throw null;
+        public override void Flush() => throw null;
+        public override int Read(byte[] buffer, int offset, int count) => throw null;
+        public override void Write(byte[] buffer, int offset, int count) => throw null;
+        public long StreamId => throw null;
+        public void ShutdownRead() => throw null;
+        public void ShutdownWrite() => throw null;
+    }
+}
diff --git a/src/libraries/System.Net.Quic/ref/System.Net.Quic.csproj b/src/libraries/System.Net.Quic/ref/System.Net.Quic.csproj
new file mode 100644 (file)
index 0000000..6ba1da7
--- /dev/null
@@ -0,0 +1,16 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <Configurations>netcoreapp-Debug;netcoreapp-Release</Configurations>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="System.Net.Quic.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\..\System.IO\ref\System.IO.csproj" />
+    <ProjectReference Include="..\..\System.IO.FileSystem.Primitives\ref\System.IO.FileSystem.Primitives.csproj" />
+    <ProjectReference Include="..\..\System.Net.Primitives\ref\System.Net.Primitives.csproj" />
+    <ProjectReference Include="..\..\System.Net.Security\ref\System.Net.Security.csproj" />
+    <ProjectReference Include="..\..\System.Runtime\ref\System.Runtime.csproj" />
+    <ProjectReference Include="..\..\System.Threading.Tasks\ref\System.Threading.Tasks.csproj" />
+  </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/src/libraries/System.Net.Quic/src/Configurations.props b/src/libraries/System.Net.Quic/src/Configurations.props
new file mode 100644 (file)
index 0000000..1c0ceb0
--- /dev/null
@@ -0,0 +1,8 @@
+<Project>
+  <PropertyGroup>
+    <BuildConfigurations>
+      netcoreapp-Unix;
+      netcoreapp-Windows_NT;
+    </BuildConfigurations>
+  </PropertyGroup>
+</Project>
\ No newline at end of file
diff --git a/src/libraries/System.Net.Quic/src/Resources/Strings.resx b/src/libraries/System.Net.Quic/src/Resources/Strings.resx
new file mode 100644 (file)
index 0000000..8c5c479
--- /dev/null
@@ -0,0 +1,64 @@
+<root>
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+
+  <data name="net_quic_placeholdertext" xml:space="preserve">
+    <value>Placeholder text</value>
+  </data>
+</root>
diff --git a/src/libraries/System.Net.Quic/src/System.Net.Quic.csproj b/src/libraries/System.Net.Quic/src/System.Net.Quic.csproj
new file mode 100644 (file)
index 0000000..ba83e1c
--- /dev/null
@@ -0,0 +1,33 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <AssemblyName>System.Net.Quic</AssemblyName>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <Configurations>netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release</Configurations>
+  </PropertyGroup>
+  <ItemGroup>
+    <!-- All configurations -->
+    <Compile Include="System\Net\Quic\QuicConnection.cs" />
+    <Compile Include="System\Net\Quic\QuicListener.cs" />
+    <Compile Include="System\Net\Quic\QuicStream.cs" />
+    <Compile Include="$(CommonPath)\CoreLib\System\Threading\Tasks\TaskToApm.cs">
+      <Link>Common\CoreLib\System\Threading\Tasks\TaskToApm.cs</Link>
+    </Compile>
+  </ItemGroup>
+  <ItemGroup>
+    <Reference Include="System.Buffers" />
+    <Reference Include="System.Console" />
+    <Reference Include="System.Diagnostics.Debug" />
+    <Reference Include="System.Memory" />
+    <Reference Include="System.Net.NameResolution" />
+    <Reference Include="System.Net.Primitives" />
+    <Reference Include="System.Net.Security" />
+    <Reference Include="System.Net.Sockets" />
+    <Reference Include="System.Resources.ResourceManager" />
+    <Reference Include="System.Runtime" />
+    <Reference Include="System.Runtime.Extensions" />
+    <Reference Include="System.Runtime.InteropServices" />
+    <Reference Include="System.Threading" />
+    <Reference Include="System.Threading.Tasks" />
+    <Reference Include="System.Threading.ThreadPool" />
+  </ItemGroup>
+</Project>
diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs
new file mode 100644 (file)
index 0000000..0180b79
--- /dev/null
@@ -0,0 +1,292 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Buffers.Binary;
+using System.Diagnostics;
+using System.Net.Security;
+using System.Net.Sockets;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace System.Net.Quic
+{
+    public sealed class QuicConnection : IDisposable
+    {
+        private readonly bool _isClient;
+        private bool _disposed = false;
+        private IPEndPoint _remoteEndPoint;
+        private IPEndPoint _localEndPoint;
+        private object _syncObject = new object();
+
+        // !!! TEMPORARY FOR QUIC MOCK SUPPORT
+        private readonly bool _mock = false;
+        private Socket _socket = null;
+        private IPEndPoint _peerListenEndPoint = null;
+        private TcpListener _inboundListener = null;
+        private long _nextOutboundBidirectionalStream;
+        private long _nextOutboundUnidirectionalStream;
+
+        /// <summary>
+        /// Create an outbound QUIC connection.
+        /// </summary>
+        /// <param name="remoteEndPoint">The remote endpoint to connect to.</param>
+        /// <param name="sslClientAuthenticationOptions">TLS options</param>
+        /// <param name="localEndPoint">The local endpoint to connect from.</param>
+        /// <param name="mock">Use mock QUIC implementation.</param>
+        // !!! TEMPORARY FOR QUIC MOCK SUPPORT: Remove "mock" parameter before shipping
+        public QuicConnection(IPEndPoint remoteEndPoint, SslClientAuthenticationOptions sslClientAuthenticationOptions, IPEndPoint localEndPoint = null, bool mock = false)
+        {
+            // TODO: TLS handling
+
+            _mock = mock;
+            _remoteEndPoint = remoteEndPoint;
+            _localEndPoint = localEndPoint;
+
+            _isClient = true;
+            _nextOutboundBidirectionalStream = 0;
+            _nextOutboundUnidirectionalStream = 2;
+        }
+
+        // Constructor for accepted inbound QuicConnections
+        // !!! TEMPORARY FOR QUIC MOCK SUPPORT
+        internal QuicConnection(Socket socket, IPEndPoint peerListenEndPoint, TcpListener inboundListener)
+        {
+            _mock = true;
+            _isClient = false;
+            _nextOutboundBidirectionalStream = 1;
+            _nextOutboundUnidirectionalStream = 3;
+            _socket = socket;
+            _peerListenEndPoint = peerListenEndPoint;
+            _inboundListener = inboundListener;
+            _localEndPoint = (IPEndPoint)socket.LocalEndPoint;
+            _remoteEndPoint = (IPEndPoint)socket.RemoteEndPoint;
+        }
+
+        /// <summary>
+        /// Indicates whether the QuicConnection is connected.
+        /// </summary>
+        public bool Connected
+        {
+            get
+            {
+                CheckDisposed();
+
+                if (_mock)
+                {
+                    return _socket != null;
+                }
+                else
+                {
+                    throw new NotImplementedException();
+                }
+            }
+        }
+
+        public IPEndPoint LocalEndPoint => new IPEndPoint(_localEndPoint.Address, _localEndPoint.Port);
+
+        public IPEndPoint RemoteEndPoint => new IPEndPoint(_remoteEndPoint.Address, _remoteEndPoint.Port);
+
+        /// <summary>
+        /// Connect to the remote endpoint.
+        /// </summary>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public async ValueTask ConnectAsync(CancellationToken cancellationToken = default)
+        {
+            CheckDisposed();
+
+            if (_mock)
+            {
+                if (Connected)
+                {
+                    // TODO: Exception text
+                    throw new InvalidOperationException("Already connected");
+                }
+
+                Socket socket = new Socket(_remoteEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
+                await socket.ConnectAsync(_remoteEndPoint).ConfigureAwait(false);
+                socket.NoDelay = true;
+
+                _localEndPoint = (IPEndPoint)socket.LocalEndPoint;
+
+                // Listen on a new local endpoint for inbound streams
+                TcpListener inboundListener = new TcpListener(_localEndPoint.Address, 0);
+                inboundListener.Start();
+                int inboundListenPort = ((IPEndPoint)inboundListener.LocalEndpoint).Port;
+
+                // Write inbound listen port to socket so server can read it
+                byte[] buffer = new byte[4];
+                BinaryPrimitives.WriteInt32LittleEndian(buffer, inboundListenPort);
+                await socket.SendAsync(buffer, SocketFlags.None).ConfigureAwait(false);
+
+                // Read first 4 bytes to get server listen port
+                int bytesRead = 0;
+                do
+                {
+                    bytesRead += await socket.ReceiveAsync(buffer.AsMemory().Slice(bytesRead), SocketFlags.None).ConfigureAwait(false);
+                } while (bytesRead != buffer.Length);
+
+                int peerListenPort = BinaryPrimitives.ReadInt32LittleEndian(buffer);
+                IPEndPoint peerListenEndPoint = new IPEndPoint(((IPEndPoint)socket.RemoteEndPoint).Address, peerListenPort);
+
+                _socket = socket;
+                _peerListenEndPoint = peerListenEndPoint;
+                _inboundListener = inboundListener;
+            }
+            else
+            {
+                throw new NotImplementedException();
+            }
+        }
+
+        /// <summary>
+        /// Create an outbound unidirectional stream.
+        /// </summary>
+        /// <returns></returns>
+        public QuicStream OpenUnidirectionalStream()
+        {
+            if (_mock)
+            {
+                long streamId;
+                lock (_syncObject)
+                {
+                    streamId = _nextOutboundUnidirectionalStream;
+                    _nextOutboundUnidirectionalStream += 4;
+                }
+
+                return new QuicStream(this, streamId, bidirectional: false);
+            }
+            else
+            {
+                throw new NotImplementedException();
+            }
+        }
+
+        /// <summary>
+        /// Create an outbound bidirectional stream.
+        /// </summary>
+        /// <returns></returns>
+        public QuicStream OpenBidirectionalStream()
+        {
+            if (_mock)
+            {
+                long streamId;
+                lock (_syncObject)
+                {
+                    streamId = _nextOutboundBidirectionalStream;
+                    _nextOutboundBidirectionalStream += 4;
+                }
+
+                return new QuicStream(this, streamId, bidirectional: true);
+            }
+            else
+            {
+                throw new NotImplementedException();
+            }
+        }
+
+        // !!! TEMPORARY FOR QUIC MOCK SUPPORT
+        internal async Task<Socket> CreateOutboundMockStreamAsync(long streamId)
+        {
+            Debug.Assert(_mock);
+            Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
+            await socket.ConnectAsync(_peerListenEndPoint).ConfigureAwait(false);
+            socket.NoDelay = true;
+
+            // Write stream ID to socket so server can read it
+            byte[] buffer = new byte[8];
+            BinaryPrimitives.WriteInt64LittleEndian(buffer, streamId);
+            await socket.SendAsync(buffer, SocketFlags.None).ConfigureAwait(false);
+
+            return socket;
+        }
+
+        /// <summary>
+        /// Accept an incoming stream.
+        /// </summary>
+        /// <returns></returns>
+        public async ValueTask<QuicStream> AcceptStreamAsync(CancellationToken cancellationToken = default)
+        {
+            CheckDisposed();
+
+            if (_mock)
+            {
+                Socket socket = await _inboundListener.AcceptSocketAsync().ConfigureAwait(false);
+
+                // Read first bytes to get stream ID
+                byte[] buffer = new byte[8];
+                int bytesRead = 0;
+                do
+                {
+                    bytesRead += await socket.ReceiveAsync(buffer.AsMemory().Slice(bytesRead), SocketFlags.None).ConfigureAwait(false);
+                } while (bytesRead != buffer.Length);
+
+                long streamId = BinaryPrimitives.ReadInt64LittleEndian(buffer);
+
+                bool clientInitiated = ((streamId & 0b01) == 0);
+                if (clientInitiated == _isClient)
+                {
+                    throw new Exception($"Wrong initiator on accepted stream??? streamId={streamId}, _isClient={_isClient}");
+                }
+
+                bool bidirectional = ((streamId & 0b10) == 0);
+                return new QuicStream(socket, streamId, bidirectional: bidirectional);
+            }
+            else
+            {
+                throw new NotImplementedException();
+            }
+        }
+
+        /// <summary>
+        /// Close the connection and terminate any active streams.
+        /// </summary>
+        public void Close()
+        {
+            Dispose();
+        }
+
+        private void CheckDisposed()
+        {
+            if (_disposed)
+            {
+                throw new ObjectDisposedException(nameof(QuicConnection));
+            }
+        }
+
+        private void Dispose(bool disposing)
+        {
+            if (!_disposed)
+            {
+                if (disposing)
+                {
+                    if (_mock)
+                    {
+                        _socket?.Dispose();
+                        _socket = null;
+
+                        _inboundListener?.Stop();
+                        _inboundListener = null;
+                    }
+                }
+
+                // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
+                // TODO: set large fields to null.
+
+                _disposed = true;
+            }
+        }
+
+        ~QuicConnection()
+        {
+            Dispose(false);
+        }
+
+        public void Dispose()
+        {
+            Dispose(true);
+            GC.SuppressFinalize(this);
+        }
+    }
+}
diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListener.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListener.cs
new file mode 100644 (file)
index 0000000..fc36baf
--- /dev/null
@@ -0,0 +1,151 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Net.Sockets;
+using System.Net.Security;
+using System.Threading.Tasks;
+using System.Threading;
+using System.Buffers.Binary;
+
+namespace System.Net.Quic
+{
+    public sealed class QuicListener : IDisposable
+    {
+        private bool _disposed = false;
+        private SslServerAuthenticationOptions _sslOptions;
+        private IPEndPoint _listenEndPoint;
+
+        // !!! TEMPORARY FOR QUIC MOCK SUPPORT
+        private bool _mock = false;
+        private TcpListener _tcpListener = null;
+
+        /// <summary>
+        /// Create a QUIC listener on the specified local endpoint and start listening.
+        /// </summary>
+        /// <param name="listenEndPoint">The local endpoint to listen on.</param>
+        /// <param name="sslServerAuthenticationOptions">TLS options for the listener.</param>
+        /// <param name="mock">Use mock QUIC implementation.</param>
+        // !!! TEMPORARY FOR QUIC MOCK SUPPORT: Remove "mock" parameter before shipping
+        public QuicListener(IPEndPoint listenEndPoint, SslServerAuthenticationOptions sslServerAuthenticationOptions, bool mock = false)
+        {
+            if (sslServerAuthenticationOptions == null && !mock)
+            {
+                throw new ArgumentNullException(nameof(sslServerAuthenticationOptions));
+            }
+
+            if (listenEndPoint == null)
+            {
+                throw new ArgumentNullException(nameof(listenEndPoint));
+            }
+
+            _sslOptions = sslServerAuthenticationOptions;
+            _listenEndPoint = listenEndPoint;
+
+            _mock = mock;
+            if (mock)
+            {
+                _tcpListener = new TcpListener(listenEndPoint);
+                _tcpListener.Start();
+
+                if (listenEndPoint.Port == 0)
+                {
+                    // Get auto-assigned port
+                    _listenEndPoint = (IPEndPoint)_tcpListener.LocalEndpoint;
+                }
+            }
+            else
+            {
+                throw new NotImplementedException();
+            }
+        }
+
+        // IPEndPoint is mutable, so we must create a new instance every time this is retrieved.
+        public IPEndPoint ListenEndPoint => new IPEndPoint(_listenEndPoint.Address, _listenEndPoint.Port);
+
+        /// <summary>
+        /// Accept a connection.
+        /// </summary>
+        /// <returns></returns>
+        public async ValueTask<QuicConnection> AcceptConnectionAsync(CancellationToken cancellationToken = default)
+        {
+            CheckDisposed();
+
+            if (_mock)
+            {
+                Socket socket = await _tcpListener.AcceptSocketAsync().ConfigureAwait(false);
+                socket.NoDelay = true;
+
+                // Read first 4 bytes to get client listen port
+                byte[] buffer = new byte[4];
+                int bytesRead = 0;
+                do
+                {
+                    bytesRead += await socket.ReceiveAsync(buffer.AsMemory().Slice(bytesRead), SocketFlags.None).ConfigureAwait(false);
+                } while (bytesRead != buffer.Length);
+
+                int peerListenPort = BinaryPrimitives.ReadInt32LittleEndian(buffer);
+                IPEndPoint peerListenEndPoint = new IPEndPoint(((IPEndPoint)socket.RemoteEndPoint).Address, peerListenPort);
+
+                // Listen on a new local endpoint for inbound streams
+                TcpListener inboundListener = new TcpListener(_listenEndPoint.Address, 0);
+                inboundListener.Start();
+                int inboundListenPort = ((IPEndPoint)inboundListener.LocalEndpoint).Port;
+
+                // Write inbound listen port to socket so client can read it
+                BinaryPrimitives.WriteInt32LittleEndian(buffer, inboundListenPort);
+                await socket.SendAsync(buffer, SocketFlags.None).ConfigureAwait(false);
+
+                return new QuicConnection(socket, peerListenEndPoint, inboundListener);
+            }
+            else
+            {
+                throw new NotImplementedException();
+            }
+        }
+
+        /// <summary>
+        /// Stop listening and close the listener.
+        /// </summary>
+        public void Close()
+        {
+            Dispose();
+        }
+
+        private void CheckDisposed()
+        {
+            if (_disposed)
+            {
+                throw new ObjectDisposedException(nameof(QuicListener));
+            }
+        }
+
+        private void Dispose(bool disposing)
+        {
+            if (!_disposed)
+            {
+                if (disposing)
+                {
+                    _tcpListener?.Stop();
+                    _tcpListener = null;
+                }
+
+                // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
+                // TODO: set large fields to null.
+
+                _disposed = true;
+            }
+        }
+
+        ~QuicListener()
+        {
+            Dispose(false);
+        }
+
+        public void Dispose()
+        {
+            Dispose(true);
+            GC.SuppressFinalize(this);
+        }
+    }
+}
diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicStream.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicStream.cs
new file mode 100644 (file)
index 0000000..22df00c
--- /dev/null
@@ -0,0 +1,276 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.IO;
+using System.Net.Security;
+using System.Net.Sockets;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace System.Net.Quic
+{
+    public sealed class QuicStream : Stream
+    {
+        private bool _disposed = false;
+        private readonly long _streamId;
+        private bool _canRead;
+        private bool _canWrite;
+        private QuicConnection _connection;
+
+        // !!! TEMPORARY FOR QUIC MOCK SUPPORT
+        private readonly bool _mock = false;
+        private Socket _socket = null;
+
+        // Constructor for outbound streams
+        // !!! TEMPORARY FOR QUIC MOCK SUPPORT
+        internal QuicStream(QuicConnection connection, long streamId, bool bidirectional)
+        {
+            _mock = true;
+            _connection = connection;
+            _streamId = streamId;
+            _canRead = bidirectional;
+            _canWrite = true;
+        }
+
+        // Constructor for inbound streams
+        // !!! TEMPORARY FOR QUIC MOCK SUPPORT
+        internal QuicStream(Socket socket, long streamId, bool bidirectional)
+        {
+            _mock = true;
+            _socket = socket;
+            _streamId = streamId;
+            _canRead = true;
+            _canWrite = bidirectional;
+        }
+
+        public override bool CanSeek => false;
+        public override long Length => throw new NotSupportedException();
+        public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
+        public override void SetLength(long value) => throw new NotSupportedException();
+        public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
+
+        public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) =>
+            TaskToApm.Begin(ReadAsync(buffer, offset, count, default), callback, state);
+
+        public override int EndRead(IAsyncResult asyncResult) =>
+            TaskToApm.End<int>(asyncResult);
+
+        public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) =>
+            TaskToApm.Begin(WriteAsync(buffer, offset, count, default), callback, state);
+
+        public override void EndWrite(IAsyncResult asyncResult) =>
+            TaskToApm.End(asyncResult);
+
+        private static void ValidateBufferArgs(byte[] buffer, int offset, int count)
+        {
+            if (buffer == null)
+            {
+                throw new ArgumentNullException(nameof(buffer));
+            }
+
+            if ((uint)offset > buffer.Length)
+            {
+                throw new ArgumentOutOfRangeException(nameof(offset));
+            }
+
+            if ((uint)count > buffer.Length - offset)
+            {
+                throw new ArgumentOutOfRangeException(nameof(count));
+            }
+        }
+
+        public override int Read(byte[] buffer, int offset, int count)
+        {
+            ValidateBufferArgs(buffer, offset, count);
+            return Read(buffer.AsSpan(offset, count));
+        }
+
+        public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+        {
+            ValidateBufferArgs(buffer, offset, count);
+            return ReadAsync(new Memory<byte>(buffer, offset, count), cancellationToken).AsTask();
+        }
+
+        public override void Write(byte[] buffer, int offset, int count)
+        {
+            ValidateBufferArgs(buffer, offset, count);
+            Write(buffer.AsSpan(offset, count));
+        }
+
+        public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+        {
+            ValidateBufferArgs(buffer, offset, count);
+            return WriteAsync(new ReadOnlyMemory<byte>(buffer, offset, count), cancellationToken).AsTask();
+        }
+
+        private async ValueTask ConnectAsync(CancellationToken cancellationToken = default)
+        {
+            Debug.Assert(_mock);
+            Debug.Assert(_connection != null, "Stream not connected but no connection???");
+
+            _socket = await _connection.CreateOutboundMockStreamAsync(_streamId).ConfigureAwait(false);
+
+            // Don't need to hold on to the connection any longer.
+            _connection = null;
+        }
+
+        /// <summary>
+        /// QUIC stream ID.
+        /// </summary>
+        public long StreamId
+        {
+            get
+            {
+                CheckDisposed();
+                return _streamId;
+            }
+        }
+
+        public override bool CanRead => _canRead;
+
+        public override int Read(Span<byte> buffer)
+        {
+            CheckDisposed();
+
+            if (!_canRead)
+            {
+                throw new NotSupportedException();
+            }
+
+            if (_mock)
+            {
+                return _socket.Receive(buffer);
+            }
+            else
+            {
+                throw new NotImplementedException();
+            }
+        }
+
+        public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
+        {
+            CheckDisposed();
+
+            if (!_canRead)
+            {
+                throw new NotSupportedException();
+            }
+
+            if (_mock)
+            {
+                if (_socket == null)
+                {
+                    await ConnectAsync(cancellationToken).ConfigureAwait(false);
+                }
+
+                return await _socket.ReceiveAsync(buffer, SocketFlags.None, cancellationToken).ConfigureAwait(false);
+            }
+            else
+            {
+                throw new NotImplementedException();
+            }
+        }
+
+        public override bool CanWrite => _canWrite;
+
+        public override void Write(ReadOnlySpan<byte> buffer)
+        {
+            CheckDisposed();
+
+            if (!_canWrite)
+            {
+                throw new NotSupportedException();
+            }
+
+            if (_mock)
+            {
+                _socket.Send(buffer);
+            }
+            else
+            {
+                throw new NotImplementedException();
+            }
+        }
+
+        public override async ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
+        {
+            CheckDisposed();
+
+            if (!_canWrite)
+            {
+                throw new NotSupportedException();
+            }
+
+            if (_mock)
+            {
+                if (_socket == null)
+                {
+                    await ConnectAsync(cancellationToken).ConfigureAwait(false);
+                }
+
+                await _socket.SendAsync(buffer, SocketFlags.None, cancellationToken).ConfigureAwait(false);
+            }
+            else
+            {
+                throw new NotImplementedException();
+            }
+        }
+
+        public override void Flush()
+        {
+            CheckDisposed();
+        }
+
+        public override Task FlushAsync(CancellationToken cancellationToken)
+        {
+            CheckDisposed();
+
+            return Task.CompletedTask;
+        }
+
+        public void ShutdownRead()
+        {
+            throw new NotImplementedException();
+        }
+
+        public void ShutdownWrite()
+        {
+            CheckDisposed();
+
+            if (_mock)
+            {
+                _socket.Shutdown(SocketShutdown.Send);
+            }
+            else
+            {
+                throw new NotImplementedException();
+            }
+        }
+
+        private void CheckDisposed()
+        {
+            if (_disposed)
+            {
+                throw new ObjectDisposedException(nameof(QuicStream));
+            }
+        }
+
+        protected override void Dispose(bool disposing)
+        {
+            if (!_disposed)
+            {
+                _disposed = true;
+
+                if (_mock)
+                {
+                    _socket?.Dispose();
+                    _socket = null;
+                }
+            }
+
+            base.Dispose(disposing);
+        }
+    }
+}
diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/Configurations.props b/src/libraries/System.Net.Quic/tests/FunctionalTests/Configurations.props
new file mode 100644 (file)
index 0000000..ad8296d
--- /dev/null
@@ -0,0 +1,8 @@
+<Project DefaultTargets="Build">
+  <PropertyGroup>
+    <BuildConfigurations>
+      netcoreapp-Windows_NT;
+      netcoreapp-Unix;
+    </BuildConfigurations>
+  </PropertyGroup>
+</Project>
\ No newline at end of file
diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicConnectionTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicConnectionTests.cs
new file mode 100644 (file)
index 0000000..a13a353
--- /dev/null
@@ -0,0 +1,183 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Net.Quic;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace System.Net.Quic.Tests
+{
+    public class QuicConnectionTests
+    {
+        private static ReadOnlyMemory<byte> s_data = Encoding.UTF8.GetBytes("Hello world!");
+
+        [Fact]
+        public async Task BasicTest()
+        {
+            using (QuicListener listener = new QuicListener(new IPEndPoint(IPAddress.Loopback, 0), sslServerAuthenticationOptions: null, mock: true))
+            {
+                IPEndPoint listenEndPoint = listener.ListenEndPoint;
+
+                await Task.WhenAll(
+                    Task.Run(async () =>
+                        {
+                            // Client code
+                            using (QuicConnection connection = new QuicConnection(listenEndPoint, sslClientAuthenticationOptions: null, mock: true))
+                            {
+                                await connection.ConnectAsync();
+                                using (QuicStream stream = connection.OpenBidirectionalStream())
+                                {
+                                    await stream.WriteAsync(s_data);
+                                }
+                            }
+                        }),
+                    Task.Run(async () =>
+                        {
+                            // Server code
+                            using (QuicConnection connection = await listener.AcceptConnectionAsync())
+                            {
+                                using (QuicStream stream = await connection.AcceptStreamAsync())
+                                {
+                                    byte[] buffer = new byte[s_data.Length];
+                                    int bytesRead = await stream.ReadAsync(buffer);
+                                    Assert.Equal(s_data.Length, bytesRead);
+                                    Assert.True(s_data.Span.SequenceEqual(buffer));
+                                }
+                            }
+                        }));
+            }
+        }
+
+        [Fact]
+        public async Task TestStreams()
+        {
+            using (QuicListener listener = new QuicListener(new IPEndPoint(IPAddress.Loopback, 0), sslServerAuthenticationOptions: null, mock: true))
+            {
+                IPEndPoint listenEndPoint = listener.ListenEndPoint;
+
+                using (QuicConnection clientConnection = new QuicConnection(listenEndPoint, sslClientAuthenticationOptions: null, mock: true))
+                {
+                    Assert.False(clientConnection.Connected);
+                    Assert.Equal(listenEndPoint, clientConnection.RemoteEndPoint);
+
+                    ValueTask connectTask = clientConnection.ConnectAsync();
+                    QuicConnection serverConnection = await listener.AcceptConnectionAsync();
+                    await connectTask;
+
+                    Assert.True(clientConnection.Connected);
+                    Assert.True(serverConnection.Connected);
+                    Assert.Equal(listenEndPoint, serverConnection.LocalEndPoint);
+                    Assert.Equal(listenEndPoint, clientConnection.RemoteEndPoint);
+                    Assert.Equal(clientConnection.LocalEndPoint, serverConnection.RemoteEndPoint);
+
+                    await CreateAndTestBidirectionalStream(clientConnection, serverConnection);
+                    await CreateAndTestBidirectionalStream(serverConnection, clientConnection);
+                    await CreateAndTestUnidirectionalStream(serverConnection, clientConnection);
+                    await CreateAndTestUnidirectionalStream(clientConnection, serverConnection);
+                }
+            }
+        }
+
+        private static async Task CreateAndTestBidirectionalStream(QuicConnection c1, QuicConnection c2)
+        {
+            using (QuicStream s1 = c1.OpenBidirectionalStream())
+            {
+                Assert.True(s1.CanRead);
+                Assert.True(s1.CanWrite);
+
+                ValueTask writeTask = s1.WriteAsync(s_data);
+                using (QuicStream s2 = await c2.AcceptStreamAsync())
+                {
+                    await ReceiveDataAsync(s_data, s2);
+                    await writeTask;
+                    await TestBidirectionalStream(s1, s2);
+                }
+            }
+        }
+
+        private static async Task CreateAndTestUnidirectionalStream(QuicConnection c1, QuicConnection c2)
+        {
+            using (QuicStream s1 = c1.OpenUnidirectionalStream())
+            {
+                Assert.False(s1.CanRead);
+                Assert.True(s1.CanWrite);
+
+                ValueTask writeTask = s1.WriteAsync(s_data);
+                using (QuicStream s2 = await c2.AcceptStreamAsync())
+                {
+                    await ReceiveDataAsync(s_data, s2);
+                    await writeTask;
+                    await TestUnidirectionalStream(s1, s2);
+                }
+            }
+        }
+
+        private static async Task TestBidirectionalStream(QuicStream s1, QuicStream s2)
+        {
+            Assert.True(s1.CanRead);
+            Assert.True(s1.CanWrite);
+            Assert.True(s2.CanRead);
+            Assert.True(s2.CanWrite);
+            Assert.Equal(s1.StreamId, s2.StreamId);
+
+            await SendAndReceiveDataAsync(s_data, s1, s2);
+            await SendAndReceiveDataAsync(s_data, s2, s1);
+            await SendAndReceiveDataAsync(s_data, s2, s1);
+            await SendAndReceiveDataAsync(s_data, s1, s2);
+
+            await SendAndReceiveEOFAsync(s1, s2);
+            await SendAndReceiveEOFAsync(s2, s1);
+        }
+
+        private static async Task TestUnidirectionalStream(QuicStream s1, QuicStream s2)
+        {
+            Assert.False(s1.CanRead);
+            Assert.True(s1.CanWrite);
+            Assert.True(s2.CanRead);
+            Assert.False(s2.CanWrite);
+            Assert.Equal(s1.StreamId, s2.StreamId);
+
+            await SendAndReceiveDataAsync(s_data, s1, s2);
+            await SendAndReceiveDataAsync(s_data, s1, s2);
+
+            await SendAndReceiveEOFAsync(s1, s2);
+        }
+
+        private static async Task SendAndReceiveDataAsync(ReadOnlyMemory<byte> data, QuicStream s1, QuicStream s2)
+        {
+            await s1.WriteAsync(data);
+            await ReceiveDataAsync(data, s2);
+        }
+
+        private static async Task ReceiveDataAsync(ReadOnlyMemory<byte> data, QuicStream s)
+        {
+            Memory<byte> readBuffer = new byte[data.Length];
+
+            int bytesRead = 0;
+            while (bytesRead < data.Length)
+            {
+                bytesRead += await s.ReadAsync(readBuffer.Slice(bytesRead));
+            }
+
+            Assert.True(data.Span.SequenceEqual(readBuffer.Span));
+        }
+
+        private static async Task SendAndReceiveEOFAsync(QuicStream s1, QuicStream s2)
+        {
+            byte[] readBuffer = new byte[1];
+
+            s1.ShutdownWrite();
+
+            int bytesRead = await s2.ReadAsync(readBuffer);
+            Assert.Equal(0, bytesRead);
+
+            // Another read should still give EOF
+            bytesRead = await s2.ReadAsync(readBuffer);
+            Assert.Equal(0, bytesRead);
+        }
+    }
+}
diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/System.Net.Quic.Tests.csproj b/src/libraries/System.Net.Quic/tests/FunctionalTests/System.Net.Quic.Tests.csproj
new file mode 100644 (file)
index 0000000..3e54cc9
--- /dev/null
@@ -0,0 +1,9 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <Configurations>netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release</Configurations>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="QuicConnectionTests.cs" />
+  </ItemGroup>
+</Project>
\ No newline at end of file
index d16ab678d2a80631fa70403098bf491ac7dc7ebc..209c785e285a518d99edee6068746fb9b438ab5c 100644 (file)
         "4.0.12.0": "4.3.0"
       }
     },
+    "System.Net.Quic": {
+      "InboxOn": {
+        "netcoreapp3.0": "4.2.1.0"
+      }
+    },
     "System.Net.Requests": {
       "StableVersions": [
         "3.9.0",