Create Context before creating a server.
This commit is contained in:
parent
e0050a51bd
commit
c537df792e
7 changed files with 85 additions and 76 deletions
SharpChat.Flashii
SharpChat
SharpChatCommon/Configuration
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue