Skip to content

Commit

Permalink
Take control code
Browse files Browse the repository at this point in the history
  • Loading branch information
drkno committed Feb 18, 2016
1 parent cb8043b commit d831b82
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 52 deletions.
12 changes: 12 additions & 0 deletions PCR1000.Network/Client/ClientRequestCode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// ReSharper disable InconsistentNaming
namespace PCR1000.Network.Client
{
internal enum ClientRequestCode
{
UNKNOWN,
ECHO,
HASCONTROL,
TAKECONTROL,
DISCONNECT
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
using System.Threading;
using PCR1000.Network.Server;

namespace PCR1000.Network
namespace PCR1000.Network.Client
{
/// <summary>
/// Client to connect to a remote radio.
Expand All @@ -38,7 +38,7 @@ public class PcrNetworkClient : IComm
// ReSharper restore ConvertToConstant.Local
private readonly string _server;
private readonly int _port;
private readonly bool _ssl;
private readonly bool _tls;

/// <summary>
/// Unnessercery characters potentially returned in each message.
Expand Down Expand Up @@ -113,10 +113,10 @@ private void ListenThread()
/// </summary>
/// <param name="server">The server to connect to.</param>
/// <param name="port">The port to connect to.</param>
/// <param name="ssl">Use SSL to secure connections. This MUST be symmetric.</param>
/// <param name="tls">Use TLS to secure connections. This MUST be symmetric.</param>
/// <param name="password">The password to connect with.</param>
/// <exception cref="ArgumentException">If invalid arguments are provided.</exception>
public PcrNetworkClient(string server, int port = 4456, bool ssl = false, string password = "")
public PcrNetworkClient(string server, int port = 4456, bool tls = false, string password = "")
{
if (string.IsNullOrWhiteSpace(server) || port <= 0)
{
Expand All @@ -125,7 +125,7 @@ public PcrNetworkClient(string server, int port = 4456, bool ssl = false, string
_password = password;
_server = server;
_port = port;
_ssl = ssl;
_tls = tls;
}

/// <summary>
Expand Down Expand Up @@ -253,13 +253,16 @@ public bool PcrOpen()
return true;
}
_tcpClient = new TcpClient(_server, _port);
_tcpStream = _ssl ? (Stream) new SslStream(_tcpClient.GetStream()) : _tcpClient.GetStream();
_tcpStream = _tls ? (Stream) new SslStream(_tcpClient.GetStream()) : _tcpClient.GetStream();
_tcpBuffer = new byte[_tcpClient.ReceiveBufferSize];
_listenActive = true;
_tcpListen = new Thread(ListenThread) {IsBackground = true};
_tcpListen.Start();
PerformClientHello();
PerformClientAuth();
var code = PerformClientHello();
if (code == ClientResponseCode.INF_AUTH_REQUIRED)
{
PerformClientAuth();
}
return true;
}
catch (Exception ex)
Expand All @@ -281,7 +284,7 @@ public bool PcrClose()
{
return true;
}
SendWait("$DISCONNECT");
SendWait(ConnectedClient.ServerPrefix + "DISCONNECT");
_listenActive = false;
_tcpListen.Abort();
_tcpBuffer = null;
Expand All @@ -296,29 +299,55 @@ public bool PcrClose()
}
}

private void PerformClientHello()
private ClientResponseCode PerformClientHello()
{
const float clientProtocolVersion = 2.0f;

var response = SendWait($"$HELLO {clientProtocolVersion}");
if (!response.StartsWith("$" + ClientErrorCode.SUC_HELLO_PASSED))
var response = SendWait($"{ConnectedClient.ServerPrefix}HELLO {clientProtocolVersion}");
var code = ParseClientCode(response);
if (code != ClientResponseCode.SUC_HELLO_PASSED && code != ClientResponseCode.INF_AUTH_REQUIRED)
{
throw new InvalidOperationException("Cannot connect to server correctly. " + response + ".");
}
return code;
}

private void PerformClientAuth()
{
if (string.IsNullOrEmpty(_password))
var response = SendWait(ConnectedClient.ServerPrefix + "AUTH \"" + _password + "\"");
if (!response.StartsWith(ConnectedClient.ServerPrefix) || !response.Contains(" "))
if (!response.StartsWith(ConnectedClient.ServerPrefix + ClientResponseCode.SUC_AUTH_PASSED))
{
return;
throw new InvalidOperationException("Cannot authenticate with server correctly. " + response + ".");
}
}

var response = SendWait("$AUTH \"" + _password + "\"");
if (!response.StartsWith("$" + ClientErrorCode.SUC_AUTH_PASSED))
private ClientResponseCode ParseClientCode(string input)
{
if (string.IsNullOrEmpty(input) || !input.Contains(" ") || !input.StartsWith(ConnectedClient.ServerPrefix))
{
throw new InvalidOperationException("Cannot authenticate with server correctly. " + response + ".");
throw new InvalidOperationException("Invalid response received from server.");
}

input = input.Substring(1, input.IndexOf(" ", StringComparison.Ordinal) - 1);
ClientResponseCode code;
if (!Enum.TryParse(input, out code))
{
throw new InvalidOperationException("Invalid response received from server.");
}
return code;
}

public bool HasControl()
{
var response = SendWait(ConnectedClient.ServerPrefix + ClientRequestCode.HASCONTROL)?.Trim();
var respYes = ConnectedClient.ServerPrefix + ClientResponseCode.SUC_HASCONTROL_RESPONSE + "\"Yes\"";
return response == respYes;
}

public void TakeControl()
{
Send(ConnectedClient.ServerPrefix + ClientRequestCode.TAKECONTROL);
}
}
}
6 changes: 4 additions & 2 deletions PCR1000.Network/PCR1000.Network.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="PcrNetworkComm.cs" />
<Compile Include="Server\ClientErrorCodes.cs" />
<Compile Include="Client\ClientRequestCode.cs" />
<Compile Include="Client\PcrNetworkComm.cs" />
<Compile Include="Server\ClientResponseCodes.cs" />
<Compile Include="Server\ConnectedClient.cs" />
<Compile Include="Server\PcrNetworkServer.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
Expand All @@ -53,6 +54,7 @@
<Name>PCR1000</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// ReSharper disable InconsistentNaming
namespace PCR1000.Network.Server
{
internal enum ClientErrorCode
internal enum ClientResponseCode
{
// Client Hello
ERR_HELLO_NOTFOUND,
Expand All @@ -11,6 +11,7 @@ internal enum ClientErrorCode
SUC_HELLO_PASSED,

// Auth
INF_AUTH_REQUIRED,
ERR_AUTH_NOTFOUND,
ERR_AUTH_INVALID,
ERR_AUTH_INCORRECT,
Expand Down
58 changes: 30 additions & 28 deletions PCR1000.Network/Server/ConnectedClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,15 @@
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using PCR1000.Network.Client;

namespace PCR1000.Network.Server
{
internal sealed class ConnectedClient
{
private const string ServerPrefix = "$";
internal const string ServerPrefix = "$";
private const float ProtocolVersion = 2.0f;

public event Action<object> OnDisconnect;

private readonly Func<string, bool> _sendFunc;
private readonly Func<bool, bool> _hasControl;
private readonly TcpClient _tcpClient;
Expand Down Expand Up @@ -50,7 +49,7 @@ internal void Send(byte[] bytes)
InternalSend(bytes);
}

private void Send(ClientErrorCode code, string message)
private void Send(ClientResponseCode code, string message)
{
Debug.WriteLine($"Network Send: code={code} message=\"{message}\"");
var cmd = ServerPrefix + code + " \"" + message + "\"";
Expand All @@ -63,7 +62,7 @@ private void InternalSend(byte[] bytes)
_networkStream.Write(bytes, 0, bytes.Length);
}

private void Disconnect(ClientErrorCode errorCode, string message)
private void Disconnect(ClientResponseCode errorCode, string message)
{
Send(errorCode, message);
_shouldListen = false;
Expand All @@ -78,61 +77,61 @@ private void HandleClientHello(string cmd)

if (!cmd.StartsWith("$HELLO "))
{
Disconnect(ClientErrorCode.ERR_HELLO_NOTFOUND, "Polite Clients Say Hello");
Disconnect(ClientResponseCode.ERR_HELLO_NOTFOUND, "Polite Clients Say Hello");
return;
}

var split = cmd.Split(' ');
if (split.Length < helloFields)
{
Disconnect(ClientErrorCode.ERR_HELLO_INVALID, "Unknown Client Hello");
Disconnect(ClientResponseCode.ERR_HELLO_INVALID, "Unknown Client Hello");
return;
}

if (split.Length > helloFields)
{
Send(ClientErrorCode.WAR_HELLO_UNKNOWN, "Client Hello Contains Unknown Values");
Send(ClientResponseCode.WAR_HELLO_UNKNOWN, "Client Hello Contains Unknown Values");
}

float protoVersion;
if (!float.TryParse(split[1], out protoVersion))
{
Disconnect(ClientErrorCode.ERR_HELLO_INVALID, "Unknown Client Hello");
Disconnect(ClientResponseCode.ERR_HELLO_INVALID, "Unknown Client Hello");
return;
}

if (protoVersion < ProtocolVersion)
{
Disconnect(ClientErrorCode.ERR_PROTOVER_TOOOLD, "Protocol Version of Client is Too Old");
Disconnect(ClientResponseCode.ERR_PROTOVER_TOOOLD, "Protocol Version of Client is Too Old");
return;
}
Send(ClientErrorCode.SUC_HELLO_PASSED, "Client has connected to server.");
Send(_isAuthenticated ? ClientResponseCode.SUC_HELLO_PASSED : ClientResponseCode.INF_AUTH_REQUIRED, "Client has connected to server.");
_hasHelloed = true;
}

private void HandleClientAuthentication(string cmd)
{
if (!cmd.StartsWith("$AUTH "))
{
Send(ClientErrorCode.ERR_AUTH_NOTFOUND, "Authentication is required.");
Disconnect(ClientResponseCode.ERR_AUTH_NOTFOUND, "Authentication is required.");
return;
}

var regex = Regex.Match(cmd, "(?<=(^\\$AUTH \"))[^\"]+(?=\")", RegexOptions.Compiled);

if (!regex.Success)
{
Disconnect(ClientErrorCode.ERR_AUTH_INVALID, "Unknown Client Authentication");
Disconnect(ClientResponseCode.ERR_AUTH_INVALID, "Unknown Client Authentication");
return;
}

if (regex.Value != _password)
{
Disconnect(ClientErrorCode.ERR_AUTH_INCORRECT, "The provided authorisation was incorrect.");
Disconnect(ClientResponseCode.ERR_AUTH_INCORRECT, "The provided authorisation was incorrect.");
return;
}

Send(ClientErrorCode.SUC_AUTH_PASSED, "Client has authenticated with server.");
Send(ClientResponseCode.SUC_AUTH_PASSED, "Client has authenticated with server.");
_isAuthenticated = true;
}

Expand All @@ -150,26 +149,29 @@ private void HandleServerCommand(string cmd)
return;
}

switch (cmd?.Trim())
cmd = string.IsNullOrEmpty(cmd) ? cmd : cmd.Substring(ServerPrefix.Length).Trim();
ClientRequestCode code;
Enum.TryParse(cmd, out code);
switch (code)
{
case "$ECHO":
Send(ClientErrorCode.SUC_ECHO_RESPONSE, "Reply name=\"" + Assembly.GetEntryAssembly().FullName + "\" proto=\"" + ProtocolVersion + "\"");
case ClientRequestCode.ECHO:
Send(ClientResponseCode.SUC_ECHO_RESPONSE, "Reply name=\"" + Assembly.GetEntryAssembly().FullName + "\" proto=\"" + ProtocolVersion + "\"");
break;

case "$HASCONTROL":
Send(ClientErrorCode.SUC_HASCONTROL_RESPONSE, _hasControl(false) ? "Yes" : "No");
case ClientRequestCode.HASCONTROL:
Send(ClientResponseCode.SUC_HASCONTROL_RESPONSE, _hasControl(false) ? "Yes" : "No");
break;

case "$TAKECONTROL":
Send(ClientErrorCode.SUC_TAKECONTROL_RESPONSE, _hasControl(true) ? "Yes" : "No");
case ClientRequestCode.TAKECONTROL:
Send(ClientResponseCode.SUC_TAKECONTROL_RESPONSE, _hasControl(true) ? "Yes" : "No");
break;

case "$DISCONNECT":
// Disconnect(ClientErrorCode.INF_CLIENT_DISCONNECT, "Client initiated disconnect.");
case ClientRequestCode.DISCONNECT:
// Disconnect(ClientResponseCode.INF_CLIENT_DISCONNECT, "Client initiated disconnect.");
break;

default:
Send(ClientErrorCode.WAR_COMMAND_UNKNOWN, "The command \"" + cmd + "\" is unknown.");
Send(ClientResponseCode.WAR_COMMAND_UNKNOWN, "The command \"" + cmd + "\" is unknown.");
break;
}
}
Expand Down Expand Up @@ -205,12 +207,12 @@ internal void Listen(object listenThread)
if (_hasControl(false))
{
Debug.WriteLine("Network: Query Failed: " + cmd);
Send(ClientErrorCode.ERR_QUERY_FAILED, "The command provided failed.");
Send(ClientResponseCode.ERR_QUERY_FAILED, "The command provided failed.");
}
else
{
Debug.WriteLine("Network: Another client has control.");
Send(ClientErrorCode.ERR_HASCONTROL_RESPONSE, "No");
Send(ClientResponseCode.ERR_HASCONTROL_RESPONSE, "No");
}
}
catch (Exception e)
Expand All @@ -228,7 +230,7 @@ internal void Listen(object listenThread)
}
}

