mirror of
https://github.com/RedWizardsLab/EchoHub.git
synced 2026-09-04 23:34:10 +02:00
- Implemented IrcCommandHandlerTests to cover various IRC commands including PING, JOIN, PART, and authentication scenarios. - Added IrcMessageFormatterTests to validate message formatting for different message types such as text, images, files, and audio. - Created IrcMessageTests to ensure correct parsing of IRC messages and handling of various command formats. - Introduced TestHelpers to facilitate testing with mock connections and streams, including a FakeChatService and FakeEncryptionService for simulating chat behavior.
94 lines
2.9 KiB
C#
94 lines
2.9 KiB
C#
using System.Net.Sockets;
|
|
using System.Text;
|
|
|
|
namespace EchoHub.Server.Irc;
|
|
|
|
/// <summary>
|
|
/// Manages a single IRC client TCP connection.
|
|
/// </summary>
|
|
public sealed class IrcClientConnection : IAsyncDisposable
|
|
{
|
|
private readonly TcpClient _tcpClient;
|
|
private readonly StreamReader _reader;
|
|
private readonly StreamWriter _writer;
|
|
private readonly SemaphoreSlim _writeLock = new(1, 1);
|
|
|
|
// Connection identity
|
|
public string ConnectionId { get; } = $"irc-{Guid.NewGuid()}";
|
|
|
|
// Registration state
|
|
public string? Nickname { get; set; }
|
|
public string? Username { get; set; }
|
|
public string? RealName { get; set; }
|
|
public string? Password { get; set; }
|
|
public Guid? UserId { get; set; }
|
|
public bool IsRegistered { get; set; }
|
|
public bool IsAuthenticated { get; set; }
|
|
public bool IsSasl { get; set; }
|
|
public bool CapNegotiating { get; set; }
|
|
|
|
// Channel state — thread-safe: written by command handler, read by broadcaster threads
|
|
private readonly HashSet<string> _joinedChannels = new(StringComparer.OrdinalIgnoreCase);
|
|
private readonly object _channelLock = new();
|
|
|
|
// Away state
|
|
public string? AwayMessage { get; set; }
|
|
|
|
public string Hostmask => $"{Nickname}!{Username ?? Nickname}@echohub";
|
|
|
|
public void JoinChannel(string channel) { lock (_channelLock) _joinedChannels.Add(channel); }
|
|
public void LeaveChannel(string channel) { lock (_channelLock) _joinedChannels.Remove(channel); }
|
|
public bool IsInChannel(string channel) { lock (_channelLock) return _joinedChannels.Contains(channel); }
|
|
public List<string> GetJoinedChannels() { lock (_channelLock) return [.. _joinedChannels]; }
|
|
|
|
public IrcClientConnection(TcpClient tcpClient, Stream stream)
|
|
{
|
|
_tcpClient = tcpClient;
|
|
_reader = new StreamReader(stream, Encoding.UTF8);
|
|
_writer = new StreamWriter(stream, Encoding.UTF8) { AutoFlush = true, NewLine = "\r\n" };
|
|
}
|
|
|
|
public async Task<string?> ReadLineAsync(CancellationToken ct)
|
|
{
|
|
try
|
|
{
|
|
return await _reader.ReadLineAsync(ct);
|
|
}
|
|
catch
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public async Task SendAsync(string line)
|
|
{
|
|
await _writeLock.WaitAsync();
|
|
try
|
|
{
|
|
await _writer.WriteLineAsync(line);
|
|
}
|
|
catch
|
|
{
|
|
// Connection lost — swallow
|
|
}
|
|
finally
|
|
{
|
|
_writeLock.Release();
|
|
}
|
|
}
|
|
|
|
public Task SendNumericAsync(string serverName, string numeric, string target, string text)
|
|
=> SendAsync($":{serverName} {numeric} {target} {text}");
|
|
|
|
public Task SendNumericAsync(string serverName, string numeric, string text)
|
|
=> SendNumericAsync(serverName, numeric, Nickname ?? "*", text);
|
|
|
|
public async ValueTask DisposeAsync()
|
|
{
|
|
try { _tcpClient.Close(); } catch { }
|
|
_reader.Dispose();
|
|
_writer.Dispose();
|
|
_writeLock.Dispose();
|
|
}
|
|
}
|