Skip to content

Commit

Permalink
Fix a few Socket.SendFile issues
Browse files Browse the repository at this point in the history
- The string argument in the single-argument overload should be nullable.
- All overloads on Windows should allow a null file path, but they've been throwing an exception
- On Linux, data was silently truncated when sending a file larger than int.MaxValue with BeginSendFile.
  • Loading branch information
stephentoub committed Sep 22, 2020
1 parent 0d62c36 commit 0a548ad
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ internal static partial class Mswsock
[DllImport(Interop.Libraries.Mswsock, SetLastError = true)]
internal static extern unsafe bool TransmitFile(
SafeHandle socket,
SafeHandle? fileHandle,
IntPtr fileHandle,
int numberOfBytesToWrite,
int numberOfBytesPerSend,
NativeOverlapped* overlapped,
Expand Down
2 changes: 1 addition & 1 deletion src/libraries/System.Net.Sockets/ref/System.Net.Sockets.cs
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ public static void Select(System.Collections.IList? checkRead, System.Collection
public int Send(System.ReadOnlySpan<byte> buffer, System.Net.Sockets.SocketFlags socketFlags) { throw null; }
public int Send(System.ReadOnlySpan<byte> buffer, System.Net.Sockets.SocketFlags socketFlags, out System.Net.Sockets.SocketError errorCode) { throw null; }
public bool SendAsync(System.Net.Sockets.SocketAsyncEventArgs e) { throw null; }
public void SendFile(string fileName) { }
public void SendFile(string? fileName) { }
public void SendFile(string? fileName, byte[]? preBuffer, byte[]? 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; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1299,7 +1299,7 @@ public int Send(ReadOnlySpan<byte> buffer, SocketFlags socketFlags, out SocketEr
return bytesTransferred;
}

public void SendFile(string fileName)
public void SendFile(string? fileName)
{
SendFile(fileName, null, null, TransmitFileOptions.UseDefaultWorkerThread);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1809,9 +1809,9 @@ public static SocketError SendAsync(SafeSocketHandle handle, IList<ArraySegment<
}

public static SocketError SendFileAsync(SafeSocketHandle handle, FileStream fileStream, Action<long, SocketError> callback) =>
SendFileAsync(handle, fileStream, 0, (int)fileStream.Length, callback);
SendFileAsync(handle, fileStream, 0, fileStream.Length, callback);

private static SocketError SendFileAsync(SafeSocketHandle handle, FileStream fileStream, long offset, int count, Action<long, SocketError> callback)
private static SocketError SendFileAsync(SafeSocketHandle handle, FileStream fileStream, long offset, long count, Action<long, SocketError> callback)
{
long bytesSent;
SocketError socketError = handle.AsyncContext.SendFileAsync(fileStream.SafeFileHandle, offset, count, out bytesSent, callback);
Expand Down Expand Up @@ -1849,7 +1849,7 @@ public static async void SendPacketsAsync(

var tcs = new TaskCompletionSource<SocketError>();
error = SendFileAsync(socket.InternalSafeHandle, fs, e.OffsetLong,
e.Count > 0 ? e.Count : checked((int)(fs.Length - e.OffsetLong)),
e.Count > 0 ? e.Count : fs.Length - e.OffsetLong,
(transferred, se) =>
{
bytesTransferred += transferred;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1137,10 +1137,27 @@ private static unsafe bool TransmitFileHelper(
transmitFileBuffers.TailLength = postBuffer.Length;
}

bool success = Interop.Mswsock.TransmitFile(socket, fileHandle, 0, 0, overlapped,
needTransmitFileBuffers ? &transmitFileBuffers : null, flags);
bool releaseRef = false;
IntPtr fileHandlePtr = IntPtr.Zero;
try
{
if (fileHandle != null)
{
fileHandle.DangerousAddRef(ref releaseRef);
fileHandlePtr = fileHandle.DangerousGetHandle();
}

return success;
return Interop.Mswsock.TransmitFile(
socket, fileHandlePtr, 0, 0, overlapped,
needTransmitFileBuffers ? &transmitFileBuffers : null, flags);
}
finally
{
if (releaseRef)
{
fileHandle!.DangerousRelease();
}
}
}

public static unsafe SocketError SendFileAsync(SafeSocketHandle handle, FileStream? fileStream, byte[]? preBuffer, byte[]? postBuffer, TransmitFileOptions flags, TransmitFileAsyncResult asyncResult)
Expand Down
89 changes: 89 additions & 0 deletions src/libraries/System.Net.Sockets/tests/FunctionalTests/SendFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Versioning;
using System.Threading;
using System.Threading.Tasks;

Expand Down Expand Up @@ -104,6 +105,94 @@ public void NotConnected_ThrowsException()
}
}

[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task SendFile_Empty_SucceedsSendingNothing(bool useAsync)
{
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();

if (useAsync)
{
await Task.Factory.FromAsync<string>(server.BeginSendFile, server.EndSendFile, null, null);
}
else
{
server.SendFile(null);
}
Assert.Equal(0, client.Available);

if (useAsync)
{
await Task.Factory.FromAsync((c, s) => server.BeginSendFile(null, null, null, TransmitFileOptions.UseDefaultWorkerThread, c, s), server.EndSendFile, null);
}
else
{
server.SendFile(null, null, null, TransmitFileOptions.UseDefaultWorkerThread);
}
Assert.Equal(0, client.Available);

server.Send(new byte[1]);
Assert.Equal(1, client.Receive(new byte[2]));
}

[ActiveIssue("https://github.com/dotnet/runtime/issues/42534", TestPlatforms.Windows)]
[OuterLoop("Creates and sends a file several gigabytes long")]
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task SendFile_GreaterThan2GBFile_SendsAllBytes(bool useAsync)
{
const long FileLength = 100L + int.MaxValue;

string tmpFile = GetTestFilePath();
using (FileStream fs = File.Create(tmpFile))
{
fs.SetLength(FileLength);
}

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();

await new Task[]
{
Task.Run(async () =>
{
if (useAsync)
{
await Task.Factory.FromAsync(server.BeginSendFile, server.EndSendFile, tmpFile, null);
}
else
{
server.SendFile(tmpFile);
}
}),
Task.Run(() =>
{
byte[] buffer = new byte[100_000];
long count = 0;
while (count < FileLength)
{
int received = client.Receive(buffer);
Assert.NotEqual(0, received);
count += received;
}
Assert.Equal(0, client.Available);
})
}.WhenAllOrAnyFailed();
}

[OuterLoop]
[Theory]
[MemberData(nameof(SendFileSync_MemberData))]
Expand Down

0 comments on commit 0a548ad

Please sign in to comment.