From a473a9c4b2d5545d8d7ce2269727a633eb90a2c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnther=20Foidl?= Date: Mon, 25 Jan 2021 10:52:51 +0100 Subject: [PATCH] Added Span overloads for Socket.SendFile (#47230) * Added Span overloads for Socket.SendFile * Updated ref * Fixed doc comments * Tests * Remove test for non-blocking Cf. https://github.com/dotnet/runtime/pull/47230#discussion_r561983960 --- .../ref/System.Net.Sockets.cs | 1 + .../src/System/Net/Sockets/Socket.Unix.cs | 6 +- .../src/System/Net/Sockets/Socket.Windows.cs | 2 +- .../src/System/Net/Sockets/Socket.cs | 50 +++++++++++++- .../System/Net/Sockets/SocketPal.Windows.cs | 32 +++++---- .../tests/FunctionalTests/SendFile.cs | 68 +++++++++++++++++++ 6 files changed, 139 insertions(+), 20 deletions(-) diff --git a/src/libraries/System.Net.Sockets/ref/System.Net.Sockets.cs b/src/libraries/System.Net.Sockets/ref/System.Net.Sockets.cs index 22f72fa5233e1..5333105b924aa 100644 --- a/src/libraries/System.Net.Sockets/ref/System.Net.Sockets.cs +++ b/src/libraries/System.Net.Sockets/ref/System.Net.Sockets.cs @@ -407,6 +407,7 @@ public partial class Socket : System.IDisposable public bool SendAsync(System.Net.Sockets.SocketAsyncEventArgs e) { throw null; } public void SendFile(string? fileName) { } public void SendFile(string? fileName, byte[]? preBuffer, byte[]? postBuffer, System.Net.Sockets.TransmitFileOptions flags) { } + public void SendFile(string? fileName, System.ReadOnlySpan preBuffer, System.ReadOnlySpan postBuffer, System.Net.Sockets.TransmitFileOptions flags) { } public bool SendPacketsAsync(System.Net.Sockets.SocketAsyncEventArgs e) { throw null; } public int SendTo(byte[] buffer, int offset, int size, System.Net.Sockets.SocketFlags socketFlags, System.Net.EndPoint remoteEP) { throw null; } public int SendTo(byte[] buffer, int size, System.Net.Sockets.SocketFlags socketFlags, System.Net.EndPoint remoteEP) { throw null; } diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Unix.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Unix.cs index fd9a6c80fcf41..a9e08bd076c96 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Unix.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Unix.cs @@ -195,7 +195,7 @@ private static void CheckTransmitFileOptions(TransmitFileOptions flags) } } - private void SendFileInternal(string? fileName, byte[]? preBuffer, byte[]? postBuffer, TransmitFileOptions flags) + private void SendFileInternal(string? fileName, ReadOnlySpan preBuffer, ReadOnlySpan postBuffer, TransmitFileOptions flags) { CheckTransmitFileOptions(flags); @@ -208,7 +208,7 @@ private void SendFileInternal(string? fileName, byte[]? preBuffer, byte[]? postB { // Send the preBuffer, if any // This will throw on error - if (preBuffer != null && preBuffer.Length > 0) + if (!preBuffer.IsEmpty) { Send(preBuffer); } @@ -230,7 +230,7 @@ private void SendFileInternal(string? fileName, byte[]? preBuffer, byte[]? postB // Send the postBuffer, if any // This will throw on error - if (postBuffer != null && postBuffer.Length > 0) + if (!postBuffer.IsEmpty) { Send(postBuffer); } diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Windows.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Windows.cs index 7fa5853e57902..267bfb0ab0aa5 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Windows.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Windows.cs @@ -392,7 +392,7 @@ private Socket GetOrCreateAcceptSocket(Socket? acceptSocket, bool checkDisconnec return acceptSocket; } - private void SendFileInternal(string? fileName, byte[]? preBuffer, byte[]? postBuffer, TransmitFileOptions flags) + private void SendFileInternal(string? fileName, ReadOnlySpan preBuffer, ReadOnlySpan postBuffer, TransmitFileOptions flags) { // Open the file, if any FileStream? fileStream = OpenFile(fileName); diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs index fc5d60bf4c18d..efb20cb4166f1 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs @@ -1262,10 +1262,57 @@ public int Send(ReadOnlySpan buffer, SocketFlags socketFlags, out SocketEr public void SendFile(string? fileName) { - SendFile(fileName, null, null, TransmitFileOptions.UseDefaultWorkerThread); + SendFile(fileName, ReadOnlySpan.Empty, ReadOnlySpan.Empty, TransmitFileOptions.UseDefaultWorkerThread); } + /// + /// Sends the file and buffers of data to a connected object + /// using the specified value. + /// + /// + /// A that contains the path and name of the file to be sent. This parameter can be . + /// + /// + /// A array that contains data to be sent before the file is sent. This parameter can be . + /// + /// + /// A array that contains data to be sent after the file is sent. This parameter can be . + /// + /// + /// One or more of values. + /// + /// The object has been closed. + /// The object is not connected to a remote host. + /// The object is not in blocking mode and cannot accept this synchronous call. + /// The file was not found. + /// An error occurred when attempting to access the socket. public void SendFile(string? fileName, byte[]? preBuffer, byte[]? postBuffer, TransmitFileOptions flags) + { + SendFile(fileName, preBuffer.AsSpan(), postBuffer.AsSpan(), flags); + } + + /// + /// Sends the file and buffers of data to a connected object + /// using the specified value. + /// + /// + /// A that contains the path and name of the file to be sent. This parameter can be . + /// + /// + /// A that contains data to be sent before the file is sent. This buffer can be empty. + /// + /// + /// A that contains data to be sent after the file is sent. This buffer can be empty. + /// + /// + /// One or more of values. + /// + /// The object has been closed. + /// The object is not connected to a remote host. + /// The object is not in blocking mode and cannot accept this synchronous call. + /// The file was not found. + /// An error occurred when attempting to access the socket. + public void SendFile(string? fileName, ReadOnlySpan preBuffer, ReadOnlySpan postBuffer, TransmitFileOptions flags) { ThrowIfDisposed(); @@ -1279,7 +1326,6 @@ public void SendFile(string? fileName, byte[]? preBuffer, byte[]? postBuffer, Tr if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"::SendFile() SRC:{LocalEndPoint} DST:{RemoteEndPoint} fileName:{fileName}"); SendFileInternal(fileName, preBuffer, postBuffer, flags); - } // Sends data to a specific end point, starting at the indicated location in the buffer. diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Windows.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Windows.cs index a48eb9bbdaf0a..ab701286e435c 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Windows.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Windows.cs @@ -291,13 +291,13 @@ public static unsafe SocketError Send(SafeSocketHandle handle, ReadOnlySpan preBuffer, ReadOnlySpan postBuffer, TransmitFileOptions flags) { fixed (byte* prePinnedBuffer = preBuffer) fixed (byte* postPinnedBuffer = postBuffer) { - bool success = TransmitFileHelper(handle, fileHandle, null, preBuffer, postBuffer, flags); - return (success ? SocketError.Success : GetLastSocketError()); + bool success = TransmitFileHelper(handle, fileHandle, null, (IntPtr)prePinnedBuffer, preBuffer.Length, (IntPtr)postPinnedBuffer, postBuffer.Length, flags); + return success ? SocketError.Success : GetLastSocketError(); } } @@ -1024,25 +1024,27 @@ public static SocketError Shutdown(SafeSocketHandle handle, bool isConnected, bo SafeHandle socket, SafeHandle? fileHandle, NativeOverlapped* overlapped, - byte[]? preBuffer, - byte[]? postBuffer, + IntPtr pinnedPreBuffer, + int preBufferLength, + IntPtr pinnedPostBuffer, + int postBufferLength, TransmitFileOptions flags) { bool needTransmitFileBuffers = false; - Interop.Mswsock.TransmitFileBuffers transmitFileBuffers = default(Interop.Mswsock.TransmitFileBuffers); + Interop.Mswsock.TransmitFileBuffers transmitFileBuffers = default; - if (preBuffer != null && preBuffer.Length > 0) + if (preBufferLength > 0) { needTransmitFileBuffers = true; - transmitFileBuffers.Head = Marshal.UnsafeAddrOfPinnedArrayElement(preBuffer, 0); - transmitFileBuffers.HeadLength = preBuffer.Length; + transmitFileBuffers.Head = pinnedPreBuffer; + transmitFileBuffers.HeadLength = preBufferLength; } - if (postBuffer != null && postBuffer.Length > 0) + if (postBufferLength > 0) { needTransmitFileBuffers = true; - transmitFileBuffers.Tail = Marshal.UnsafeAddrOfPinnedArrayElement(postBuffer, 0); - transmitFileBuffers.TailLength = postBuffer.Length; + transmitFileBuffers.Tail = pinnedPostBuffer; + transmitFileBuffers.TailLength = postBufferLength; } bool releaseRef = false; @@ -1077,8 +1079,10 @@ public static unsafe SocketError SendFileAsync(SafeSocketHandle handle, FileStre handle, fileStream?.SafeFileHandle, asyncResult.DangerousOverlappedPointer, // SafeHandle was just created in SetUnmanagedStructures - preBuffer, - postBuffer, + preBuffer is not null ? Marshal.UnsafeAddrOfPinnedArrayElement(preBuffer, 0) : IntPtr.Zero, + preBuffer?.Length ?? 0, + postBuffer is not null ? Marshal.UnsafeAddrOfPinnedArrayElement(postBuffer, 0) : IntPtr.Zero, + postBuffer?.Length ?? 0, flags); return asyncResult.ProcessOverlappedResult(success, 0); diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendFile.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendFile.cs index 539ed40e867af..d138281212564 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendFile.cs +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendFile.cs @@ -79,6 +79,8 @@ public void Disposed_ThrowsException() { s.Dispose(); Assert.Throws(() => s.SendFile(null)); + Assert.Throws(() => s.SendFile(null, null, null, TransmitFileOptions.UseDefaultWorkerThread)); + Assert.Throws(() => s.SendFile(null, ReadOnlySpan.Empty, ReadOnlySpan.Empty, TransmitFileOptions.UseDefaultWorkerThread)); Assert.Throws(() => s.BeginSendFile(null, null, null)); Assert.Throws(() => s.BeginSendFile(null, null, null, TransmitFileOptions.UseDefaultWorkerThread, null, null)); Assert.Throws(() => s.EndSendFile(null)); @@ -100,11 +102,44 @@ public void NotConnected_ThrowsException() using (Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) { Assert.Throws(() => s.SendFile(null)); + Assert.Throws(() => s.SendFile(null, null, null, TransmitFileOptions.UseDefaultWorkerThread)); + Assert.Throws(() => s.SendFile(null, ReadOnlySpan.Empty, ReadOnlySpan.Empty, TransmitFileOptions.UseDefaultWorkerThread)); Assert.Throws(() => s.BeginSendFile(null, null, null)); Assert.Throws(() => s.BeginSendFile(null, null, null, TransmitFileOptions.UseDefaultWorkerThread, null, null)); } } + [ConditionalTheory] + [PlatformSpecific(TestPlatforms.Windows)] + [InlineData(true)] + [InlineData(false)] + public async Task UdpConnection_ThrowsException(bool useAsync) + { + // Create file to send + byte[] preBuffer; + byte[] postBuffer; + Fletcher32 sentChecksum; + string filename = CreateFileToSend(size: 1, sendPreAndPostBuffers: false, out preBuffer, out postBuffer, out sentChecksum); + + using var client = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + using var listener = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + listener.BindToAnonymousPort(IPAddress.Loopback); + + client.Connect(listener.LocalEndPoint); + + if (useAsync) + { + await Assert.ThrowsAsync(() => Task.Factory.FromAsync(client.BeginSendFile, client.EndSendFile, filename, null)); + } + else + { + Assert.Throws(() => client.SendFile(filename)); + } + + // Clean up the file we created + File.Delete(filename); + } + [Theory] [InlineData(false, false, false)] [InlineData(false, false, true)] @@ -156,6 +191,39 @@ public async Task SendFile_NoFile_Succeeds(bool useAsync, bool usePreBuffer, boo Assert.Equal(0, client.Available); } + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public void SendFileSpan_NoFile_Succeeds(bool usePreBuffer, bool usePostBuffer) + { + using var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + using var listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + listener.BindToAnonymousPort(IPAddress.Loopback); + listener.Listen(1); + + client.Connect(listener.LocalEndPoint); + using Socket server = listener.Accept(); + + server.SendFile(null); + Assert.Equal(0, client.Available); + + byte[] preBuffer = usePreBuffer ? new byte[1] : null; + byte[] postBuffer = usePostBuffer ? new byte[1] : null; + int bytesExpected = (usePreBuffer ? 1 : 0) + (usePostBuffer ? 1 : 0); + + server.SendFile(null, preBuffer.AsSpan(), postBuffer.AsSpan(), TransmitFileOptions.UseDefaultWorkerThread); + + byte[] receiveBuffer = new byte[1]; + for (int i = 0; i < bytesExpected; i++) + { + Assert.Equal(1, client.Receive(receiveBuffer)); + } + + Assert.Equal(0, client.Available); + } + [ActiveIssue("https://github.com/dotnet/runtime/issues/42534", TestPlatforms.Windows)] [OuterLoop("Creates and sends a file several gigabytes long")] [Theory]