Create Context before creating a server.

This commit is contained in:
flash 2025-04-28 13:03:38 +00:00
parent e0050a51bd
commit c537df792e
Signed by: flash
GPG key ID: 2C9C2C574D47FE3E
7 changed files with 85 additions and 76 deletions

View file

@ -61,10 +61,11 @@ public class FlashiiClient(ILogger logger, HttpClient httpClient, Config config)
private const string AUTH_BUMP_USERS_ONLINE_URL = "{0}/bump"; private const string AUTH_BUMP_USERS_ONLINE_URL = "{0}/bump";
public async Task AuthBumpUsersOnline(IEnumerable<(IPAddress remoteAddr, string userId)> entries) { public async Task AuthBumpUsersOnline(IEnumerable<(IPAddress remoteAddr, string userId)> entries) {
logger.ZLogInformation($"Bumping online users list...");
if(!entries.Any()) if(!entries.Any())
return; return;
logger.ZLogInformation($"Bumping online users list...");
string now = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(); string now = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
StringBuilder sb = new(); StringBuilder sb = new();
sb.AppendFormat("bump#{0}", now); sb.AppendFormat("bump#{0}", now);

View file

@ -1,5 +1,8 @@
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using SharpChat.Auth;
using SharpChat.Bans;
using SharpChat.Channels; using SharpChat.Channels;
using SharpChat.Configuration;
using SharpChat.Events; using SharpChat.Events;
using SharpChat.Messages; using SharpChat.Messages;
using SharpChat.Snowflake; using SharpChat.Snowflake;
@ -12,11 +15,26 @@ using ZLogger;
namespace SharpChat; namespace SharpChat;
public class Context { public class Context {
public const int DEFAULT_MSG_LENGTH_MAX = 5000;
public const int DEFAULT_MAX_CONNECTIONS = 5;
public const int DEFAULT_FLOOD_KICK_LENGTH = 30;
public const int DEFAULT_FLOOD_KICK_EXEMPT_RANK = 9;
public record ChannelUserAssoc(string UserId, string ChannelName); public record ChannelUserAssoc(string UserId, string ChannelName);
public readonly SemaphoreSlim ContextAccess = new(1, 1); public readonly SemaphoreSlim ContextAccess = new(1, 1);
public ILoggerFactory LoggerFactory { get; } public ILoggerFactory LoggerFactory { get; }
public Config Config { get; }
public MessageStorage Messages { get; }
public AuthClient Auth { get; }
public BansClient Bans { get; }
public CachedValue<int> MaxMessageLength { get; }
public CachedValue<int> MaxConnections { get; }
public CachedValue<int> FloodKickLength { get; }
public CachedValue<int> FloodKickExemptRank { get; }
private readonly ILogger Logger; private readonly ILogger Logger;
public SnowflakeGenerator SnowflakeGenerator { get; } = new(); public SnowflakeGenerator SnowflakeGenerator { get; } = new();
@ -25,16 +43,47 @@ public class Context {
public ChannelsContext Channels { get; } = new(); public ChannelsContext Channels { get; } = new();
public HashSet<Connection> Connections { get; } = []; public HashSet<Connection> Connections { get; } = [];
public HashSet<User> Users { get; } = []; public HashSet<User> Users { get; } = [];
public MessageStorage Messages { get; }
public HashSet<ChannelUserAssoc> ChannelUsers { get; } = []; public HashSet<ChannelUserAssoc> ChannelUsers { get; } = [];
public Dictionary<string, RateLimiter> UserRateLimiters { get; } = []; public Dictionary<string, RateLimiter> UserRateLimiters { get; } = [];
public Dictionary<string, Channel> UserLastChannel { get; } = []; public Dictionary<string, Channel> UserLastChannel { get; } = [];
public Context(ILoggerFactory logFactory, MessageStorage msgs) { public Context(
ILoggerFactory logFactory,
Config config,
Storage storage,
AuthClient authClient,
BansClient bansClient
) {
LoggerFactory = logFactory; LoggerFactory = logFactory;
Logger = logFactory.CreateLogger("ctx"); Logger = logFactory.CreateLogger("ctx");
Messages = msgs; Config = config;
Messages = storage.CreateMessageStorage();
Auth = authClient;
Bans = bansClient;
RandomSnowflake = new(SnowflakeGenerator); RandomSnowflake = new(SnowflakeGenerator);
Logger.ZLogDebug($"Reading cached config values...");
MaxMessageLength = config.ReadCached("msgMaxLength", DEFAULT_MSG_LENGTH_MAX);
MaxConnections = config.ReadCached("connMaxCount", DEFAULT_MAX_CONNECTIONS);
FloodKickLength = config.ReadCached("floodKickLength", DEFAULT_FLOOD_KICK_LENGTH);
FloodKickExemptRank = config.ReadCached("floodKickExemptRank", DEFAULT_FLOOD_KICK_EXEMPT_RANK);
Logger.ZLogDebug($"Creating channel list...");
string[] channelNames = config.ReadValue<string[]>("channels") ?? ["lounge"];
if(channelNames is not null)
foreach(string channelName in channelNames) {
Config channelCfg = config.ScopeTo($"channels:{channelName}");
string name = channelCfg.SafeReadValue("name", string.Empty)!;
if(string.IsNullOrWhiteSpace(name))
name = channelName;
Channels.CreateChannel(
name,
channelCfg.SafeReadValue("password", string.Empty)!,
rank: channelCfg.SafeReadValue("minRank", 0)
);
}
} }
public async Task DispatchEvent(ChatEvent eventInfo) { public async Task DispatchEvent(ChatEvent eventInfo) {

View file

@ -82,11 +82,12 @@ if(!File.Exists(configFile) && configFile == CONFIG) {
sw.WriteLine(); sw.WriteLine();
sw.WriteLine("# General Configuration"); sw.WriteLine("# General Configuration");
sw.WriteLine($"#chat:port {SockChatServer.DEFAULT_PORT}"); sw.WriteLine($"#chat:msgMaxLength {Context.DEFAULT_MSG_LENGTH_MAX}");
sw.WriteLine($"#chat:msgMaxLength {SockChatServer.DEFAULT_MSG_LENGTH_MAX}"); sw.WriteLine($"#chat:connMaxCount {Context.DEFAULT_MAX_CONNECTIONS}");
sw.WriteLine($"#chat:connMaxCount {SockChatServer.DEFAULT_MAX_CONNECTIONS}"); sw.WriteLine($"#chat:floodKickLength {Context.DEFAULT_FLOOD_KICK_LENGTH}");
sw.WriteLine($"#chat:floodKickLength {SockChatServer.DEFAULT_FLOOD_KICK_LENGTH}");
sw.WriteLine("# Sock Chat Configuration");
sw.WriteLine($"#sockchat:port {SockChatServer.DEFAULT_PORT}");
sw.WriteLine(); sw.WriteLine();
sw.WriteLine("# Channels"); sw.WriteLine("# Channels");
@ -173,14 +174,17 @@ try {
if(cts.IsCancellationRequested) return; if(cts.IsCancellationRequested) return;
logger.ZLogDebug($"Creating context...");
Config chatConfig = config.ScopeTo("chat");
Context ctx = new(logFactory, chatConfig, storage, flashii, flashii);
if(cts.IsCancellationRequested) return;
logger.ZLogInformation($"Preparing server..."); logger.ZLogInformation($"Preparing server...");
await new SockChatServer( await new SockChatServer(
logFactory,
cts, cts,
flashii, ctx,
flashii, config.ScopeTo("sockchat")
storage.CreateMessageStorage(),
config.ScopeTo("chat")
).Listen(cts.Token); ).Listen(cts.Token);
} finally { } finally {
if(storage is IDisposable disp) { if(storage is IDisposable disp) {

View file

@ -1,10 +1,8 @@
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using SharpChat.Auth;
using SharpChat.Bans; using SharpChat.Bans;
using SharpChat.C2SPacketHandlers; using SharpChat.C2SPacketHandlers;
using SharpChat.ClientCommands; using SharpChat.ClientCommands;
using SharpChat.Configuration; using SharpChat.Configuration;
using SharpChat.Messages;
using SharpChat.SockChat.S2CPackets; using SharpChat.SockChat.S2CPackets;
using System.Net; using System.Net;
using ZLogger; using ZLogger;
@ -13,83 +11,44 @@ namespace SharpChat;
public class SockChatServer { public class SockChatServer {
public const ushort DEFAULT_PORT = 6770; public const ushort DEFAULT_PORT = 6770;
public const int DEFAULT_MSG_LENGTH_MAX = 5000;
public const int DEFAULT_MAX_CONNECTIONS = 5;
public const int DEFAULT_FLOOD_KICK_LENGTH = 30;
public const int DEFAULT_FLOOD_KICK_EXEMPT_RANK = 9;
public Context Context { get; } public Context Context { get; }
private readonly ILogger Logger; private readonly ILogger Logger;
private readonly BansClient BansClient;
private readonly CachedValue<ushort> Port; private readonly CachedValue<ushort> Port;
private readonly CachedValue<int> MaxMessageLength;
private readonly CachedValue<int> MaxConnections;
private readonly CachedValue<int> FloodKickLength;
private readonly CachedValue<int> FloodKickExemptRank;
private readonly List<C2SPacketHandler> GuestHandlers = []; private readonly List<C2SPacketHandler> GuestHandlers = [];
private readonly List<C2SPacketHandler> AuthedHandlers = []; private readonly List<C2SPacketHandler> AuthedHandlers = [];
private readonly SendMessageC2SPacketHandler SendMessageHandler; private readonly SendMessageC2SPacketHandler SendMessageHandler;
private static readonly string[] DEFAULT_CHANNELS = ["lounge"];
public SockChatServer( public SockChatServer(
ILoggerFactory logFactory,
CancellationTokenSource cancellationTokenSource, CancellationTokenSource cancellationTokenSource,
AuthClient authClient, Context ctx,
BansClient bansClient,
MessageStorage msgStorage,
Config config Config config
) { ) {
Logger = logFactory.CreateLogger("sockchat"); Logger = ctx.LoggerFactory.CreateLogger("sockchat");
Logger.ZLogInformation($"Initialising Sock Chat server..."); Logger.ZLogInformation($"Initialising Sock Chat server...");
BansClient = bansClient; Context = ctx;
Logger.ZLogDebug($"Fetching configuration values..."); Logger.ZLogDebug($"Fetching configuration values...");
Port = config.ReadCached("port", DEFAULT_PORT); Port = config.ReadCached("port", DEFAULT_PORT);
MaxMessageLength = config.ReadCached("msgMaxLength", DEFAULT_MSG_LENGTH_MAX);
MaxConnections = config.ReadCached("connMaxCount", DEFAULT_MAX_CONNECTIONS);
FloodKickLength = config.ReadCached("floodKickLength", DEFAULT_FLOOD_KICK_LENGTH);
FloodKickExemptRank = config.ReadCached("floodKickExemptRank", DEFAULT_FLOOD_KICK_EXEMPT_RANK);
Logger.ZLogDebug($"Creating context...");
Context = new Context(logFactory, msgStorage ?? throw new ArgumentNullException(nameof(msgStorage)));
Logger.ZLogDebug($"Creating channel list...");
string[]? channelNames = config.ReadValue("channels", DEFAULT_CHANNELS);
if(channelNames is not null)
foreach(string channelName in channelNames) {
Config channelCfg = config.ScopeTo($"channels:{channelName}");
string name = channelCfg.SafeReadValue("name", string.Empty)!;
if(string.IsNullOrWhiteSpace(name))
name = channelName;
Context.Channels.CreateChannel(
name,
channelCfg.SafeReadValue("password", string.Empty)!,
rank: channelCfg.SafeReadValue("minRank", 0)
);
}
Logger.ZLogDebug($"Registering unauthenticated handlers..."); Logger.ZLogDebug($"Registering unauthenticated handlers...");
GuestHandlers.Add(new AuthC2SPacketHandler( GuestHandlers.Add(new AuthC2SPacketHandler(
authClient, Context.Auth,
bansClient, Context.Bans,
Context.Channels, Context.Channels,
Context.RandomSnowflake, Context.RandomSnowflake,
MaxMessageLength, Context.MaxMessageLength,
MaxConnections Context.MaxConnections
)); ));
Logger.ZLogDebug($"Registering authenticated handlers..."); Logger.ZLogDebug($"Registering authenticated handlers...");
AuthedHandlers.AddRange([ AuthedHandlers.AddRange([
new PingC2SPacketHandler(authClient), new PingC2SPacketHandler(Context.Auth),
SendMessageHandler = new SendMessageC2SPacketHandler(Context.RandomSnowflake, MaxMessageLength), SendMessageHandler = new SendMessageC2SPacketHandler(Context.RandomSnowflake, Context.MaxMessageLength),
]); ]);
Logger.ZLogDebug($"Registering client commands..."); Logger.ZLogDebug($"Registering client commands...");
@ -106,10 +65,10 @@ public class SockChatServer {
new RankChannelClientCommand(), new RankChannelClientCommand(),
new BroadcastClientCommand(), new BroadcastClientCommand(),
new DeleteMessageClientCommand(), new DeleteMessageClientCommand(),
new KickBanClientCommand(bansClient), new KickBanClientCommand(Context.Bans),
new PardonUserClientCommand(bansClient), new PardonUserClientCommand(Context.Bans),
new PardonAddressClientCommand(bansClient), new PardonAddressClientCommand(Context.Bans),
new BanListClientCommand(bansClient), new BanListClientCommand(Context.Bans),
new RemoteAddressClientCommand(), new RemoteAddressClientCommand(),
new ShutdownRestartClientCommand(cancellationTokenSource) new ShutdownRestartClientCommand(cancellationTokenSource)
]); ]);
@ -190,7 +149,7 @@ public class SockChatServer {
await Context.SafeUpdate(); await Context.SafeUpdate();
// this doesn't affect non-authed connections????? // this doesn't affect non-authed connections?????
if(conn.User is not null && conn.User.Rank < FloodKickExemptRank) { if(conn.User is not null && conn.User.Rank < Context.FloodKickExemptRank) {
User? banUser = null; User? banUser = null;
string banAddr = string.Empty; string banAddr = string.Empty;
TimeSpan banDuration = TimeSpan.MinValue; TimeSpan banDuration = TimeSpan.MinValue;
@ -207,7 +166,7 @@ public class SockChatServer {
rateLimiter.Update(); rateLimiter.Update();
if(rateLimiter.IsExceeded) { if(rateLimiter.IsExceeded) {
banDuration = TimeSpan.FromSeconds(FloodKickLength); banDuration = TimeSpan.FromSeconds(Context.FloodKickLength);
banUser = conn.User; banUser = conn.User;
banAddr = conn.RemoteEndPoint.Address.ToString(); banAddr = conn.RemoteEndPoint.Address.ToString();
conn.Logger.ZLogWarning($"Exceeded flood limit! Issuing ban with duration {banDuration} on {banAddr}/{banUser.UserId}..."); conn.Logger.ZLogWarning($"Exceeded flood limit! Issuing ban with duration {banDuration} on {banAddr}/{banUser.UserId}...");
@ -224,7 +183,7 @@ public class SockChatServer {
await Context.BanUser(conn.User, banDuration, UserDisconnectS2CPacket.Reason.Flood); await Context.BanUser(conn.User, banDuration, UserDisconnectS2CPacket.Reason.Flood);
if(banDuration > TimeSpan.Zero) if(banDuration > TimeSpan.Zero)
await BansClient.BanCreate( await Context.Bans.BanCreate(
BanKind.User, BanKind.User,
banDuration, banDuration,
conn.RemoteEndPoint.Address, conn.RemoteEndPoint.Address,

View file

@ -1,6 +1,6 @@
namespace SharpChat.Configuration; namespace SharpChat.Configuration;
public interface Config : IDisposable { public interface Config {
/// <summary> /// <summary>
/// Creates a proxy object that forces all names to start with the given prefix. /// Creates a proxy object that forces all names to start with the given prefix.
/// </summary> /// </summary>

View file

@ -27,8 +27,4 @@ public class ScopedConfig(Config config, string prefix) : Config {
public CachedValue<T> ReadCached<T>(string name, T? fallback = default, TimeSpan? lifetime = null) { public CachedValue<T> ReadCached<T>(string name, T? fallback = default, TimeSpan? lifetime = null) {
return Config.ReadCached(GetName(name), fallback, lifetime); return Config.ReadCached(GetName(name), fallback, lifetime);
} }
public void Dispose() {
GC.SuppressFinalize(this);
}
} }

View file

@ -2,7 +2,7 @@ using System.Text;
namespace SharpChat.Configuration; namespace SharpChat.Configuration;
public class StreamConfig : Config { public class StreamConfig : Config, IDisposable {
private Stream Stream { get; } private Stream Stream { get; }
private StreamReader StreamReader { get; } private StreamReader StreamReader { get; }
private Mutex Lock { get; } private Mutex Lock { get; }