Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions Lib/Network/SocketExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/// @file
/// @copyright Copyright (c) 2026 SafeTwice S.L. All rights reserved.
/// @license See LICENSE.txt

using System;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;

namespace Utilities.DotNet.Network
{
/// <summary>
/// Utility class providing extension methods for the <see cref="Socket"/> class.
/// </summary>
public static class SocketExtensions
{
//===========================================================================
// PUBLIC METHODS
//===========================================================================

/// <summary>
/// Establishes a connection to a remote host.
/// </summary>
/// <param name="socket">Socket used to establish connection.</param>
/// <param name="address">The IPAddress of the remote host to connect to.</param>
/// <param name="port">The port on the remote host to connect to.</param>
/// <param name="timeout">The number of milliseconds to wait for connection to be established,
/// or <see cref="Timeout.Infinite"/> to wait indefinitely.</param>
/// <exception cref="SocketException">An error occurred when accessing the socket.</exception>
/// <exception cref="System.ObjectDisposedException">The <see cref="Socket"/> has been disposed.</exception>
/// <exception cref="System.Security.SecurityException">A caller higher in the call stack does not have permission for the requested operation.</exception>
/// <exception cref="System.InvalidOperationException">The <see cref="Socket"/> has been placed in a listening state by calling <see cref="Socket.Listen(int)"/>.</exception>
public static void Connect( this Socket socket, IPAddress address, int port, int timeout )
{
if( timeout == Timeout.Infinite )
{
socket.Connect( address, port );
}
else
{
Task connectTask;

try
{
connectTask = socket.ConnectAsync( address, port );

if( !connectTask.Wait( timeout ) )
{
Comment on lines +35 to +50
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

timeout is only treated specially for Timeout.Infinite; any other negative value will cause connectTask.Wait(timeout) to throw ArgumentOutOfRangeException, which is then caught and triggers socket.Close(). Consider validating timeout up front (e.g., allow -1 or >= 0) and throwing ArgumentOutOfRangeException without closing the socket for invalid input.

Copilot uses AI. Check for mistakes.
socket.Close();
throw new SocketException( (int) SocketError.TimedOut );
}
Comment on lines +47 to +53
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the wait times out, the code closes the socket and throws, but the connectTask is left running and may later fault due to the close, risking an unobserved task exception. Consider explicitly observing the task outcome (e.g., attach a fault-only continuation to read t.Exception) before returning/throwing, or use an approach that cancels/awaits the connect attempt deterministically.

Copilot uses AI. Check for mistakes.
}
catch( AggregateException ex )
{
socket.Close();
throw ex.InnerException ?? ex;
}
catch( Exception )
{
socket.Close();
throw;
}

Debug.Assert( !connectTask.IsFaulted );
Debug.Assert( socket.Connected );
}
}
}
}
69 changes: 69 additions & 0 deletions Lib/Network/TcpClientExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/// @file
/// @copyright Copyright (c) 2026 SafeTwice S.L. All rights reserved.
/// @license See LICENSE.txt

using System;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;

namespace Utilities.DotNet.Network
{
/// <summary>
/// Utility class providing extension methods for the <see cref="TcpClient"/> class.
/// </summary>
public static class TcpClientExtensions
{
//===========================================================================
// PUBLIC METHODS
//===========================================================================

/// <summary>
/// Establishes a connection to a remote TCP host.
/// </summary>
/// <param name="client">TCP client used to establish connection.</param>
/// <param name="address">The IPAddress of the remote host to connect to.</param>
/// <param name="port">The port on the remote host to connect to.</param>
/// <param name="timeout">The number of milliseconds to wait for connection to be established,
/// or <see cref="Timeout.Infinite"/> to wait indefinitely.</param>
/// <exception cref="SocketException">An error occurred when accessing the socket.</exception>
/// <exception cref="ObjectDisposedException">The <see cref="TcpClient"/> has been disposed.</exception>
public static void Connect( this TcpClient client, IPAddress address, int port, int timeout )
{
if( timeout == Timeout.Infinite )
{
client.Connect( address, port );
}
else
{
Task connectTask;

try
{
connectTask = client.ConnectAsync( address, port );

if( !connectTask.Wait( timeout ) )
{
Comment on lines +33 to +48
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

timeout is only handled specially for Timeout.Infinite; any other negative value will cause connectTask.Wait(timeout) to throw ArgumentOutOfRangeException, which is then caught and triggers client.Close(). Consider validating timeout up front (allow -1 or >= 0) and throwing ArgumentOutOfRangeException without closing the client for invalid input.

Copilot uses AI. Check for mistakes.
client.Close();
throw new SocketException( (int) SocketError.TimedOut );
}
Comment on lines +45 to +51
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the wait times out, the code closes the client and throws, but the connectTask is left running and may later fault due to the close, risking an unobserved task exception. Consider explicitly observing the task outcome (e.g., attach a fault-only continuation to read t.Exception) before returning/throwing, or use an approach that cancels/awaits the connect attempt deterministically.

Copilot uses AI. Check for mistakes.
}
catch( AggregateException ex )
{
client.Close();
throw ex.InnerException ?? ex;
}
Comment thread
jgonzalez-stw marked this conversation as resolved.
catch( Exception )
{
client.Close();
throw;
}

Debug.Assert( !connectTask.IsFaulted );
Debug.Assert( client.Connected );
}
}
}
}
Loading