initial commit
This commit is contained in:
@@ -0,0 +1,511 @@
|
||||
using Pulsar.Common.Messages.Other;
|
||||
using Pulsar.Common.Networking;
|
||||
using Pulsar.Server.Forms;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading;
|
||||
|
||||
namespace Pulsar.Server.Networking
|
||||
{
|
||||
public class Client : IEquatable<Client>, ISender
|
||||
{
|
||||
/// <summary>
|
||||
/// Occurs as a result of an unrecoverable issue with the client.
|
||||
/// </summary>
|
||||
public event ClientFailEventHandler ClientFail;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a method that will handle failure of the client.
|
||||
/// </summary>
|
||||
/// <param name="s">The client that has failed.</param>
|
||||
/// <param name="ex">The exception containing information about the cause of the client's failure.</param>
|
||||
public delegate void ClientFailEventHandler(Client s, Exception ex);
|
||||
|
||||
/// <summary>
|
||||
/// Fires an event that informs subscribers that the client has failed.
|
||||
/// </summary>
|
||||
/// <param name="ex">The exception containing information about the cause of the client's failure.</param>
|
||||
private void OnClientFail(Exception ex)
|
||||
{
|
||||
var handler = ClientFail;
|
||||
handler?.Invoke(this, ex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the state of the client changes.
|
||||
/// </summary>
|
||||
public event ClientStateEventHandler ClientState;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the method that will handle a change in a client's state.
|
||||
/// </summary>
|
||||
/// <param name="s">The client which changed its state.</param>
|
||||
/// <param name="connected">The new connection state of the client.</param>
|
||||
public delegate void ClientStateEventHandler(Client s, bool connected);
|
||||
|
||||
/// <summary>
|
||||
/// Fires an event that informs subscribers that the state of the client has changed.
|
||||
/// </summary>
|
||||
/// <param name="connected">The new connection state of the client.</param>
|
||||
private void OnClientState(bool connected)
|
||||
{
|
||||
if (Connected == connected) return;
|
||||
|
||||
Connected = connected;
|
||||
|
||||
var handler = ClientState;
|
||||
handler?.Invoke(this, connected);
|
||||
|
||||
if (connected)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a message is received from the client.
|
||||
/// </summary>
|
||||
public event ClientReadEventHandler ClientRead;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the method that will handle a message received from the client.
|
||||
/// </summary>
|
||||
/// <param name="s">The client that has received the message.</param>
|
||||
/// <param name="message">The message that received by the client.</param>
|
||||
/// <param name="messageLength">The length of the message.</param>
|
||||
public delegate void ClientReadEventHandler(Client s, IMessage message, int messageLength);
|
||||
|
||||
/// <summary>
|
||||
/// Fires an event that informs subscribers that a message has been
|
||||
/// received from the client.
|
||||
/// </summary>
|
||||
/// <param name="message">The message that received by the client.</param>
|
||||
/// <param name="messageLength">The length of the message.</param>
|
||||
private void OnClientRead(IMessage message, int messageLength)
|
||||
{
|
||||
Debug.WriteLine($"[SERVER] Received packet: {message.GetType().Name} (Length: {messageLength} bytes) from {EndPoint}");
|
||||
var handler = ClientRead;
|
||||
handler?.Invoke(this, message, messageLength);
|
||||
}
|
||||
|
||||
public static bool operator ==(Client c1, Client c2)
|
||||
{
|
||||
if (ReferenceEquals(c1, null))
|
||||
return ReferenceEquals(c2, null);
|
||||
|
||||
return c1.Equals(c2);
|
||||
}
|
||||
|
||||
public static bool operator !=(Client c1, Client c2)
|
||||
{
|
||||
return !(c1 == c2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the clients are equal.
|
||||
/// </summary>
|
||||
/// <param name="other">Client to compare with.</param>
|
||||
/// <returns>True if equal, else False.</returns>
|
||||
public bool Equals(Client other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
|
||||
try
|
||||
{
|
||||
// the port is always unique for each client
|
||||
return this.EndPoint.Port.Equals(other.EndPoint.Port);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return this.Equals(obj as Client);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the hashcode for this instance.
|
||||
/// </summary>
|
||||
/// <returns>A hash code for the current instance.</returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return this.EndPoint.GetHashCode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The stream used for communication.
|
||||
/// </summary>
|
||||
private Stream _stream;
|
||||
private readonly X509Certificate2 _serverCertificate;
|
||||
private readonly bool _encryptTraffic;
|
||||
|
||||
readonly object _readMessageLock = new object();
|
||||
readonly object _sendMessageLock = new object();
|
||||
readonly object _proxyClientsLock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// The buffer for the client's incoming payload.
|
||||
/// </summary>
|
||||
private byte[] _readBuffer;
|
||||
|
||||
/// <summary>
|
||||
/// The queue which holds messages to send.
|
||||
/// </summary>
|
||||
private readonly ConcurrentQueue<IMessage> _sendBuffers = new ConcurrentQueue<IMessage>();
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the client is currently sending messages.
|
||||
/// </summary>
|
||||
private int _sendingMessagesFlag;
|
||||
|
||||
// Receive info
|
||||
private int _readOffset;
|
||||
private int _readLength;
|
||||
|
||||
/// <summary>
|
||||
/// The time when the client connected.
|
||||
/// </summary>
|
||||
public DateTime ConnectedTime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The connection state of the client.
|
||||
/// </summary>
|
||||
public bool Connected { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the client is identified.
|
||||
/// </summary>
|
||||
public bool Identified { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Stores values of the user.
|
||||
/// </summary>
|
||||
public UserState Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether this client currently allows clipboard mirroring to the server host.
|
||||
/// </summary>
|
||||
public bool ClipboardSyncEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Endpoint which the client is connected to.
|
||||
/// </summary>
|
||||
public IPEndPoint EndPoint { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The header size in bytes.
|
||||
/// </summary>
|
||||
private const int HEADER_SIZE = 4; // 4 B
|
||||
|
||||
public Client(Stream stream, IPEndPoint endPoint, X509Certificate2 serverCertificate)
|
||||
{
|
||||
try
|
||||
{
|
||||
Identified = false;
|
||||
Value = new UserState();
|
||||
EndPoint = endPoint;
|
||||
ConnectedTime = DateTime.UtcNow;
|
||||
_stream = stream ?? throw new ArgumentNullException(nameof(stream));
|
||||
_serverCertificate = serverCertificate ?? throw new ArgumentNullException(nameof(serverCertificate));
|
||||
|
||||
#if DEBUG
|
||||
var certificateUsable = SecureMessageEnvelopeHelper.CanUse(_serverCertificate);
|
||||
var enforceEncryptionFlag = Environment.GetEnvironmentVariable("PULSAR_DEBUG_ENFORCE_ENCRYPTION");
|
||||
var enforceEncryption = !string.IsNullOrWhiteSpace(enforceEncryptionFlag)
|
||||
&& (enforceEncryptionFlag.Equals("1", StringComparison.OrdinalIgnoreCase)
|
||||
|| enforceEncryptionFlag.Equals("true", StringComparison.OrdinalIgnoreCase)
|
||||
|| enforceEncryptionFlag.Equals("yes", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (enforceEncryption && certificateUsable)
|
||||
{
|
||||
_encryptTraffic = true;
|
||||
Debug.WriteLine("[SERVER] Debug build: encryption enforced via PULSAR_DEBUG_ENFORCE_ENCRYPTION.");
|
||||
}
|
||||
else
|
||||
{
|
||||
_encryptTraffic = false;
|
||||
if (!certificateUsable)
|
||||
{
|
||||
Debug.WriteLine("[SERVER] Debug build: server certificate unavailable, running without encryption.");
|
||||
}
|
||||
else
|
||||
{
|
||||
var logMessage = enforceEncryption
|
||||
? "[SERVER] Debug build: encryption enforcement requested but certificate cannot be used; continuing without encryption."
|
||||
: "[SERVER] Debug build: encryption disabled by default for development.";
|
||||
Debug.WriteLine(logMessage);
|
||||
}
|
||||
}
|
||||
#else
|
||||
_encryptTraffic = SecureMessageEnvelopeHelper.CanUse(_serverCertificate);
|
||||
if (!_encryptTraffic)
|
||||
{
|
||||
throw new InvalidOperationException("A valid server certificate is required for secure communication.");
|
||||
}
|
||||
#endif
|
||||
|
||||
_readBuffer = new byte[HEADER_SIZE];
|
||||
_readOffset = 0;
|
||||
_readLength = HEADER_SIZE;
|
||||
_stream.BeginRead(_readBuffer, _readOffset, _readBuffer.Length, AsyncReceive, null);
|
||||
OnClientState(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Disconnect();
|
||||
OnClientFail(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void AsyncReceive(IAsyncResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_stream == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var bytesRead = _stream.EndRead(result);
|
||||
if (bytesRead <= 0)
|
||||
throw new Exception("no bytes transferred");
|
||||
|
||||
lock (_readMessageLock)
|
||||
{
|
||||
_readOffset += bytesRead;
|
||||
_readLength -= bytesRead;
|
||||
|
||||
if (_readLength == 0)
|
||||
{
|
||||
if (_readBuffer.Length == HEADER_SIZE)
|
||||
{
|
||||
var length = BitConverter.ToInt32(_readBuffer, 0);
|
||||
if (length <= 0)
|
||||
throw new InvalidDataException("Invalid message length.");
|
||||
|
||||
_readBuffer = new byte[length];
|
||||
_readOffset = 0;
|
||||
_readLength = length;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
var message = PulsarMessagePackSerializer.Deserialize(_readBuffer);
|
||||
message = ProcessIncomingMessage(message);
|
||||
OnClientRead(message, _readBuffer.Length);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_readBuffer = new byte[HEADER_SIZE];
|
||||
_readOffset = 0;
|
||||
_readLength = HEADER_SIZE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (_stream != null)
|
||||
{
|
||||
ThreadPool.QueueUserWorkItem(_ =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_stream != null)
|
||||
{
|
||||
_stream.BeginRead(_readBuffer, _readOffset, _readLength, AsyncReceive, result.AsyncState);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Disconnect();
|
||||
OnClientFail(ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Disconnect();
|
||||
OnClientFail(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a message to the connected client.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the message.</typeparam>
|
||||
/// <param name="message">The message to be sent.</param>
|
||||
public void Send<T>(T message) where T : IMessage
|
||||
{
|
||||
if (!Connected || message == null) return;
|
||||
|
||||
_sendBuffers.Enqueue(message);
|
||||
if (Interlocked.Exchange(ref _sendingMessagesFlag, 1) == 0)
|
||||
{
|
||||
ThreadPool.QueueUserWorkItem(ProcessSendBuffers);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a message to the connected client.
|
||||
/// Blocks the thread until the message has been sent.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the message.</typeparam>
|
||||
/// <param name="message">The message to be sent.</param>
|
||||
public void SendBlocking<T>(T message) where T : IMessage
|
||||
{
|
||||
if (!Connected || message == null) return;
|
||||
|
||||
SafeSendMessage(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Safely sends a message and prevents multiple simultaneous
|
||||
/// write operations on the <see cref="_stream"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to send.</param>
|
||||
private void SafeSendMessage(IMessage message)
|
||||
{
|
||||
try
|
||||
{
|
||||
lock (_sendMessageLock)
|
||||
{
|
||||
if (_stream == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var prepared = PrepareMessageForSend(message);
|
||||
if (prepared == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var payload = PulsarMessagePackSerializer.Serialize(prepared);
|
||||
int totalLength = HEADER_SIZE + payload.Length;
|
||||
var buffer = System.Buffers.ArrayPool<byte>.Shared.Rent(totalLength);
|
||||
try
|
||||
{
|
||||
System.Buffers.Binary.BinaryPrimitives.WriteInt32LittleEndian(new Span<byte>(buffer, 0, HEADER_SIZE), payload.Length);
|
||||
Buffer.BlockCopy(payload, 0, buffer, HEADER_SIZE, payload.Length);
|
||||
_stream.Write(buffer, 0, totalLength);
|
||||
}
|
||||
finally
|
||||
{
|
||||
System.Buffers.ArrayPool<byte>.Shared.Return(buffer, clearArray: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessSendBuffers(object state)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (!Connected)
|
||||
{
|
||||
SendCleanup(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_sendBuffers.TryDequeue(out var message))
|
||||
{
|
||||
SendCleanup();
|
||||
return;
|
||||
}
|
||||
|
||||
SafeSendMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
private void SendCleanup(bool clear = false)
|
||||
{
|
||||
Interlocked.Exchange(ref _sendingMessagesFlag, 0);
|
||||
if (clear)
|
||||
{
|
||||
while (_sendBuffers.TryDequeue(out _)) ;
|
||||
}
|
||||
}
|
||||
|
||||
private IMessage PrepareMessageForSend(IMessage message)
|
||||
{
|
||||
if (message == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (message is SecureMessageEnvelope)
|
||||
{
|
||||
return message;
|
||||
}
|
||||
|
||||
if (!_encryptTraffic)
|
||||
{
|
||||
return message;
|
||||
}
|
||||
|
||||
if (!SecureMessageEnvelopeHelper.CanUse(_serverCertificate))
|
||||
{
|
||||
throw new InvalidOperationException("Secure transport is enabled but the server certificate is unavailable.");
|
||||
}
|
||||
|
||||
return SecureMessageEnvelopeHelper.Wrap(message, _serverCertificate);
|
||||
}
|
||||
|
||||
private IMessage ProcessIncomingMessage(IMessage message)
|
||||
{
|
||||
if (message is SecureMessageEnvelope secureEnvelope)
|
||||
{
|
||||
if (!SecureMessageEnvelopeHelper.CanUse(_serverCertificate))
|
||||
{
|
||||
throw new InvalidOperationException("Received a secure envelope but the server certificate is unavailable for decryption.");
|
||||
}
|
||||
|
||||
return SecureMessageEnvelopeHelper.Unwrap(secureEnvelope, _serverCertificate);
|
||||
}
|
||||
|
||||
if (_encryptTraffic)
|
||||
{
|
||||
throw new InvalidOperationException($"Received unexpected plaintext message of type {message?.GetType().Name} while encryption is enforced.");
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnect the client from the server and dispose of
|
||||
/// resources associated with the client.
|
||||
/// </summary>
|
||||
public void Disconnect()
|
||||
{
|
||||
if (_stream != null)
|
||||
{
|
||||
_stream.Dispose();
|
||||
_stream = null;
|
||||
}
|
||||
|
||||
_readBuffer = new byte[HEADER_SIZE];
|
||||
_readOffset = 0;
|
||||
_readLength = HEADER_SIZE;
|
||||
|
||||
SendCleanup(true);
|
||||
OnClientState(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
using Pulsar.Common.Messages;
|
||||
using Pulsar.Common.Messages.Other;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using Pulsar.Common.Networking;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Pulsar.Server.TelegramSender;
|
||||
using System.Security.Cryptography;
|
||||
using Pulsar.Common.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Pulsar.Server.Networking
|
||||
{
|
||||
public class PulsarServer : Server
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the clients currently connected and identified to the server.
|
||||
/// </summary>
|
||||
public Client[] ConnectedClients
|
||||
{
|
||||
get { return Clients.Where(c => c != null && c.Identified).ToArray(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a client connected.
|
||||
/// </summary>
|
||||
public event ClientConnectedEventHandler ClientConnected;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the method that will handle the connected client.
|
||||
/// </summary>
|
||||
/// <param name="client">The connected client.</param>
|
||||
public delegate void ClientConnectedEventHandler(Client client);
|
||||
|
||||
/// <summary>
|
||||
/// Fires an event that informs subscribers that the client is connected.
|
||||
/// </summary>
|
||||
/// <param name="client">The connected client.</param>
|
||||
private void OnClientConnected(Client client)
|
||||
{
|
||||
if (ProcessingDisconnect || !Listening) return;
|
||||
if (Models.Settings.TelegramNotifications)
|
||||
{
|
||||
Task.Run(() => Send.SendConnectionMessage(
|
||||
Models.Settings.TelegramBotToken,
|
||||
Models.Settings.TelegramChatID,
|
||||
client.Value.Username,
|
||||
client.Value.PublicIP ?? "Unknown",
|
||||
client.Value.Country
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
var handler = ClientConnected;
|
||||
handler?.Invoke(client);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a client disconnected.
|
||||
/// </summary>
|
||||
public event ClientDisconnectedEventHandler ClientDisconnected;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the method that will handle the disconnected client.
|
||||
/// </summary>
|
||||
/// <param name="client">The disconnected client.</param>
|
||||
public delegate void ClientDisconnectedEventHandler(Client client);
|
||||
|
||||
/// <summary>
|
||||
/// Fires an event that informs subscribers that the client is disconnected.
|
||||
/// </summary>
|
||||
/// <param name="client">The disconnected client.</param>
|
||||
private void OnClientDisconnected(Client client)
|
||||
{
|
||||
if (ProcessingDisconnect || !Listening) return;
|
||||
var handler = ClientDisconnected;
|
||||
handler?.Invoke(client);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor, initializes required objects and subscribes to events of the server.
|
||||
/// </summary>
|
||||
/// <param name="serverCertificate">The server certificate.</param>
|
||||
public PulsarServer(X509Certificate2 serverCertificate) : base(serverCertificate)
|
||||
{
|
||||
base.ClientState += OnClientState;
|
||||
base.ClientRead += OnClientRead;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decides if the client connected or disconnected.
|
||||
/// </summary>
|
||||
/// <param name="server">The server the client is connected to.</param>
|
||||
/// <param name="client">The client which changed its state.</param>
|
||||
/// <param name="connected">True if the client connected, false if disconnected.</param>
|
||||
private void OnClientState(Server server, Client client, bool connected)
|
||||
{
|
||||
if (!connected)
|
||||
{
|
||||
if (client.Identified)
|
||||
{
|
||||
OnClientDisconnected(client);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forwards received messages from the client to the MessageHandler.
|
||||
/// </summary>
|
||||
/// <param name="server">The server the client is connected to.</param>
|
||||
/// <param name="client">The client which has received the message.</param>
|
||||
/// <param name="message">The received message.</param>
|
||||
private void OnClientRead(Server server, Client client, IMessage message)
|
||||
{
|
||||
if (!client.Identified)
|
||||
{
|
||||
if (message.GetType() == typeof(ClientIdentification))
|
||||
{
|
||||
client.Identified = IdentifyClient(client, (ClientIdentification)message);
|
||||
if (client.Identified)
|
||||
{
|
||||
var response = new ClientIdentificationResult { Result = true };
|
||||
client.Send(response); // finish handshake
|
||||
OnClientConnected(client);
|
||||
}
|
||||
else
|
||||
{
|
||||
// identification failed
|
||||
client.Disconnect();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// no messages of other types are allowed as long as client is in unidentified state
|
||||
client.Disconnect();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsHighPriorityMessage(message))
|
||||
{
|
||||
MessageHandler.Process(client, message);
|
||||
}
|
||||
else
|
||||
{
|
||||
ThreadPool.QueueUserWorkItem(_ =>
|
||||
{
|
||||
try
|
||||
{
|
||||
MessageHandler.Process(client, message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Message processing error: {ex.Message}");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsHighPriorityMessage(IMessage message)
|
||||
{
|
||||
return message is ClientIdentificationResult;
|
||||
}
|
||||
|
||||
private bool IdentifyClient(Client client, ClientIdentification packet)
|
||||
{
|
||||
if (packet.Id.Length != 64)
|
||||
return false;
|
||||
|
||||
client.Value.Version = packet.Version;
|
||||
client.Value.OperatingSystem = packet.OperatingSystem;
|
||||
client.Value.AccountType = packet.AccountType;
|
||||
client.Value.Country = packet.Country;
|
||||
client.Value.CountryCode = packet.CountryCode;
|
||||
client.Value.Id = packet.Id;
|
||||
client.Value.Username = packet.Username;
|
||||
client.Value.PcName = packet.PcName;
|
||||
client.Value.Tag = packet.Tag;
|
||||
client.Value.ImageIndex = packet.ImageIndex;
|
||||
client.Value.EncryptionKey = packet.EncryptionKey;
|
||||
client.Value.PublicIP = packet.PublicIP;
|
||||
|
||||
// TODO: Refactor tooltip
|
||||
//if (Settings.ShowToolTip)
|
||||
// client.Send(new GetSystemInfo());
|
||||
|
||||
#if !DEBUG
|
||||
try
|
||||
{
|
||||
using (var rsa = ServerCertificate.GetRSAPublicKey())
|
||||
{
|
||||
var hash = Sha256.ComputeHash(Encoding.UTF8.GetBytes(packet.EncryptionKey));
|
||||
return rsa.VerifyHash(hash, packet.Signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using Pulsar.Common.Messages;
|
||||
|
||||
namespace Pulsar.Server.Networking
|
||||
{
|
||||
public static class PushSender
|
||||
{
|
||||
public static void LoadUniversalPlugin(Client client, string pluginId, byte[] pluginBytes, byte[] initData, string typeName, string methodName)
|
||||
{
|
||||
if (client == null || pluginBytes == null || pluginBytes.Length == 0) return;
|
||||
|
||||
client.Send(new DoLoadUniversalPlugin
|
||||
{
|
||||
PluginId = pluginId,
|
||||
PluginBytes = pluginBytes,
|
||||
InitData = initData,
|
||||
TypeName = typeName,
|
||||
MethodName = methodName
|
||||
});
|
||||
}
|
||||
|
||||
public static void ExecuteUniversalCommand(Client client, string pluginId, string command, byte[] parameters)
|
||||
{
|
||||
if (client == null) return;
|
||||
|
||||
client.Send(new DoExecuteUniversalCommand
|
||||
{
|
||||
PluginId = pluginId,
|
||||
Command = command,
|
||||
Parameters = parameters
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,546 @@
|
||||
using Pulsar.Common.Extensions;
|
||||
using Pulsar.Common.Messages;
|
||||
using Pulsar.Common.Messages.Other;
|
||||
using Pulsar.Common.Networking;
|
||||
using Pulsar.Server.Forms;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace Pulsar.Server.Networking
|
||||
{
|
||||
public class Server
|
||||
{
|
||||
/// <summary>
|
||||
/// Occurs when the state of the server changes.
|
||||
/// </summary>
|
||||
public event ServerStateEventHandler ServerState;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a method that will handle a change in the server's state.
|
||||
/// </summary>
|
||||
/// <param name="s">The server which changed its state.</param>
|
||||
/// <param name="listening">The new listening state of the server.</param>
|
||||
/// <param name="port">The port the server is listening on, if listening is True.</param>
|
||||
public delegate void ServerStateEventHandler(Server s, bool listening, ushort port);
|
||||
|
||||
/// <summary>
|
||||
/// Fires an event that informs subscribers that the server has changed it's state.
|
||||
/// </summary>
|
||||
/// <param name="listening">The new listening state of the server.</param>
|
||||
private void OnServerState(bool listening)
|
||||
{
|
||||
if (Listening == listening) return;
|
||||
|
||||
Listening = listening;
|
||||
|
||||
var handler = ServerState;
|
||||
handler?.Invoke(this, listening, Port);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the state of a client changes.
|
||||
/// </summary>
|
||||
public event ClientStateEventHandler ClientState;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a method that will handle a change in a client's state.
|
||||
/// </summary>
|
||||
/// <param name="s">The server, the client is connected to.</param>
|
||||
/// <param name="c">The client which changed its state.</param>
|
||||
/// <param name="connected">The new connection state of the client.</param>
|
||||
public delegate void ClientStateEventHandler(Server s, Client c, bool connected);
|
||||
|
||||
/// <summary>
|
||||
/// Fires an event that informs subscribers that a client has changed its state.
|
||||
/// </summary>
|
||||
/// <param name="c">The client which changed its state.</param>
|
||||
/// <param name="connected">The new connection state of the client.</param>
|
||||
private void OnClientState(Client c, bool connected)
|
||||
{
|
||||
if (!connected)
|
||||
RemoveClient(c);
|
||||
|
||||
var handler = ClientState;
|
||||
handler?.Invoke(this, c, connected);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a message is received by a client.
|
||||
/// </summary>
|
||||
public event ClientReadEventHandler ClientRead;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a method that will handle a message received from a client.
|
||||
/// </summary>
|
||||
/// <param name="s">The server, the client is connected to.</param>
|
||||
/// <param name="c">The client that has received the message.</param>
|
||||
/// <param name="message">The message that received by the client.</param>
|
||||
public delegate void ClientReadEventHandler(Server s, Client c, IMessage message);
|
||||
|
||||
/// <summary>
|
||||
/// Fires an event that informs subscribers that a message has been
|
||||
/// received from the client.
|
||||
/// </summary>
|
||||
/// <param name="c">The client that has received the message.</param>
|
||||
/// <param name="message">The message that received by the client.</param>
|
||||
/// <param name="messageLength">The length of the message.</param>
|
||||
private void OnClientRead(Client c, IMessage message, int messageLength)
|
||||
{
|
||||
BytesReceived += messageLength;
|
||||
var handler = ClientRead;
|
||||
handler?.Invoke(this, c, message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a message is sent by a client.
|
||||
/// </summary>
|
||||
public event ClientWriteEventHandler ClientWrite;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the method that will handle the sent message by a client.
|
||||
/// </summary>
|
||||
/// <param name="s">The server, the client is connected to.</param>
|
||||
/// <param name="c">The client that has sent the message.</param>
|
||||
/// <param name="message">The message that has been sent by the client.</param>
|
||||
public delegate void ClientWriteEventHandler(Server s, Client c, IMessage message);
|
||||
|
||||
/// <summary>
|
||||
/// Fires an event that informs subscribers that the client has sent a message.
|
||||
/// </summary>
|
||||
/// <param name="c">The client that has sent the message.</param>
|
||||
/// <param name="message">The message that has been sent by the client.</param>
|
||||
/// <param name="messageLength">The length of the message.</param>
|
||||
private void OnClientWrite(Client c, IMessage message, int messageLength)
|
||||
{
|
||||
BytesSent += messageLength;
|
||||
var handler = ClientWrite;
|
||||
handler?.Invoke(this, c, message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The port on which the server is listening.
|
||||
/// For multi-port scenarios, this is the last port that was started.
|
||||
/// </summary>
|
||||
public ushort Port { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The total amount of received bytes.
|
||||
/// </summary>
|
||||
public long BytesReceived { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The total amount of sent bytes.
|
||||
/// </summary>
|
||||
public long BytesSent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The keep-alive time in ms.
|
||||
/// </summary>
|
||||
private const uint KeepAliveTime = 25000; // 25 s
|
||||
|
||||
/// <summary>
|
||||
/// The keep-alive interval in ms.
|
||||
/// </summary>
|
||||
private const uint KeepAliveInterval = 25000; // 25 s
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The listening state of the server. True if listening, else False.
|
||||
/// </summary>
|
||||
public bool Listening { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the clients currently connected to the server.
|
||||
/// </summary>
|
||||
protected Client[] Clients
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_clientsLock)
|
||||
{
|
||||
return _clients.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of clients currently connected to the server without array allocation.
|
||||
/// </summary>
|
||||
public int ClientCount
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_clientsLock)
|
||||
{
|
||||
return _clients.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle(s) of the Server Socket(s).
|
||||
/// </summary>
|
||||
private readonly List<Socket> _handles = new List<Socket>();
|
||||
private readonly object _handlesLock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Accept event args, one per listening socket.
|
||||
/// </summary>
|
||||
private readonly Dictionary<Socket, SocketAsyncEventArgs> _acceptArgs = new Dictionary<Socket, SocketAsyncEventArgs>();
|
||||
|
||||
/// <summary>
|
||||
/// The server certificate.
|
||||
/// </summary>
|
||||
protected readonly X509Certificate2 ServerCertificate;
|
||||
|
||||
/// <summary>
|
||||
/// List of the clients connected to the server.
|
||||
/// </summary>
|
||||
private readonly List<Client> _clients = new List<Client>();
|
||||
|
||||
/// <summary>
|
||||
/// The UPnP service used to create port mappings per port.
|
||||
/// </summary>
|
||||
private readonly Dictionary<ushort, UPnPService> _upnpByPort = new Dictionary<ushort, UPnPService>();
|
||||
|
||||
/// <summary>
|
||||
/// Lock object for the list of clients.
|
||||
/// </summary>
|
||||
private readonly object _clientsLock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the server is currently processing Disconnect method.
|
||||
/// </summary>
|
||||
protected bool ProcessingDisconnect { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor of the server.
|
||||
/// </summary>
|
||||
/// <param name="serverCertificate">The server certificate.</param>
|
||||
protected Server(X509Certificate2 serverCertificate)
|
||||
{
|
||||
ServerCertificate = serverCertificate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the status strip icon for the server listening state.
|
||||
/// </summary>
|
||||
/// <param name="isListening">True if server is listening, false otherwise.</param>
|
||||
private void UpdateServerStatusIcon(bool isListening)
|
||||
{
|
||||
var mainForm = GetMainFormSafe();
|
||||
if (mainForm == null) return;
|
||||
|
||||
var iconResource = isListening
|
||||
? Properties.Resources.bullet_green
|
||||
: Properties.Resources.bullet_red;
|
||||
|
||||
try
|
||||
{
|
||||
if (mainForm.InvokeRequired)
|
||||
{
|
||||
mainForm.BeginInvoke(new Action(() => SetStatusStripIcon(mainForm, iconResource)));
|
||||
}
|
||||
else
|
||||
{
|
||||
SetStatusStripIcon(mainForm, iconResource);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Safely gets the main form instance if it exists and is valid.
|
||||
/// </summary>
|
||||
/// <returns>The main form instance or null if not available.</returns>
|
||||
private static FrmMain GetMainFormSafe()
|
||||
{
|
||||
var mainForm = Application.OpenForms.OfType<FrmMain>().FirstOrDefault();
|
||||
return (mainForm != null && !mainForm.IsDisposed && !mainForm.Disposing) ? mainForm : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the status strip icon if the control is valid.
|
||||
/// </summary>
|
||||
/// <param name="mainForm">The main form instance.</param>
|
||||
/// <param name="icon">The icon to set.</param>
|
||||
private static void SetStatusStripIcon(FrmMain mainForm, System.Drawing.Image icon)
|
||||
{
|
||||
if (mainForm.statusStrip?.IsDisposed == false &&
|
||||
mainForm.statusStrip.Items.ContainsKey("listenToolStripStatusLabel"))
|
||||
{
|
||||
mainForm.statusStrip.Items["listenToolStripStatusLabel"].Image = icon;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Begins listening for clients on a single port.
|
||||
/// </summary>
|
||||
public void Listen(ushort port, bool ipv6, bool enableUPnP)
|
||||
{
|
||||
ListenMany(new[] { port }, ipv6, enableUPnP);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Begins listening for clients on multiple ports.
|
||||
/// </summary>
|
||||
/// <param name="ports">Ports to listen on.</param>
|
||||
/// <param name="ipv6">If set to true, use a dual-stack socket to allow IPv4/6 connections. Otherwise use IPv4-only socket.</param>
|
||||
/// <param name="enableUPnP">Enables the automatic UPnP port forwarding for each port.</param>
|
||||
public void ListenMany(IEnumerable<ushort> ports, bool ipv6, bool enableUPnP)
|
||||
{
|
||||
var startNow = !Listening;
|
||||
|
||||
foreach (var port in ports.Distinct())
|
||||
{
|
||||
lock (_handlesLock)
|
||||
{
|
||||
if (_handles.Any(h => (h.LocalEndPoint as IPEndPoint)?.Port == port))
|
||||
continue;
|
||||
}
|
||||
Socket handle;
|
||||
if (Socket.OSSupportsIPv6 && ipv6)
|
||||
{
|
||||
handle = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp);
|
||||
handle.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, 0);
|
||||
handle.Bind(new IPEndPoint(IPAddress.IPv6Any, port));
|
||||
}
|
||||
else
|
||||
{
|
||||
handle = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
||||
handle.Bind(new IPEndPoint(IPAddress.Any, port));
|
||||
}
|
||||
|
||||
handle.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
|
||||
handle.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);
|
||||
handle.Listen(1000);
|
||||
lock (_handlesLock)
|
||||
{
|
||||
_handles.Add(handle);
|
||||
}
|
||||
|
||||
if (enableUPnP)
|
||||
{
|
||||
var upnp = new UPnPService();
|
||||
_upnpByPort[port] = upnp;
|
||||
upnp.CreatePortMapAsync(port);
|
||||
}
|
||||
|
||||
var item = new SocketAsyncEventArgs();
|
||||
item.Completed += AcceptClient;
|
||||
_acceptArgs[handle] = item;
|
||||
|
||||
Port = port; // keep last started port for compatibility
|
||||
|
||||
if (!handle.AcceptAsync(item))
|
||||
AcceptClient(handle, item);
|
||||
|
||||
var mainForm = GetMainFormSafe();
|
||||
if (mainForm != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (mainForm.InvokeRequired)
|
||||
{
|
||||
mainForm.BeginInvoke(new Action(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
mainForm.EventLog($"Started listening for connections on port: {port}", "info");
|
||||
UpdateServerStatusIcon(true);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}));
|
||||
}
|
||||
else
|
||||
{
|
||||
mainForm.EventLog($"Started listening for connections on port: {port}", "info");
|
||||
UpdateServerStatusIcon(true);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (startNow && _handles.Count > 0)
|
||||
{
|
||||
OnServerState(true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Accepts and begins authenticating an incoming client.
|
||||
/// </summary>
|
||||
/// <param name="s">The listening socket.</param>
|
||||
/// <param name="e">Asynchronous socket event.</param>
|
||||
private void AcceptClient(object s, SocketAsyncEventArgs e)
|
||||
{
|
||||
var listenSocket = s as Socket;
|
||||
if (listenSocket == null)
|
||||
{
|
||||
// Try to recover the listen socket from our map
|
||||
listenSocket = _acceptArgs.Keys.FirstOrDefault();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
do
|
||||
{
|
||||
switch (e.SocketError)
|
||||
{
|
||||
case SocketError.Success:
|
||||
try
|
||||
{
|
||||
Socket clientSocket = e.AcceptSocket;
|
||||
clientSocket.SetKeepAliveEx(KeepAliveInterval, KeepAliveTime);
|
||||
clientSocket.NoDelay = true;
|
||||
|
||||
var networkStream = new NetworkStream(clientSocket, true);
|
||||
var client = new Client(networkStream, (IPEndPoint)clientSocket.RemoteEndPoint, ServerCertificate);
|
||||
AddClient(client);
|
||||
OnClientState(client, true);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
try
|
||||
{
|
||||
e.AcceptSocket?.Close();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SocketError.ConnectionReset:
|
||||
break;
|
||||
default:
|
||||
throw new SocketException((int)e.SocketError);
|
||||
}
|
||||
|
||||
e.AcceptSocket = null; // enable reuse
|
||||
} while (listenSocket != null && !listenSocket.AcceptAsync(e));
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a connected client to the list of clients,
|
||||
/// subscribes to the client's events.
|
||||
/// </summary>
|
||||
/// <param name="client">The client to add.</param>
|
||||
private void AddClient(Client client)
|
||||
{
|
||||
lock (_clientsLock)
|
||||
{
|
||||
client.ClientState += OnClientState;
|
||||
client.ClientRead += OnClientRead;
|
||||
|
||||
_clients.Add(client);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a disconnected client from the list of clients,
|
||||
/// unsubscribes from the client's events.
|
||||
/// </summary>
|
||||
/// <param name="client">The client to remove.</param>
|
||||
private void RemoveClient(Client client)
|
||||
{
|
||||
if (ProcessingDisconnect) return;
|
||||
|
||||
lock (_clientsLock)
|
||||
{
|
||||
client.ClientState -= OnClientState;
|
||||
client.ClientRead -= OnClientRead;
|
||||
|
||||
_clients.Remove(client);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnect the server from all of the clients and discontinue
|
||||
/// listening (placing the server in an "off" state).
|
||||
/// </summary>
|
||||
public void Disconnect()
|
||||
{
|
||||
if (ProcessingDisconnect) return;
|
||||
ProcessingDisconnect = true;
|
||||
|
||||
List<Socket> toClose;
|
||||
lock (_handlesLock)
|
||||
{
|
||||
toClose = _handles.ToList();
|
||||
_handles.Clear();
|
||||
}
|
||||
foreach (var handle in toClose)
|
||||
{
|
||||
try { handle.Close(); } catch { }
|
||||
}
|
||||
|
||||
foreach (var kvp in _acceptArgs.ToList())
|
||||
{
|
||||
try { kvp.Value.Dispose(); } catch { }
|
||||
}
|
||||
_acceptArgs.Clear();
|
||||
|
||||
foreach (var upnpKvp in _upnpByPort.ToList())
|
||||
{
|
||||
try { upnpKvp.Value.DeletePortMapAsync(upnpKvp.Key); } catch { }
|
||||
}
|
||||
_upnpByPort.Clear();
|
||||
|
||||
lock (_clientsLock)
|
||||
{
|
||||
var clientsToDisconnect = _clients.ToList();
|
||||
_clients.Clear();
|
||||
|
||||
foreach (var client in clientsToDisconnect)
|
||||
{
|
||||
try
|
||||
{
|
||||
client.Disconnect();
|
||||
client.ClientState -= OnClientState;
|
||||
client.ClientRead -= OnClientRead;
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ProcessingDisconnect = false;
|
||||
OnServerState(false);
|
||||
UpdateServerStatusIcon(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ports the server is currently listening on.
|
||||
/// </summary>
|
||||
public ushort[] GetListeningPorts()
|
||||
{
|
||||
lock (_handlesLock)
|
||||
{
|
||||
return _handles.Select(h => (ushort)((IPEndPoint)h.LocalEndPoint).Port).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
using Open.Nat;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace Pulsar.Server.Networking
|
||||
{
|
||||
public class UPnPService
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to keep track of all created mappings.
|
||||
/// </summary>
|
||||
private readonly Dictionary<int, Mapping> _mappings = new Dictionary<int, Mapping>();
|
||||
|
||||
/// <summary>
|
||||
/// The discovered UPnP device.
|
||||
/// </summary>
|
||||
private NatDevice _device;
|
||||
|
||||
/// <summary>
|
||||
/// The NAT discoverer used to discover NAT-UPnP devices.
|
||||
/// </summary>
|
||||
private NatDiscoverer _discoverer;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the discovery of new UPnP devices.
|
||||
/// </summary>
|
||||
public UPnPService()
|
||||
{
|
||||
_discoverer = new NatDiscoverer();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new port mapping on the UPnP device.
|
||||
/// </summary>
|
||||
/// <param name="port">The port to map.</param>
|
||||
public async void CreatePortMapAsync(int port)
|
||||
{
|
||||
try
|
||||
{
|
||||
var cts = new CancellationTokenSource(10000);
|
||||
_device = await _discoverer.DiscoverDeviceAsync(PortMapper.Upnp, cts);
|
||||
|
||||
Mapping mapping = new Mapping(Protocol.Tcp, port, port);
|
||||
|
||||
await _device.CreatePortMapAsync(mapping);
|
||||
|
||||
if (_mappings.ContainsKey(mapping.PrivatePort))
|
||||
_mappings[mapping.PrivatePort] = mapping;
|
||||
else
|
||||
_mappings.Add(mapping.PrivatePort, mapping);
|
||||
}
|
||||
catch (Exception ex) when (ex is MappingException || ex is NatDeviceNotFoundException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes an existing port mapping.
|
||||
/// </summary>
|
||||
/// <param name="port">The port mapping to delete.</param>
|
||||
public async void DeletePortMapAsync(int port)
|
||||
{
|
||||
if (_mappings.TryGetValue(port, out var mapping))
|
||||
{
|
||||
try
|
||||
{
|
||||
await _device.DeletePortMapAsync(mapping);
|
||||
_mappings.Remove(mapping.PrivatePort);
|
||||
}
|
||||
catch (Exception ex) when (ex is MappingException || ex is NatDeviceNotFoundException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using Pulsar.Common.Cryptography;
|
||||
using Pulsar.Common.Helpers;
|
||||
using System.IO;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace Pulsar.Server.Networking
|
||||
{
|
||||
public class UserState
|
||||
{
|
||||
private string _downloadDirectory;
|
||||
private Aes256 _aesInstance;
|
||||
|
||||
public string Version { get; set; }
|
||||
public string OperatingSystem { get; set; }
|
||||
public string AccountType { get; set; }
|
||||
public int ImageIndex { get; set; }
|
||||
public string Country { get; set; }
|
||||
public string CountryCode { get; set; }
|
||||
public string Id { get; set; }
|
||||
public string Username { get; set; }
|
||||
public string PublicIP { get; set; }
|
||||
public string PcName { get; set; }
|
||||
public string UserAtPc => $"{Username}@{PcName}";
|
||||
public string CountryWithCode => $"{Country} [{CountryCode}]";
|
||||
public string Tag { get; set; }
|
||||
public string EncryptionKey { get; set; }
|
||||
|
||||
public Aes256 AesInstance => _aesInstance ?? (_aesInstance = new Aes256(EncryptionKey));
|
||||
|
||||
public string DownloadDirectory => _downloadDirectory ?? (_downloadDirectory = (!FileHelper.HasIllegalCharacters(UserAtPc))
|
||||
? Path.Combine(Application.StartupPath, $"Clients\\{UserAtPc}_{Id.Substring(0, 7)}\\")
|
||||
: Path.Combine(Application.StartupPath, $"Clients\\{Id}\\"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user