using System.Threading;
using Hazel.Tcp;
+using System.Linq;
namespace Hazel.UnitTests
{
//TcpConnection fields
Assert.AreEqual(new IPEndPoint(IPAddress.Loopback, 4296), connection.RemoteEndPoint);
- Assert.AreEqual(0, connection.Statistics.DataBytesSent);
+ Assert.AreEqual(1, connection.Statistics.DataBytesSent);
Assert.AreEqual(0, connection.Statistics.DataBytesReceived);
}
}
+ [TestMethod]
+ public void TcpHandshakeTest()
+ {
+ using (TcpConnectionListener listener = new TcpConnectionListener(IPAddress.Any, 4296, IPMode.IPv4))
+ using (TcpConnection connection = new TcpConnection(new NetworkEndPoint(IPAddress.Loopback, 4296, IPMode.IPv4)))
+ {
+ listener.Start();
+
+ listener.NewConnection += delegate (object sender, NewConnectionEventArgs e)
+ {
+ Assert.IsTrue(Enumerable.SequenceEqual(e.HandshakeData, new byte[] { 1, 2, 3, 4, 5, 6 }));
+ };
+
+ connection.Connect(new byte[] { 1, 2, 3, 4, 5, 6 });
+ }
+ }
+
/// <summary>
/// Tests IPv4 connectivity.
/// </summary>
using (TcpConnectionListener listener = new TcpConnectionListener(IPAddress.Any, 4296))
using (TcpConnection connection = new TcpConnection(new NetworkEndPoint(IPAddress.Loopback, 4296)))
{
- TestHelper.RunServerToClientTest(listener, connection, 4, 0, SendOption.FragmentedReliable);
+ TestHelper.RunServerToClientTest(listener, connection, 4, 5, SendOption.FragmentedReliable);
}
}
using (TcpConnectionListener listener = new TcpConnectionListener(IPAddress.Any, 4296))
using (TcpConnection connection = new TcpConnection(new NetworkEndPoint(IPAddress.Loopback, 4296)))
{
- TestHelper.RunClientToServerTest(listener, connection, 4, 0, SendOption.FragmentedReliable);
+ TestHelper.RunClientToServerTest(listener, connection, 4, 5, SendOption.FragmentedReliable);
}
}
//Wait until data is received
mutex.WaitOne();
- Assert.AreEqual(0, connection.Statistics.DataBytesSent);
+ Assert.AreEqual(1, connection.Statistics.DataBytesSent);
Assert.AreEqual(data.Length, connection.Statistics.DataBytesReceived);
Assert.AreEqual(totalHandshakeSize, connection.Statistics.TotalBytesSent);
Assert.AreEqual(data.Length + headerSize, connection.Statistics.TotalBytesReceived);
//Wait until data is received
mutex2.WaitOne();
- Assert.AreEqual(data.Length, connection.Statistics.DataBytesSent);
+ Assert.AreEqual(data.Length + 1, connection.Statistics.DataBytesSent);
Assert.AreEqual(0, connection.Statistics.DataBytesReceived);
Assert.AreEqual(totalHandshakeSize + data.Length + headerSize, connection.Statistics.TotalBytesSent);
Assert.AreEqual(0, connection.Statistics.TotalBytesReceived);
using System.Threading;
using Hazel.Udp;
+using System.Linq;
namespace Hazel.UnitTests
{
//UdpConnection fields
Assert.AreEqual(new IPEndPoint(IPAddress.Loopback, 4296), connection.RemoteEndPoint);
- Assert.AreEqual(0, connection.Statistics.DataBytesSent);
+ Assert.AreEqual(1, connection.Statistics.DataBytesSent);
Assert.AreEqual(0, connection.Statistics.DataBytesReceived);
}
}
+ [TestMethod]
+ public void UdpHandshakeTest()
+ {
+ using (UdpConnectionListener listener = new UdpConnectionListener(IPAddress.Any, 4296, IPMode.IPv4))
+ using (UdpConnection connection = new UdpClientConnection(new NetworkEndPoint(IPAddress.Loopback, 4296, IPMode.IPv4)))
+ {
+ listener.Start();
+
+ listener.NewConnection += delegate (object sender, NewConnectionEventArgs e)
+ {
+ Assert.IsTrue(Enumerable.SequenceEqual(e.HandshakeData, new byte[] { 1, 2, 3, 4, 5, 6 }));
+ };
+
+ connection.Connect(new byte[] { 1, 2, 3, 4, 5, 6 });
+ }
+ }
+
/// <summary>
/// Tests IPv4 connectivity.
/// </summary>
using (UdpConnectionListener listener = new UdpConnectionListener(new NetworkEndPoint(IPAddress.Any, 4296)))
using (UdpConnection connection = new UdpClientConnection(new NetworkEndPoint(IPAddress.Loopback, 4296)))
{
- TestHelper.RunServerToClientTest(listener, connection, 1, 3, SendOption.None);
+ TestHelper.RunServerToClientTest(listener, connection, 1, 4, SendOption.None);
}
}
using (UdpConnectionListener listener = new UdpConnectionListener(new NetworkEndPoint(IPAddress.Any, 4296)))
using (UdpConnection connection = new UdpClientConnection(new NetworkEndPoint(IPAddress.Loopback, 4296)))
{
- TestHelper.RunServerToClientTest(listener, connection, 3, 3, SendOption.Reliable);
+ TestHelper.RunServerToClientTest(listener, connection, 3, 4, SendOption.Reliable);
}
}
using (UdpConnectionListener listener = new UdpConnectionListener(new NetworkEndPoint(IPAddress.Any, 4296)))
using (UdpConnection connection = new UdpClientConnection(new NetworkEndPoint(IPAddress.Loopback, 4296)))
{
- TestHelper.RunClientToServerTest(listener, connection, 1, 3, SendOption.None);
+ TestHelper.RunClientToServerTest(listener, connection, 1, 4, SendOption.None);
}
}
using (UdpConnectionListener listener = new UdpConnectionListener(new NetworkEndPoint(IPAddress.Any, 4296)))
using (UdpConnection connection = new UdpClientConnection(new NetworkEndPoint(IPAddress.Loopback, 4296)))
{
- TestHelper.RunClientToServerTest(listener, connection, 3, 3, SendOption.Reliable);
+ TestHelper.RunClientToServerTest(listener, connection, 3, 4, SendOption.Reliable);
}
}
connection.Connect();
connection.KeepAliveInterval = 100;
- System.Threading.Thread.Sleep(1100); //Enough time for ~10 keep alive packets
+ System.Threading.Thread.Sleep(1050); //Enough time for ~10 keep alive packets
Assert.IsTrue(
- connection.Statistics.TotalBytesSent >= 27 &&
- connection.Statistics.TotalBytesSent <= 33,
+ connection.Statistics.TotalBytesSent >= 30 &&
+ connection.Statistics.TotalBytesSent <= 50,
"Sent: " + connection.Statistics.TotalBytesSent
);
}
{
((UdpConnection)args.Connection).KeepAliveInterval = 100;
- Thread.Sleep(1100); //Enough time for ~10 keep alive packets
+ Thread.Sleep(1050); //Enough time for ~10 keep alive packets
Assert.IsTrue(
- args.Connection.Statistics.TotalBytesSent >= 27 &&
- args.Connection.Statistics.TotalBytesSent <= 33,
- "Sent: " + connection.Statistics.TotalBytesSent
+ args.Connection.Statistics.TotalBytesSent >= 30 &&
+ args.Connection.Statistics.TotalBytesSent <= 50,
+ "Sent: " + args.Connection.Statistics.TotalBytesSent
);
mutex.Set();
/// <summary>
/// Connects the connection to a server and begins listening.
/// </summary>
+ /// <param name="bytes">The bytes of data to send in the handshake.</param>
/// <remarks>
/// Calling Connect makes the connection attempt to connect to the end point that's specified in the
/// constructor. This method will block until the connection attempt completes and will throw a
/// <see cref="HazelException"/> if there is a problem connecting.
/// </remarks>
- public abstract void Connect();
+ public abstract void Connect(byte[] bytes = null);
/// <summary>
/// Invokes the DataReceived event.
/// <summary>
/// Invokes the NewConnection event with the supplied connection.
/// </summary>
+ /// <param name="bytes">The user sent bytes that were received as part of the handshake.</param>
/// <param name="connection">The connection to pass in the arguments.</param>
/// <remarks>
/// Implementers should call this to invoke the <see cref="NewConnection"/> event before data is received so that
/// subscribers do not miss any data that may have been sent immediately after connecting.
/// </remarks>
- protected void InvokeNewConnection(Connection connection)
+ protected void InvokeNewConnection(byte[] bytes, Connection connection)
{
//Get new args
NewConnectionEventArgs args = NewConnectionEventArgs.GetObject();
- args.Set(connection);
+ args.Set(bytes, connection);
//Make a copy to avoid race condition between null check and invocation
EventHandler<NewConnectionEventArgs> handler = NewConnection;
return objectPool.GetObject();
}
+ /// <summary>
+ /// The data received from the client in the handshake.
+ /// </summary>
+ public byte[] HandshakeData { get; private set; }
+
/// <summary>
/// The <see cref="Connection"/> to the new client.
/// </summary>
/// <summary>
/// Sets the members of the arguments.
/// </summary>
- /// <param name="Connection">The new connection</param>
- internal void Set(Connection Connection)
+ /// <param name="bytes">The bytes that were received in the handshake.</param>
+ /// <param name="connection">The new connection</param>
+ internal void Set(byte[] bytes, Connection connection)
{
- this.Connection = Connection;
+ this.HandshakeData = bytes;
+ this.Connection = connection;
}
/// <inheritdoc />
}
/// <inheritdoc />
- public override void Connect()
+ public override void Connect(byte[] bytes = null)
{
lock(socketLock)
{
//Start receiving data
try
{
- StartWaitingForHeader();
+ StartWaitingForHeader(BodyReadCallback);
}
- catch (SocketException e)
+ catch (SocketException e) //TODO change these to catch exception, security risk
{
throw new HazelException("A Socket exception occured while initiating the first receive operation.", e);
}
+ //Send handshake
+ byte[] actualBytes;
+ if (bytes == null)
+ {
+ actualBytes = new byte[1];
+ }
+ else
+ {
+ actualBytes = new byte[bytes.Length + 1];
+ Buffer.BlockCopy(bytes, 0, actualBytes, 1, bytes.Length);
+ }
+
//Set connected
State = ConnectionState.Connected;
+
+ SendBytes(actualBytes);
}
}
/// Called when a 4 byte header has been received.
/// </summary>
/// <param name="bytes">The 4 header bytes read.</param>
- void HeaderReadCallback(byte[] bytes)
+ /// <param name="callback">The callback to invoke when the body has been received.</param>
+ void HeaderReadCallback(byte[] bytes, Action<byte[]> callback)
{
//Get length
int length = GetLengthFromBytes(bytes);
//Begin receiving the body
try
{
- StartWaitingForBytes(length, BodyReadCallback);
+ StartWaitingForBytes(length, callback);
}
catch (SocketException e)
{
//Begin receiving from the start
try
{
- StartWaitingForHeader();
+ StartWaitingForHeader(BodyReadCallback);
}
catch (SocketException e)
{
{
try
{
- StartWaitingForHeader();
+ StartWaitingForHeader(BodyReadCallback);
+ }
+ catch (SocketException e)
+ {
+ HandleDisconnect(new HazelException("A Socket exception occured while initiating the first receive operation.", e));
+ }
+ }
+
+ /// <summary>
+ /// Starts waiting for a first handshake packet to be received.
+ /// </summary>
+ /// <param name="callback">The callback to invoke when the handshake has been received.</param>
+ internal void StartWaitingForHandshake(Action<byte[]> callback)
+ {
+ try
+ {
+ StartWaitingForHeader(
+ delegate (byte[] bytes)
+ {
+ //Remove version byte
+ byte[] dataBytes = new byte[bytes.Length - 1];
+ Buffer.BlockCopy(bytes, 1, dataBytes, 0, bytes.Length - 1);
+
+ callback.Invoke(dataBytes);
+ }
+ );
}
catch (SocketException e)
{
/// <summary>
/// Starts this connections waiting for the header.
/// </summary>
- void StartWaitingForHeader()
+ /// <param name="callback">The callback to invoke when the body has been read.</param>
+ void StartWaitingForHeader(Action<byte[]> callback)
{
- StartWaitingForBytes(4, HeaderReadCallback);
+ StartWaitingForBytes(4, (bytes) => HeaderReadCallback(bytes, callback));
}
/// <summary>
//Sort the event out
TcpConnection tcpConnection = new TcpConnection(tcpSocket);
- //Invoke
- InvokeNewConnection(tcpConnection);
-
- tcpConnection.StartReceiving();
+ //Wait for handshake
+ tcpConnection.StartWaitingForHandshake(
+ delegate (byte[] bytes)
+ {
+ //Invoke
+ InvokeNewConnection(bytes, tcpConnection);
+
+ tcpConnection.StartReceiving();
+ }
+ );
}
}
}
/// <inheritdoc />
- public override void Connect()
+ public override void Connect(byte[] bytes = null)
{
lock(socketLock)
{
//Write bytes to the server to tell it hi (and to punch a hole in our NAT, if present)
//When acknowledged set the state to connected
- SendHello(() => { lock (socketLock) State = ConnectionState.Connected; });
+ SendHello(bytes, () => { lock (socketLock) State = ConnectionState.Connected; });
//Wait till hello packet is acknowledged and the state is set to Connected
WaitOnConnect();
(o) =>
{
Trace.WriteLine("Keepalive packet sent.");
- SendHello(null);
+ SendHello(null, null);
},
null,
keepAliveInterval,
byte[] bytes;
switch (sendOption)
{
- //Handle reliable header
+ //Handle reliable header and hellos
case (byte)SendOption.Reliable:
- bytes = new byte[data.Length + 3];
- WriteReliableSendHeader(bytes, ackCallback);
- break;
-
- //Handle hellos (ignore data)
case (byte)SendOptionInternal.Hello:
- bytes = new byte[3];
+ bytes = new byte[data.Length + 3];
WriteReliableSendHeader(bytes, ackCallback);
break;
/// Sends a hello packet to the remote endpoint.
/// </summary>
/// <param name="acknowledgeCallback">The callback to invoke when the hello packet is acknowledged.</param>
- protected void SendHello(Action acknowledgeCallback)
+ protected void SendHello(byte[] bytes, Action acknowledgeCallback)
{
- HandleSend(new byte[0], (byte)SendOptionInternal.Hello, acknowledgeCallback);
+ //First byte of handshake is version indicator so add data after
+ byte[] actualBytes;
+ if (bytes == null)
+ {
+ actualBytes = new byte[1];
+ }
+ else
+ {
+ actualBytes = new byte[bytes.Length + 1];
+ Buffer.BlockCopy(bytes, 0, actualBytes, 1, bytes.Length);
+ }
+
+ HandleSend(actualBytes, (byte)SendOptionInternal.Hello, acknowledgeCallback);
}
/// <summary>
else
{
//Check for malformed connection attempts
- if (buffer[0] != (byte)SendOptionInternal.Hello || buffer.Length != 3)
+ if (buffer[0] != (byte)SendOptionInternal.Hello)
return;
connection = new UdpServerConnection(this, remoteEndPoint, IPMode);
//And fire the corresponding event
if (aware)
+ {
connection.InvokeDataReceived(buffer);
+ }
else
- InvokeNewConnection(connection);
+ {
+ byte[] dataBuffer = new byte[buffer.Length - 1];
+ Buffer.BlockCopy(buffer, 1, dataBuffer, 0, buffer.Length - 1);
+ InvokeNewConnection(dataBuffer, connection);
+ }
}
/// <summary>
/// <remarks>
/// This will always throw a HazelException.
/// </remarks>
- public override void Connect()
+ public override void Connect(byte[] bytes)
{
throw new HazelException("Cannot manually connect a UdpServerConnection, did you mean to use UdpClientConnection?");
}