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";
public async Task AuthBumpUsersOnline(IEnumerable<(IPAddress remoteAddr, string userId)> entries) {
logger.ZLogInformation($"Bumping online users list...");
if(!entries.Any())
return;
logger.ZLogInformation($"Bumping online users list...");
string now = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
StringBuilder sb = new();
sb.AppendFormat("bump#{0}", now);

View file

@ -1,5 +1,8 @@
using Microsoft.Extensions.Logging;
using SharpChat.Auth;
using SharpChat.Bans;
using SharpChat.Channels;
using SharpChat.Configuration;
using SharpChat.Events;
using SharpChat.Messages;
using SharpChat.Snowflake;
@ -12,11 +15,26 @@ using ZLogger;
namespace SharpChat;
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 readonly SemaphoreSlim ContextAccess = new(1, 1);
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;
public SnowflakeGenerator SnowflakeGenerator { get; } = new();
@ -25,16 +43,47 @@ public class Context {
public ChannelsContext Channels { get; } = new();
public HashSet<Connection> Connections { get; } = [];
public HashSet<User> Users { get; } = [];
public MessageStorage Messages { get; }
public HashSet<ChannelUserAssoc> ChannelUsers { get; } = [];
public Dictionary<string, RateLimiter> UserRateLimiters { 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;
Logger = logFactory.CreateLogger("ctx");
Messages = msgs;
Config = config;
Messages = storage.CreateMessageStorage();
Auth = authClient;
Bans = bansClient;
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) {

View file

@ -82,11 +82,12 @@ if(!File.Exists(configFile) && configFile == CONFIG) {
sw.WriteLine();
sw.WriteLine("# General Configuration");
sw.WriteLine($"#chat:port {SockChatServer.DEFAULT_PORT}");
sw.WriteLine($"#chat:msgMaxLength {SockChatServer.DEFAULT_MSG_LENGTH_MAX}");
sw.WriteLine($"#chat:connMaxCount {SockChatServer.DEFAULT_MAX_CONNECTIONS}");
sw.WriteLine($"#chat:floodKickLength {SockChatServer.DEFAULT_FLOOD_KICK_LENGTH}");
sw.WriteLine($"#chat:msgMaxLength {Context.DEFAULT_MSG_LENGTH_MAX}");
sw.WriteLine($"#chat:connMaxCount {Context.DEFAULT_MAX_CONNECTIONS}");
sw.WriteLine($"#chat:floodKickLength {Context.DEFAULT_FLOOD_KICK_LENGTH}");
sw.WriteLine("# Sock Chat Configuration");
sw.WriteLine($"#sockchat:port {SockChatServer.DEFAULT_PORT}");
sw.WriteLine();
sw.WriteLine("# Channels");
@ -173,14 +174,17 @@ try {
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...");
await new SockChatServer(
logFactory,
cts,
flashii,
flashii,
storage.CreateMessageStorage(),
config.ScopeTo("chat")
ctx,
config.ScopeTo("sockchat")
).Listen(cts.Token);
} finally {
if(storage is IDisposable disp) {

View file

@ -1,10 +1,8 @@
using Microsoft.Extensions.Logging;
using SharpChat.Auth;
using SharpChat.Bans;
using SharpChat.C2SPacketHandlers;
using SharpChat.ClientCommands;
using SharpChat.Configuration;
using SharpChat.Messages;
using SharpChat.SockChat.S2CPackets;
using System.Net;
using ZLogger;
@ -13,83 +11,44 @@ namespace SharpChat;
public class SockChatServer {
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; }
private readonly ILogger Logger;
private readonly BansClient BansClient;
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> AuthedHandlers = [];
private readonly SendMessageC2SPacketHandler SendMessageHandler;
private static readonly string[] DEFAULT_CHANNELS = ["lounge"];
public SockChatServer(
ILoggerFactory logFactory,
CancellationTokenSource cancellationTokenSource,
AuthClient authClient,
BansClient bansClient,
MessageStorage msgStorage,
Context ctx,
Config config
) {
Logger = logFactory.CreateLogger("sockchat");
Logger = ctx.LoggerFactory.CreateLogger("sockchat");
Logger.ZLogInformation($"Initialising Sock Chat server...");
BansClient = bansClient;
Context = ctx;
Logger.ZLogDebug($"Fetching configuration values...");
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...");
GuestHandlers.Add(new AuthC2SPacketHandler(
authClient,
bansClient,
Context.Auth,
Context.Bans,
Context.Channels,
Context.RandomSnowflake,
MaxMessageLength,
MaxConnections
Context.MaxMessageLength,
Context.MaxConnections
));
Logger.ZLogDebug($"Registering authenticated handlers...");
AuthedHandlers.AddRange([
new PingC2SPacketHandler(authClient),
SendMessageHandler = new SendMessageC2SPacketHandler(Context.RandomSnowflake, MaxMessageLength),
new PingC2SPacketHandler(Context.Auth),
SendMessageHandler = new SendMessageC2SPacketHandler(Context.RandomSnowflake, Context.MaxMessageLength),
]);
Logger.ZLogDebug($"Registering client commands...");
@ -106,10 +65,10 @@ public class SockChatServer {
new RankChannelClientCommand(),
new BroadcastClientCommand(),
new DeleteMessageClientCommand(),
new KickBanClientCommand(bansClient),
new PardonUserClientCommand(bansClient),
new PardonAddressClientCommand(bansClient),
new BanListClientCommand(bansClient),
new KickBanClientCommand(Context.Bans),
new PardonUserClientCommand(Context.Bans),
new PardonAddressClientCommand(Context.Bans),
new BanListClientCommand(Context.Bans),
new RemoteAddressClientCommand(),
new ShutdownRestartClientCommand(cancellationTokenSource)
]);
@ -190,7 +149,7 @@ public class SockChatServer {
await Context.SafeUpdate();
// 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;
string banAddr = string.Empty;
TimeSpan banDuration = TimeSpan.MinValue;
@ -207,7 +166,7 @@ public class SockChatServer {
rateLimiter.Update();
if(rateLimiter.IsExceeded) {
banDuration = TimeSpan.FromSeconds(FloodKickLength);
banDuration = TimeSpan.FromSeconds(Context.FloodKickLength);
banUser = conn.User;
banAddr = conn.RemoteEndPoint.Address.ToString();
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);
if(banDuration > TimeSpan.Zero)
await BansClient.BanCreate(
await Context.Bans.BanCreate(
BanKind.User,
banDuration,
conn.RemoteEndPoint.Address,

View file

@ -1,6 +1,6 @@
namespace SharpChat.Configuration;
public interface Config : IDisposable {
public interface Config {
/// <summary>
/// Creates a proxy object that forces all names to start with the given prefix.
/// </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) {
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;
public class StreamConfig : Config {
public class StreamConfig : Config, IDisposable {
private Stream Stream { get; }
private StreamReader StreamReader { get; }
private Mutex Lock { get; }