diff --git a/LarpixServer/Account/Requests.cs b/LarpixServer/Account/Requests.cs index 7080d5b..a8b4cc7 100644 --- a/LarpixServer/Account/Requests.cs +++ b/LarpixServer/Account/Requests.cs @@ -666,7 +666,7 @@ public static async Task Auth(HttpContext context, Func next, IQueryCollec "dm/message/send", "dm/message/delete", "dm/message/react", "dm/key/update", "dm/message/pin/toggle", "dm/message/react/add", "dm/message/react/remove", "user/storage/public/update", "user/storage/private/update", "dm/read", - "group/create", "user/group/list", "group/info", "group/settings/update", + "group/create", "user/group/list", "group/info", "user/invite/accept", "user/invite/decline", "user/invite/revoke", "user/dm/list", "user/group/list", "user/storage/public/update", "user/storage/private/update", "group/storage/public/update", "group/storage/private/update", "group/storage/private/getentry", "dm/read", "group/read", "dm/message/send", "dm/message/delete", "dm/message/react/add", "dm/message/react/remove", "dm/message/pin/toggle", @@ -806,32 +806,23 @@ public static async Task Auth(HttpContext context, Func next, IQueryCollec break; case "group/create": { - Universal3String groupCreate = JsonSerializer.Deserialize( + Universal2String groupCreate = JsonSerializer.Deserialize( serializedBody.string2, - AppJsonSerializerContext.Default.Universal3String + AppJsonSerializerContext.Default.Universal2String ); - await context.Response.WriteAsync(Encryption.Encryption.EncryptString(await Room.Groups.GroupCreate(id, groupCreate.string1, groupCreate.string2, groupCreate.string3), password)); + await context.Response.WriteAsync(Encryption.Encryption.EncryptString(await Room.Groups.GroupCreate(id, groupCreate.string1, groupCreate.string2), password)); break; } case "group/info": await context.Response.WriteAsync(Encryption.Encryption.EncryptString(await Room.Groups.GroupInfo(serializedBody.string2), password)); break; - case "group/settings/update": - { - Universal3String req = JsonSerializer.Deserialize( - serializedBody.string2, - AppJsonSerializerContext.Default.Universal3String - ); - await context.Response.WriteAsync(Encryption.Encryption.EncryptString(await Room.Groups.GroupSettingsUpdate(id, req.string1, req.string2, req.string3), password)); - break; - } case "group/invite": { - Universal3String payload = JsonSerializer.Deserialize( + Universal2String payload = JsonSerializer.Deserialize( serializedBody.string2, - AppJsonSerializerContext.Default.Universal3String + AppJsonSerializerContext.Default.Universal2String ); - await context.Response.WriteAsync(Encryption.Encryption.EncryptString(await Room.Groups.GroupInvite(id, payload.string1, payload.string2, payload.string3), password)); + await context.Response.WriteAsync(Encryption.Encryption.EncryptString(await Room.Groups.GroupInvite(id, payload.string1, payload.string2), password)); break; } case "group/invite/revoke": diff --git a/LarpixServer/Account/Utils.cs b/LarpixServer/Account/Utils.cs index f0da0f6..2411e59 100644 --- a/LarpixServer/Account/Utils.cs +++ b/LarpixServer/Account/Utils.cs @@ -256,23 +256,7 @@ public class Utils string content = Encoding.UTF8.GetString(await Fs.ReadFile($"{path}/{invite}")); if (!first) groupBuilder.Append(','); first = false; - - string timestampStr; - string keysPayload = ""; - if (content.StartsWith("{")) - { - var data = JsonSerializer.Deserialize(content, LarpixServer.Utils.Jsons.AppJsonSerializerContext.Default.Universal2String); - timestampStr = data.string1; - if (!string.IsNullOrEmpty(data.string2)) { - keysPayload = $", \"keysPayload\": {JsonSerializer.Serialize(data.string2, LarpixServer.Utils.Jsons.AppJsonSerializerContext.Default.String)}"; - } - } - else - { - timestampStr = content; - } - - groupBuilder.Append($"\"{groupId};{userId}\": {{ \"timestamp\": {timestampStr}, \"receiver\": \"{userId}\", \"groupId\": \"{groupId}\"{keysPayload} }}"); + groupBuilder.Append($"\"{groupId};{userId}\": {{ \"timestamp\": {content}, \"receiver\": \"{userId}\", \"groupId\": \"{groupId}\" }}"); } } groupBuilder.Append("}"); @@ -313,23 +297,7 @@ public class Utils string content = Encoding.UTF8.GetString(await Fs.ReadFile($"{path}/{invite}")); if (!first) groupBuilder.Append(','); first = false; - - string timestampStr; - string keysPayload = ""; - if (content.StartsWith("{")) - { - var data = JsonSerializer.Deserialize(content, LarpixServer.Utils.Jsons.AppJsonSerializerContext.Default.Universal2String); - timestampStr = data.string1; - if (!string.IsNullOrEmpty(data.string2)) { - keysPayload = $", \"keysPayload\": {JsonSerializer.Serialize(data.string2, LarpixServer.Utils.Jsons.AppJsonSerializerContext.Default.String)}"; - } - } - else - { - timestampStr = content; - } - - groupBuilder.Append($"\"{groupId};{userId}\": {{ \"timestamp\": {timestampStr}, \"sender\": \"{userId}\", \"groupId\": \"{groupId}\"{keysPayload} }}"); + groupBuilder.Append($"\"{groupId};{userId}\": {{ \"timestamp\": {content}, \"sender\": \"{userId}\", \"groupId\": \"{groupId}\" }}"); } } groupBuilder.Append("}"); @@ -379,24 +347,15 @@ public class Utils string dirPath = $"{ACCOUNTS_DATA_DIR}/{id}/storage/public"; string path = $"{dirPath}/{entry}"; - SemaphoreSlim userLock = GetUserLock(id); - await userLock.WaitAsync(); - try - { - long currentTotalSize = LarpixServer.Utils.Utils.GetDirectorySize(dirPath); - long fileExistingSize = File.Exists(path) ? new FileInfo(path).Length : 0; + long currentTotalSize = LarpixServer.Utils.Utils.GetDirectorySize(dirPath); + long fileExistingSize = File.Exists(path) ? new FileInfo(path).Length : 0; - if (currentTotalSize - fileExistingSize + contentBytes.Length > PUBLIC_STORAGE_LIMIT) - { - return "error:storage.limit.exceeded"; - } - - await Fs.WriteFile(path, contentBytes); - } - finally + if (currentTotalSize - fileExistingSize + contentBytes.Length > PUBLIC_STORAGE_LIMIT) { - userLock.Release(); + return "error:storage.limit.exceeded"; } + + await Fs.WriteFile(path, contentBytes); return "success:storage.entry.updated"; } @@ -418,24 +377,15 @@ public class Utils string dirPath = $"{ACCOUNTS_DATA_DIR}/{id}/storage/public"; string path = $"{dirPath}/{entry}"; - SemaphoreSlim userLock = GetUserLock(id); - await userLock.WaitAsync(); - try - { - long currentTotalSize = LarpixServer.Utils.Utils.GetDirectorySize(dirPath); - long fileExistingSize = File.Exists(path) ? new FileInfo(path).Length : 0; + long currentTotalSize = LarpixServer.Utils.Utils.GetDirectorySize(dirPath); + long fileExistingSize = File.Exists(path) ? new FileInfo(path).Length : 0; - if (currentTotalSize - fileExistingSize + contentBytes.Length > PUBLIC_STORAGE_LIMIT) - { - return "error:storage.limit.exceeded"; - } - - await Fs.WriteFile(path, contentBytes); - } - finally + if (currentTotalSize - fileExistingSize + contentBytes.Length > PUBLIC_STORAGE_LIMIT) { - userLock.Release(); + return "error:storage.limit.exceeded"; } + + await Fs.WriteFile(path, contentBytes); return "success:storage.entry.updated"; } @@ -471,24 +421,15 @@ public class Utils string dirPath = $"{ACCOUNTS_DATA_DIR}/{id}/storage/private"; string path = $"{dirPath}/{entry}"; - SemaphoreSlim userLock = GetUserLock(id); - await userLock.WaitAsync(); - try - { - long currentTotalSize = LarpixServer.Utils.Utils.GetDirectorySize(dirPath); - long fileExistingSize = File.Exists(path) ? new FileInfo(path).Length : 0; + long currentTotalSize = LarpixServer.Utils.Utils.GetDirectorySize(dirPath); + long fileExistingSize = File.Exists(path) ? new FileInfo(path).Length : 0; - if (currentTotalSize - fileExistingSize + contentBytes.Length > PRIVATE_STORAGE_LIMIT) - { - return "error:storage.limit.exceeded"; - } - - await Fs.WriteFile(path, contentBytes); - } - finally + if (currentTotalSize - fileExistingSize + contentBytes.Length > PRIVATE_STORAGE_LIMIT) { - userLock.Release(); + return "error:storage.limit.exceeded"; } + + await Fs.WriteFile(path, contentBytes); return "success:storage.entry.updated"; } diff --git a/LarpixServer/Filesystem/Fs.cs b/LarpixServer/Filesystem/Fs.cs index ebacd4e..2d69a7d 100644 --- a/LarpixServer/Filesystem/Fs.cs +++ b/LarpixServer/Filesystem/Fs.cs @@ -37,33 +37,25 @@ public class Fs public static void ProcessCacheSpace() { - if (fileCache.Count >= FILE_CACHE_SIZE) + while (fileCache.Count >= FILE_CACHE_SIZE) { - foreach (var kvp in fileCache) - { - fileCache.TryRemove(kvp.Key, out _); - if (fileCache.Count < FILE_CACHE_SIZE) break; - } + var firstKey = fileCache.Keys.FirstOrDefault(); + if (firstKey != null) fileCache.TryRemove(firstKey, out _); } - if (existCache.Count >= EXIST_CACHE_SIZE) + while (existCache.Count >= EXIST_CACHE_SIZE) { - foreach (var kvp in existCache) - { - existCache.TryRemove(kvp.Key, out _); - if (existCache.Count < EXIST_CACHE_SIZE) break; - } + var firstKey = existCache.Keys.FirstOrDefault(); + if (firstKey != null) existCache.TryRemove(firstKey, out _); } - if (dirCache.Count >= DIR_CACHE_SIZE) + while (dirCache.Count >= DIR_CACHE_SIZE) { - foreach (var kvp in dirCache) - { - dirCache.TryRemove(kvp.Key, out _); - if (dirCache.Count < DIR_CACHE_SIZE) break; - } + var firstKey = dirCache.Keys.FirstOrDefault(); + if (firstKey != null) dirCache.TryRemove(firstKey, out _); + } + } - } public static ulong ClearCache(string pattern) { diff --git a/LarpixServer/Room/Groups.cs b/LarpixServer/Room/Groups.cs index bad84bc..d7e355c 100644 --- a/LarpixServer/Room/Groups.cs +++ b/LarpixServer/Room/Groups.cs @@ -53,10 +53,9 @@ public class Groups await Fs.WriteFile(groupPath, Encoding.UTF8.GetBytes(JsonSerializer.Serialize(fileGroup, AppJsonSerializerContext.Default.Universal3String))); } - public static async Task GroupCreate(string id, string name, string body, string historyVisible = "false") + public static async Task GroupCreate(string id, string name, string body) { - SemaphoreSlim roomLock = Room.Requests.GetRoomLock(id); - await roomLock.WaitAsync(); + await Room.Requests.createLock.WaitAsync(); try { string groupsDir = $"{ROOMS_DIR}/groups"; @@ -85,7 +84,6 @@ public class Groups await Fs.WriteFile($"{groupPath}/keys/last", Encoding.UTF8.GetBytes("0")); await Fs.WriteFile($"{groupPath}/keys/newrequired", Encoding.UTF8.GetBytes("false")); await Fs.WriteFile($"{groupPath}/name", Encoding.UTF8.GetBytes(name)); - await Fs.WriteFile($"{groupPath}/history_visible", Encoding.UTF8.GetBytes(historyVisible == "true" ? "true" : "false")); // body is the key encrypted for the creator await Fs.WriteFile($"{groupPath}/keys/0/{id};{DOMAIN}", Encoding.UTF8.GetBytes(body)); @@ -113,7 +111,7 @@ public class Groups } finally { - roomLock.Release(); + Room.Requests.createLock.Release(); } } @@ -151,103 +149,6 @@ public class Groups return $"{ROOMS_DIR}/groups/{domain}/{id}"; } - public static async Task GroupSettingsUpdate(string userId, string groupId, string name, string historyVisible) - { - string groupPath = GetGroupPath(groupId); - if (!Fs.Exists(groupPath)) return "error:group.not.found"; - - string memberPath = $"{groupPath}/members/{userId};{Utils.Utils.DOMAIN}"; - - SemaphoreSlim roomLock = Room.Requests.GetRoomLock(groupId); - await roomLock.WaitAsync(); - try - { - if (!Fs.Exists(memberPath)) return "error:not.a.member"; - if (!string.IsNullOrEmpty(name)) - { - if (string.IsNullOrWhiteSpace(name)) return "error:invalid.name"; - await Fs.WriteFile($"{groupPath}/name", Encoding.UTF8.GetBytes(name)); - } - - if (!string.IsNullOrEmpty(historyVisible)) - { - bool currentState = false; - if (Fs.Exists($"{groupPath}/history_visible")) - { - currentState = Encoding.UTF8.GetString(await Fs.ReadFile($"{groupPath}/history_visible")) == "true"; - } - - bool newState = historyVisible == "true"; - - if (currentState != newState) - { - await Fs.WriteFile($"{groupPath}/history_visible", Encoding.UTF8.GetBytes(newState ? "true" : "false")); - - string timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString(); - - Message infoMsg = new(); - infoMsg.attachment = ""; - infoMsg.author = userId; - infoMsg.timestamp = timestamp; - infoMsg.type = "larp.info.history"; - infoMsg.content = newState ? "{blah(history.visible)}" : "{blah(history.hidden)}"; - infoMsg.key = ""; - - ulong lastMessageId = 0; - if (Fs.Exists($"{groupPath}/messages/last")) - { - lastMessageId = ulong.Parse(Encoding.UTF8.GetString(await Fs.ReadFile($"{groupPath}/messages/last"))); - } - ulong newMessageId = lastMessageId + 1; - - infoMsg.pervious = lastMessageId.ToString(); - - await Fs.WriteFile($"{groupPath}/messages/{newMessageId}", Encoding.UTF8.GetBytes(JsonSerializer.Serialize(infoMsg, AppJsonSerializerContext.Default.Message))); - await Fs.WriteFile($"{groupPath}/messages/last", Encoding.UTF8.GetBytes(newMessageId.ToString())); - - string[] members = Fs.ReadDirectory($"{groupPath}/members"); - foreach (string memberWD in members) - { - string memberStr = Path.GetFileName(memberWD); - bool isLocal = Account.Utils.IsUserLocal(memberStr, out string d); - string memberId = Account.Utils.GetValidIdOrZero(memberStr); - if (isLocal) - { - if (memberId == userId) - { - await UpdateUserGroup(memberId, groupId, "true", timestamp); - await Utils.Utils.SendWsMessage(memberId, $"group_message:{groupId}:read"); - } - else - { - _ = Task.Run(async () => - { - SemaphoreSlim userLock = Account.Utils.GetUserLock(memberId); - await userLock.WaitAsync(); - try - { - await UpdateUserGroup(memberId, groupId, "false", timestamp); - } - finally - { - userLock.Release(); - } - await Utils.Utils.SendWsMessage(memberId, $"group_message:{groupId}:unread"); - }); - } - } - } - } - } - } - finally - { - roomLock.Release(); - } - - return "success"; - } - public static async Task GroupInfo(string groupId) { string groupPath = GetGroupPath(groupId); @@ -280,26 +181,19 @@ public class Groups newRequired = Encoding.UTF8.GetString(await Fs.ReadFile($"{groupPath}/keys/newrequired")); } - string historyVisible = "false"; - if (Fs.Exists($"{groupPath}/history_visible")) - { - historyVisible = Encoding.UTF8.GetString(await Fs.ReadFile($"{groupPath}/history_visible")); - } - - var info = new Universal6String + var info = new Universal5String { string1 = groupId, string2 = name, string3 = memberCount, string4 = lastKeyIndex, - string5 = newRequired, - string6 = historyVisible + string5 = newRequired }; - return JsonSerializer.Serialize(info, AppJsonSerializerContext.Default.Universal6String); + return JsonSerializer.Serialize(info, AppJsonSerializerContext.Default.Universal5String); } - public static async Task GroupInvite(string id, string groupId, string targetId, string keysPayload = "") + public static async Task GroupInvite(string id, string groupId, string targetId) { bool isLocal = Account.Utils.IsUserLocal(targetId, out string domain); string tId = Account.Utils.GetValidIdOrZero(targetId); @@ -307,54 +201,6 @@ public class Groups string groupPath = GetGroupPath(groupId); if (!Fs.Exists(groupPath)) return "error:group.not.found"; - // Validate keys payload based on historyVisible - bool historyVisible = false; - string historyFile = $"{groupPath}/history_visible"; - if (Fs.Exists(historyFile)) - { - string histContent = Encoding.UTF8.GetString(await Fs.ReadFile(historyFile)); - historyVisible = (histContent == "true"); - } - - if (!historyVisible) - { - if (!string.IsNullOrEmpty(keysPayload)) - { - return "error:history.hidden.keys.provided"; - } - } - else - { - if (string.IsNullOrEmpty(keysPayload)) return "error:missing.keys"; - - System.Collections.Generic.Dictionary keysDict = null; - try - { - keysDict = JsonSerializer.Deserialize>(keysPayload, AppJsonSerializerContext.Default.DictionaryStringString); - } - catch { } - - if (keysDict == null) return "error:missing.keys"; - - string lastKeyFile = $"{groupPath}/keys/last"; - if (Fs.Exists(lastKeyFile)) - { - string lastKeyIndexStr = Encoding.UTF8.GetString(await Fs.ReadFile(lastKeyFile)); - if (int.TryParse(lastKeyIndexStr, out int lastKeyIndex)) - { - for (int i = 0; i <= lastKeyIndex; i++) - { - if (!keysDict.ContainsKey(i.ToString())) - { - return "error:missing.keys"; - } - } - } - else return "error:missing.keys"; - } - else return "error:missing.keys"; - } - // Check if inviter is in group if (!Fs.Exists($"{groupPath}/members/{id};{DOMAIN}")) return "error:not.in.group"; @@ -373,15 +219,9 @@ public class Groups string inviteFile = ACCOUNTS_DATA_DIR + $"/{id2}/groupinvites/recv/{groupId};{id}"; if (Fs.Exists(inviteFile)) return "info:user.already.invited"; - LarpixServer.Utils.Jsons.Universal2String inviteData = new LarpixServer.Utils.Jsons.Universal2String - { - string1 = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString(), - string2 = keysPayload ?? "" - }; - byte[] inviteBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(inviteData, AppJsonSerializerContext.Default.Universal2String)); - + byte[] timestamp = Encoding.UTF8.GetBytes(DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString()); Fs.CreateDirectory(ACCOUNTS_DATA_DIR + $"/{id}/groupinvites/sent"); - await Fs.WriteFile(ACCOUNTS_DATA_DIR + $"/{id}/groupinvites/sent/{groupId};{id2}", inviteBytes); + await Fs.WriteFile(ACCOUNTS_DATA_DIR + $"/{id}/groupinvites/sent/{groupId};{id2}", timestamp); _ = Task.Run(async () => { @@ -390,7 +230,7 @@ public class Groups try { Fs.CreateDirectory(ACCOUNTS_DATA_DIR + $"/{id2}/groupinvites/recv"); - await Fs.WriteFile(inviteFile, inviteBytes); + await Fs.WriteFile(inviteFile, timestamp); } finally { @@ -461,17 +301,14 @@ public class Groups if (iId == "0") return "error:invalid.user"; string inviteFile = ACCOUNTS_DATA_DIR + $"/{id}/groupinvites/recv/{groupId};{iId}"; + if (!Fs.Exists(inviteFile)) return "error:no.invite"; string groupPath = GetGroupPath(groupId); if (!Fs.Exists(groupPath)) return "error:group.not.found"; - SemaphoreSlim roomLock = Room.Requests.GetRoomLock(groupId); - await roomLock.WaitAsync(); + await Room.Requests.createLock.WaitAsync(); try { - if (!Fs.Exists(inviteFile)) return "error:no.invite"; - string inviteContent = Encoding.UTF8.GetString(await Fs.ReadFile(inviteFile)); - // Delete the invites Fs.DeleteFile(inviteFile); if (Account.Utils.IsUserLocal(inviterId, out string domain)) @@ -482,57 +319,9 @@ public class Groups // Add to members await Fs.WriteFile($"{groupPath}/members/{id};{DOMAIN}", Array.Empty()); - // Read invite data - string keysPayload = ""; - if (inviteContent.StartsWith("{")) - { - try - { - var data = JsonSerializer.Deserialize(inviteContent, LarpixServer.Utils.Jsons.AppJsonSerializerContext.Default.Universal2String); - if (data != null) - { - keysPayload = data.string2 ?? ""; - } - } - catch { } - } + // Require new key + await Fs.WriteFile($"{groupPath}/keys/newrequired", Encoding.UTF8.GetBytes("true")); - // Require new key if history is not visible or no keys were provided - bool historyVisible = false; - if (Fs.Exists($"{groupPath}/history_visible")) - { - historyVisible = Encoding.UTF8.GetString(await Fs.ReadFile($"{groupPath}/history_visible")) == "true"; - } - - if (historyVisible && !string.IsNullOrEmpty(keysPayload)) - { - System.Collections.Generic.Dictionary keysDict = null; - try - { - keysDict = JsonSerializer.Deserialize>(keysPayload, AppJsonSerializerContext.Default.DictionaryStringString); - } - catch { } - - if (keysDict != null) - { - foreach (var kvp in keysDict) - { - string keyIndex = kvp.Key; - string keyData = kvp.Value; - string newKeyDir = $"{groupPath}/keys/{keyIndex}"; - Fs.CreateDirectory(newKeyDir); - await Fs.WriteFile($"{newKeyDir}/{id};{DOMAIN}", Encoding.UTF8.GetBytes(keyData)); - } - } - else - { - await Fs.WriteFile($"{groupPath}/keys/newrequired", Encoding.UTF8.GetBytes("true")); - } - } - else - { - await Fs.WriteFile($"{groupPath}/keys/newrequired", Encoding.UTF8.GetBytes("true")); - } string timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString(); Message joinMsg = new(); @@ -554,26 +343,11 @@ public class Groups await UpdateUserGroup(id, groupId, "true", timestamp); - // Notify all members (including the new one) via WebSocket - string[] currentMembers = Fs.ReadDirectory($"{groupPath}/members"); - foreach (string memberWD in currentMembers) - { - string memberStr = Path.GetFileName(memberWD); - string memberIdStr = Account.Utils.GetValidIdOrZero(memberStr); - if (Account.Utils.IsUserLocal(memberStr, out string d)) - { - _ = Task.Run(async () => - { - await Utils.Utils.SendWsMessage(memberIdStr, $"group_message:{groupId}:unread"); - }); - } - } - return "success:joined"; } finally { - roomLock.Release(); + Room.Requests.createLock.Release(); } } @@ -607,13 +381,11 @@ public class Groups string groupPath = GetGroupPath(groupId); if (!Fs.Exists(groupPath)) return "error:group.not.found"; + if (!Fs.Exists($"{groupPath}/members/{id};{DOMAIN}")) return "error:not.in.group"; - SemaphoreSlim roomLock = Room.Requests.GetRoomLock(groupId); - await roomLock.WaitAsync(); + await Room.Requests.createLock.WaitAsync(); try { - if (!Fs.Exists($"{groupPath}/members/{id};{DOMAIN}")) return "error:not.in.group"; - // 1. Check if newrequired is true string newRequiredPath = $"{groupPath}/keys/newrequired"; if (!Fs.Exists(newRequiredPath) || Encoding.UTF8.GetString(await Fs.ReadFile(newRequiredPath)) != "true") @@ -666,7 +438,7 @@ public class Groups } finally { - roomLock.Release(); + Room.Requests.createLock.Release(); } } @@ -708,20 +480,11 @@ public class Groups catch { contentBytes = Encoding.UTF8.GetBytes(contentBase64); } string dirPath = $"{Utils.Utils.ROOMS_DIR}/groups/{domain}/{id}/storage/public"; string path = $"{dirPath}/{entry}"; - SemaphoreSlim roomLock = Room.Requests.GetRoomLock(groupId); - await roomLock.WaitAsync(); - try - { - Fs.CreateDirectory(dirPath); - long currentTotalSize = Utils.Utils.GetDirectorySize(dirPath); - long fileExistingSize = Fs.Exists(path) ? new System.IO.FileInfo(path).Length : 0; - if (currentTotalSize - fileExistingSize + contentBytes.Length > Utils.Utils.PUBLIC_STORAGE_LIMIT) return "error:storage.limit.exceeded"; - await Fs.WriteFile(path, contentBytes); - } - finally - { - roomLock.Release(); - } + Fs.CreateDirectory(dirPath); + long currentTotalSize = Utils.Utils.GetDirectorySize(dirPath); + long fileExistingSize = Fs.Exists(path) ? new System.IO.FileInfo(path).Length : 0; + if (currentTotalSize - fileExistingSize + contentBytes.Length > Utils.Utils.PUBLIC_STORAGE_LIMIT) return "error:storage.limit.exceeded"; + await Fs.WriteFile(path, contentBytes); return "success:storage.entry.updated"; } public static async Task GetGroupPrivateStorageEntry(string userId, string groupId, string entry) @@ -749,60 +512,14 @@ public class Groups catch { contentBytes = Encoding.UTF8.GetBytes(contentBase64); } string dirPath = $"{Utils.Utils.ROOMS_DIR}/groups/{domain}/{id}/storage/private"; string path = $"{dirPath}/{entry}"; - SemaphoreSlim roomLock = Room.Requests.GetRoomLock(groupId); - await roomLock.WaitAsync(); - try - { - Fs.CreateDirectory(dirPath); - long currentTotalSize = Utils.Utils.GetDirectorySize(dirPath); - long fileExistingSize = Fs.Exists(path) ? new System.IO.FileInfo(path).Length : 0; - if (currentTotalSize - fileExistingSize + contentBytes.Length > Utils.Utils.PRIVATE_STORAGE_LIMIT) return "error:storage.limit.exceeded"; - await Fs.WriteFile(path, contentBytes); - } - finally - { - roomLock.Release(); - } + Fs.CreateDirectory(dirPath); + long currentTotalSize = Utils.Utils.GetDirectorySize(dirPath); + long fileExistingSize = Fs.Exists(path) ? new System.IO.FileInfo(path).Length : 0; + if (currentTotalSize - fileExistingSize + contentBytes.Length > Utils.Utils.PRIVATE_STORAGE_LIMIT) return "error:storage.limit.exceeded"; + await Fs.WriteFile(path, contentBytes); return "success:storage.entry.updated"; } - public static async Task GroupDelete(string groupId) - { - string[] parts = groupId.Split(';'); - string id = parts[0]; - string domain = parts.Length > 1 ? parts[1] : Utils.Utils.DOMAIN; - string groupPath = $"{Utils.Utils.ROOMS_DIR}/groups/{domain}/{id}"; - - if (!Fs.Exists(groupPath)) return "error:group.not.found"; - - SemaphoreSlim roomLock = Room.Requests.GetRoomLock(groupId); - await roomLock.WaitAsync(); - try - { - string membersDir = $"{groupPath}/members"; - if (Fs.Exists(membersDir)) - { - string[] memberFiles = Fs.ReadDirectory(membersDir); - foreach (string memberFile in memberFiles) - { - string memberId = memberFile.Split(';')[0]; - string userGroupPath = $"{Utils.Utils.ACCOUNTS_DATA_DIR}/{memberId}/groups/{id};{domain}"; - if (Fs.Exists(userGroupPath)) - { - Fs.DeleteFile(userGroupPath); - } - } - } - Fs.DeleteDirectory(groupPath); - } - finally - { - roomLock.Release(); - } - - return "success"; - } - public static async Task GroupLeave(string userId, string groupId) { string[] parts = groupId.Split(';'); @@ -814,14 +531,12 @@ public class Groups return "error:group.not.found"; string memberPath = $"{groupPath}/members/{userId};{Utils.Utils.DOMAIN}"; + if (!Fs.Exists(memberPath)) + return "error:not.a.member"; - SemaphoreSlim roomLock = Room.Requests.GetRoomLock(groupId); - await roomLock.WaitAsync(); + await Room.Requests.createLock.WaitAsync(); try { - if (!Fs.Exists(memberPath)) - return "error:not.a.member"; - // Remove from members Fs.DeleteFile(memberPath); @@ -832,7 +547,7 @@ public class Groups Fs.DeleteFile(userGroupPath); } - // Always require new key when someone leaves + // Require new key await Fs.WriteFile($"{groupPath}/keys/newrequired", Encoding.UTF8.GetBytes("true")); // Add info message @@ -854,32 +569,10 @@ public class Groups } await Fs.WriteFile($"{groupPath}/messages/last", Encoding.UTF8.GetBytes(msgId.ToString())); await Fs.WriteFile($"{groupPath}/messages/{msgId}", Encoding.UTF8.GetBytes(JsonSerializer.Serialize(msg, AppJsonSerializerContext.Default.Message))); - - // Notify remaining members via WebSocket - string[] currentMembers = Fs.ReadDirectory($"{groupPath}/members"); - foreach (string memberWD in currentMembers) - { - string memberStr = Path.GetFileName(memberWD); - string memberIdStr = Account.Utils.GetValidIdOrZero(memberStr); - if (Account.Utils.IsUserLocal(memberStr, out string d)) - { - _ = Task.Run(async () => - { - await Utils.Utils.SendWsMessage(memberIdStr, $"group_message:{groupId}:unread"); - }); - } - } } finally { - roomLock.Release(); - } - - // If members count == 0, delete group folder and we are done. - // Doing it outside the lock since GroupDelete takes the lock again. - if (Fs.ReadDirectory($"{groupPath}/members").Length == 0) - { - await GroupDelete(groupId); + Room.Requests.createLock.Release(); } return "success"; diff --git a/LarpixServer/Room/Requests.cs b/LarpixServer/Room/Requests.cs index 57d8d57..5fbafe1 100644 --- a/LarpixServer/Room/Requests.cs +++ b/LarpixServer/Room/Requests.cs @@ -10,23 +10,6 @@ namespace LarpixServer.Room; public class Requests { public static SemaphoreSlim createLock = new SemaphoreSlim(1, 1); - - private static SemaphoreSlim[]? _roomLocksArray = null; - - public static SemaphoreSlim GetRoomLock(string roomId) - { - if (_roomLocksArray == null) - { - int size = LOCK_SIZE > 0 ? LOCK_SIZE : 65536; - var newArray = Enumerable.Range(0, size).Select(_ => new SemaphoreSlim(1, 1)).ToArray(); - Interlocked.CompareExchange(ref _roomLocksArray, newArray, null); - } - - int hash = roomId.GetHashCode(); - int index = (hash & 0x7FFFFFFF) % _roomLocksArray.Length; - return _roomLocksArray[index]; - } - public const int MAX_DM_KEY_SIZE = 8192; private static bool IsSafeDmId(string dmId) diff --git a/LarpixServer/Room/Shared.cs b/LarpixServer/Room/Shared.cs index 339af72..0a6e4be 100644 --- a/LarpixServer/Room/Shared.cs +++ b/LarpixServer/Room/Shared.cs @@ -122,46 +122,45 @@ public class Shared public static async Task MessageSend(string id, string roomType, string roomId, string content, string attachment, string responded, string keyIndex = "0") { + if (!await IsMemberOfRoom(id, roomType, roomId)) return "error:forbidden"; + + if (roomType == "groups") + { + string newRequiredPath = $"{Groups.GetGroupPath(roomId)}/keys/newrequired"; + if (Fs.Exists(newRequiredPath)) + { + string isRequired = Encoding.UTF8.GetString(await Fs.ReadFile(newRequiredPath)); + if (isRequired == "true") + { + return "error:key.rotation.required"; + } + } + + // Check if keyIndex matches last key + string lastKeyPath = $"{Groups.GetGroupPath(roomId)}/keys/last"; + if (Fs.Exists(lastKeyPath)) + { + string lastKey = Encoding.UTF8.GetString(await Fs.ReadFile(lastKeyPath)); + if (keyIndex != lastKey && keyIndex != "") // empty key means larp.info messages + { + return "error:invalid.key.index"; + } + } + } + + if (content != null && content.Length > LarpixServer.Utils.Utils.MAX_MESSAGE_SIZE) + { + string sizeStr = LarpixServer.Utils.Utils.MAX_MESSAGE_SIZE >= 1024 ? $"{LarpixServer.Utils.Utils.MAX_MESSAGE_SIZE / 1024.0:0.##} KB" : $"{LarpixServer.Utils.Utils.MAX_MESSAGE_SIZE} B"; + return $"error:message.too.long:{sizeStr}"; + } + string msgDir = $"{GetRoomPath(roomType, roomId)}/messages"; long nextId = 0; Message msg = new Message(); - SemaphoreSlim roomLock = Room.Requests.GetRoomLock(roomId); - await roomLock.WaitAsync(); + await Room.Requests.createLock.WaitAsync(); try { - if (!await IsMemberOfRoom(id, roomType, roomId)) return "error:forbidden"; - - if (roomType == "groups") - { - string newRequiredPath = $"{Groups.GetGroupPath(roomId)}/keys/newrequired"; - if (Fs.Exists(newRequiredPath)) - { - string isRequired = Encoding.UTF8.GetString(await Fs.ReadFile(newRequiredPath)); - if (isRequired == "true") - { - return "error:key.rotation.required"; - } - } - - // Check if keyIndex matches last key - string lastKeyPath = $"{Groups.GetGroupPath(roomId)}/keys/last"; - if (Fs.Exists(lastKeyPath)) - { - string lastKey = Encoding.UTF8.GetString(await Fs.ReadFile(lastKeyPath)); - if (keyIndex != lastKey && keyIndex != "") // empty key means larp.info messages - { - return "error:invalid.key.index"; - } - } - } - - if (content != null && content.Length > LarpixServer.Utils.Utils.MAX_MESSAGE_SIZE) - { - string sizeStr = LarpixServer.Utils.Utils.MAX_MESSAGE_SIZE >= 1024 ? $"{LarpixServer.Utils.Utils.MAX_MESSAGE_SIZE / 1024.0:0.##} KB" : $"{LarpixServer.Utils.Utils.MAX_MESSAGE_SIZE} B"; - return $"error:message.too.long:{sizeStr}"; - } - long last = 0; if (Fs.Exists($"{msgDir}/last")) { @@ -219,7 +218,7 @@ public class Shared } finally { - roomLock.Release(); + Room.Requests.createLock.Release(); } string[] members = Fs.ReadDirectory($"{GetRoomPath(roomType, roomId)}/members"); @@ -267,41 +266,32 @@ public class Shared string msgPath = $"{GetRoomPath(roomType, roomId)}/messages/{msgId}"; if (!Fs.Exists(msgPath)) return "error:message.not.found"; - SemaphoreSlim roomLock = Room.Requests.GetRoomLock(roomId); - await roomLock.WaitAsync(); - try + Message msg = JsonSerializer.Deserialize( + Encoding.UTF8.GetString(await Fs.ReadFile(msgPath)), + AppJsonSerializerContext.Default.Message + ); + + if (msg.author != id) return "error:forbidden"; + if (msg.type == "larp.redacted") return "error:message.already.deleted"; + + msg.type = "larp.redacted"; + msg.reactions = ""; + msg.content = "{blah(larp.redacted)}"; + msg.attachment = ""; + msg.key = ""; + + await Fs.WriteFile(msgPath, Encoding.UTF8.GetBytes(JsonSerializer.Serialize(msg, AppJsonSerializerContext.Default.Message))); + + string reactDir = $"{GetRoomPath(roomType, roomId)}/reactions/{msgId}"; + if (Fs.Exists(reactDir)) { - Message msg = JsonSerializer.Deserialize( - Encoding.UTF8.GetString(await Fs.ReadFile(msgPath)), - AppJsonSerializerContext.Default.Message - ); - - if (msg.author != id) return "error:forbidden"; - if (msg.type == "larp.redacted") return "error:message.already.deleted"; - - msg.type = "larp.redacted"; - msg.reactions = ""; - msg.content = "{blah(larp.redacted)}"; - msg.attachment = ""; - msg.key = ""; - - await Fs.WriteFile(msgPath, Encoding.UTF8.GetBytes(JsonSerializer.Serialize(msg, AppJsonSerializerContext.Default.Message))); - - string reactDir = $"{GetRoomPath(roomType, roomId)}/reactions/{msgId}"; - if (Fs.Exists(reactDir)) - { - Fs.DeleteDirectory(reactDir); - } - - string attDir = $"{GetRoomPath(roomType, roomId)}/attachments/{msgId}"; - if (Fs.Exists(attDir)) - { - Fs.DeleteDirectory(attDir); - } + Fs.DeleteDirectory(reactDir); } - finally + + string attDir = $"{GetRoomPath(roomType, roomId)}/attachments/{msgId}"; + if (Fs.Exists(attDir)) { - roomLock.Release(); + Fs.DeleteDirectory(attDir); } string[] members = Fs.ReadDirectory($"{GetRoomPath(roomType, roomId)}/members"); @@ -341,8 +331,7 @@ public class Shared string reactDir = $"{GetRoomPath(roomType, roomId)}/reactions/{msgId}"; if (!Fs.Exists(reactDir)) Fs.CreateDirectory(reactDir); - SemaphoreSlim roomLock = Room.Requests.GetRoomLock(roomId); - await roomLock.WaitAsync(); + await Room.Requests.createLock.WaitAsync(); try { long last = 0; @@ -357,7 +346,7 @@ public class Shared } finally { - roomLock.Release(); + Room.Requests.createLock.Release(); } string[] members = Fs.ReadDirectory($"{GetRoomPath(roomType, roomId)}/members"); @@ -422,8 +411,7 @@ public class Shared string pinnedFilePath = $"{pinnedDir}/{msgId}"; bool isNowPinned = false; - SemaphoreSlim roomLock = Room.Requests.GetRoomLock(roomId); - await roomLock.WaitAsync(); + await Room.Requests.createLock.WaitAsync(); try { if (Fs.Exists(pinnedFilePath)) @@ -462,7 +450,7 @@ public class Shared } finally { - roomLock.Release(); + Room.Requests.createLock.Release(); } string[] members = Fs.ReadDirectory($"{GetRoomPath(roomType, roomId)}/members"); diff --git a/LarpixServer/Utils/AppJsonSerializerContext.cs b/LarpixServer/Utils/AppJsonSerializerContext.cs index 606e290..51e875a 100644 --- a/LarpixServer/Utils/AppJsonSerializerContext.cs +++ b/LarpixServer/Utils/AppJsonSerializerContext.cs @@ -11,7 +11,6 @@ using System.Collections.Generic; [JsonSerializable(typeof(Universal3String))] [JsonSerializable(typeof(Universal4String))] [JsonSerializable(typeof(Universal5String))] -[JsonSerializable(typeof(Universal6String))] [JsonSerializable(typeof(ServerInfo))] [JsonSerializable(typeof(Message))] [JsonSerializable(typeof(Dictionary))] diff --git a/LarpixServer/Utils/Jsons.cs b/LarpixServer/Utils/Jsons.cs index 8c6dcc5..3c9e25e 100644 --- a/LarpixServer/Utils/Jsons.cs +++ b/LarpixServer/Utils/Jsons.cs @@ -34,15 +34,6 @@ public class Universal5String public string string5 { get; set; } } -public class Universal6String -{ - public string string1 { get; set; } - public string string2 { get; set; } - public string string3 { get; set; } - public string string4 { get; set; } - public string string5 { get; set; } - public string string6 { get; set; } -} public class KeyExchangePayload {