Disconnect(ClientErrorCode.INF_CLIENT_DISCONNECT, "Client has initiated disconnect.");
Disconnect(ClientResponseCode.INF_CLIENT_DISCONNECT, "Client has initiated disconnect.");
}

/// <summary>
Expand Down
1 change: 1 addition & 0 deletions PCR1000LibTest/Control.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Windows.Forms;
using PCR1000;
using PCR1000.Network;
using PCR1000.Network.Client;

namespace PCR1000LibTest
{
Expand Down
3 changes: 1 addition & 2 deletions PCRNetworkServer/Cli.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using System.Net;
using System.Net.Sockets;
using PCR1000;
using PCR1000.Network;
using PCR1000.Network.Server;

namespace PCRNetworkServer
Expand Down Expand Up @@ -48,7 +47,7 @@ public static void Run(bool security, string password, int port, string device)
Console.Title = title;
}

public static string GetLocalIPAddress()
private static string GetLocalIPAddress()
{
var host = Dns.GetHostEntry(Dns.GetHostName());
foreach (var ip in host.AddressList.Where(ip => ip.AddressFamily == AddressFamily.InterNetwork))
Expand Down
4 changes: 2 additions & 2 deletions PCRNetworkServer/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,12 +140,12 @@ public static void Main(string[] args)
else if (e is InvalidOperationException)
{
Console.Error.WriteLine("A fatal error occured while running " + name +
". " + e.Message);
".\n" + e.Message);
}
else
{
Console.Error.WriteLine("A fatal error occured while running " + name +
". One of your options was probably malformed. ");
".\nOne of your options was probably malformed.");
#if DEBUG
Console.Error.WriteLine(e.StackTrace);
#endif
Expand Down

0 comments on commit d831b82

Please sign in to comment.