about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--.idea/.idea.LibMatrix/.idea/vcs.xml1
m---------ArcaneLibs0
-rw-r--r--LibMatrix.EventTypes/Common/MjolnirShortcodeEventContent.cs2
-rw-r--r--LibMatrix.EventTypes/Spec/RoomMessageEventContent.cs28
-rw-r--r--LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs59
-rw-r--r--LibMatrix.EventTypes/Spec/State/RoomInfo/RoomHistoryVisibilityEventContent.cs6
-rw-r--r--LibMatrix.EventTypes/Spec/State/RoomInfo/RoomPolicyServerEventContent.cs11
-rw-r--r--LibMatrix.EventTypes/Spec/State/RoomInfo/RoomRedactionEventContent.cs18
-rw-r--r--LibMatrix.Federation/AuthenticatedFederationClient.cs20
-rw-r--r--LibMatrix.Federation/Extensions/Ed25519Extensions.cs8
-rw-r--r--LibMatrix.Federation/LibMatrix.Federation.csproj19
-rw-r--r--LibMatrix.Federation/Utilities/JsonSigning.cs108
-rw-r--r--LibMatrix.Federation/Utilities/UnpaddedBase64.cs17
-rw-r--r--LibMatrix.Federation/XMatrixAuthorizationScheme.cs71
-rw-r--r--LibMatrix.sln458
-rw-r--r--LibMatrix/Extensions/CanonicalJsonSerializer.cs9
-rw-r--r--LibMatrix/Extensions/MatrixHttpClient.Single.cs36
-rw-r--r--LibMatrix/Helpers/MessageBuilder.cs14
-rw-r--r--LibMatrix/Helpers/RoomBuilder.cs173
-rw-r--r--LibMatrix/Helpers/SyncHelper.cs17
-rw-r--r--LibMatrix/Helpers/SyncProcessors/Msc4222EmulationSyncProcessor.cs30
-rw-r--r--LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs37
-rw-r--r--LibMatrix/Homeservers/FederationClient.cs65
-rw-r--r--LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Filters/SynapseAdminLocalUserQueryFilter.cs22
-rw-r--r--LibMatrix/Homeservers/ImplementationDetails/Synapse/SynapseAdminApiClient.cs4
-rw-r--r--LibMatrix/Responses/CreateRoomRequest.cs2
-rw-r--r--LibMatrix/Responses/EventIdResponse.cs (renamed from LibMatrix/EventIdResponse.cs)2
-rw-r--r--LibMatrix/Responses/LoginResponse.cs5
-rw-r--r--LibMatrix/Responses/MessagesResponse.cs (renamed from LibMatrix/MessagesResponse.cs)2
-rw-r--r--LibMatrix/Responses/UserIdAndReason.cs (renamed from LibMatrix/UserIdAndReason.cs)2
-rw-r--r--LibMatrix/Responses/WhoAmIResponse.cs (renamed from LibMatrix/WhoAmIResponse.cs)2
-rw-r--r--LibMatrix/RoomTypes/GenericRoom.cs6
-rw-r--r--LibMatrix/RoomTypes/SpaceRoom.cs1
-rw-r--r--LibMatrix/Services/HomeserverResolverService.cs34
-rw-r--r--LibMatrix/StateEvent.cs11
-rw-r--r--Tests/LibMatrix.Tests/DataTests/WhoAmITests.cs2
-rw-r--r--Utilities/LibMatrix.FederationTest/.gitignore1
-rw-r--r--Utilities/LibMatrix.FederationTest/Controllers/FederationKeysController.cs41
-rw-r--r--Utilities/LibMatrix.FederationTest/Controllers/FederationVersionController.cs18
-rw-r--r--Utilities/LibMatrix.FederationTest/Controllers/TestController.cs52
-rw-r--r--Utilities/LibMatrix.FederationTest/Controllers/WellKnownController.cs19
-rw-r--r--Utilities/LibMatrix.FederationTest/LibMatrix.FederationTest.csproj18
-rw-r--r--Utilities/LibMatrix.FederationTest/Program.cs34
-rw-r--r--Utilities/LibMatrix.FederationTest/Services/FederationKeyStore.cs31
-rw-r--r--Utilities/LibMatrix.FederationTest/Services/FederationTestConfiguration.cs10
-rw-r--r--Utilities/LibMatrix.FederationTest/Utilities/Ed25519Utils.cs28
-rw-r--r--Utilities/LibMatrix.FederationTest/appsettings.Development.json13
-rw-r--r--Utilities/LibMatrix.FederationTest/appsettings.json9
-rw-r--r--Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomStateController.cs1
-rw-r--r--Utilities/LibMatrix.HomeserverEmulator/Controllers/Users/UserController.cs1
-rw-r--r--Utilities/LibMatrix.Utilities.Bot/Interfaces/CommandContext.cs1
-rw-r--r--Utilities/LibMatrix.Utilities.Bot/Services/CommandListenerHostedService.cs3
52 files changed, 1322 insertions, 260 deletions
diff --git a/.idea/.idea.LibMatrix/.idea/vcs.xml b/.idea/.idea.LibMatrix/.idea/vcs.xml

index 35eb1dd..63ea78b 100644 --- a/.idea/.idea.LibMatrix/.idea/vcs.xml +++ b/.idea/.idea.LibMatrix/.idea/vcs.xml
@@ -2,5 +2,6 @@ <project version="4"> <component name="VcsDirectoryMappings"> <mapping directory="" vcs="Git" /> + <mapping directory="$PROJECT_DIR$/ArcaneLibs" vcs="Git" /> </component> </project> \ No newline at end of file diff --git a/ArcaneLibs b/ArcaneLibs -Subproject a2c73b7b89ca8910a0f9a9856bb06a6d5f49ce0 +Subproject 7cbf235cceb82dc711622314c49edfc7e2614dc diff --git a/LibMatrix.EventTypes/Common/MjolnirShortcodeEventContent.cs b/LibMatrix.EventTypes/Common/MjolnirShortcodeEventContent.cs
index a31cbbb..a1ebd79 100644 --- a/LibMatrix.EventTypes/Common/MjolnirShortcodeEventContent.cs +++ b/LibMatrix.EventTypes/Common/MjolnirShortcodeEventContent.cs
@@ -3,7 +3,7 @@ using System.Text.Json.Serialization; namespace LibMatrix.EventTypes.Common; [MatrixEvent(EventName = EventId)] -public class MjolnirShortcodeEventContent : TimelineEventContent { +public class MjolnirShortcodeEventContent : EventContent { public const string EventId = "org.matrix.mjolnir.shortcode"; [JsonPropertyName("shortcode")] diff --git a/LibMatrix.EventTypes/Spec/RoomMessageEventContent.cs b/LibMatrix.EventTypes/Spec/RoomMessageEventContent.cs
index 9602bf3..d1cf8be 100644 --- a/LibMatrix.EventTypes/Spec/RoomMessageEventContent.cs +++ b/LibMatrix.EventTypes/Spec/RoomMessageEventContent.cs
@@ -34,9 +34,29 @@ public class RoomMessageEventContent : TimelineEventContent { [JsonPropertyName("info")] public FileInfoStruct? FileInfo { get; set; } - + [JsonIgnore] - public string BodyWithoutReplyFallback => Body.Split('\n').SkipWhile(x => x.StartsWith(">")).SkipWhile(x=>x.Trim().Length == 0).Aggregate((x, y) => $"{x}\n{y}"); + public string BodyWithoutReplyFallback { + get { + var parts = Body + .Split('\n') + .SkipWhile(x => x.StartsWith(">")) + .SkipWhile(x => x.Trim().Length == 0) + .ToList(); + return parts.Count > 0 ? parts.Aggregate((x, y) => $"{x}\n{y}") : Body; + } + } + + [JsonPropertyName("m.mentions")] + public MentionsStruct? Mentions { get; set; } + + public class MentionsStruct { + [JsonPropertyName("user_ids")] + public List<string>? Users { get; set; } + + [JsonPropertyName("room")] + public bool? Room { get; set; } + } public class FileInfoStruct { [JsonPropertyName("mimetype")] @@ -47,10 +67,10 @@ public class RoomMessageEventContent : TimelineEventContent { [JsonPropertyName("thumbnail_url")] public string? ThumbnailUrl { get; set; } - + [JsonPropertyName("w")] public int? Width { get; set; } - + [JsonPropertyName("h")] public int? Height { get; set; } } diff --git a/LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs b/LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs
index 6f8c194..d75b19f 100644 --- a/LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs +++ b/LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs
@@ -35,7 +35,6 @@ public class RoomPolicyRuleEventContent : PolicyRuleEventContent { [DebuggerDisplay("""{GetType().Name.Replace("PolicyRuleEventContent", ""),nq} policy matching {Entity}, Reason: {Reason}""")] public abstract class PolicyRuleEventContent : EventContent { // public PolicyRuleEventContent() => Console.WriteLine($"init policy {GetType().Name}"); - private string? _reason; /// <summary> /// Entity this ban applies to, can use * and ? as globs. @@ -52,17 +51,7 @@ public abstract class PolicyRuleEventContent : EventContent { /// </summary> [JsonPropertyName("reason")] [FriendlyName(Name = "Reason")] - public virtual string? Reason { - get => - // Console.WriteLine($"Read policy reason: {_reason}"); - _reason; - set => - // Console.WriteLine($"Set policy reason: {value}"); - // if(init) - // Console.WriteLine(string.Join('\n', Environment.StackTrace.Split('\n')[..5])); - // init = true; - _reason = value; - } + public string? Reason { get; set; } /// <summary> /// Suggested action to take @@ -93,29 +82,46 @@ public abstract class PolicyRuleEventContent : EventContent { } } + [JsonPropertyName("org.matrix.msc4205.hashes")] + [TableHide] + public PolicyHash? Hashes { get; set; } + public string GetDraupnir2StateKey() => Convert.ToBase64String(SHA256.HashData($"{Entity}{Recommendation}".AsBytes().ToArray())); public Regex? GetEntityRegex() => Entity is null ? null : new(Entity.Replace(".", "\\.").Replace("*", ".*").Replace("?", ".")); public bool IsGlobRule() => - Entity != null + !string.IsNullOrWhiteSpace(Entity) && (Entity.Contains('*') || Entity.Contains('?')); - public bool EntityMatches(string entity) => - Entity != null - && ( - Entity == entity - || ( - IsGlobRule() - ? GetEntityRegex()!.IsMatch(entity) - : entity == Entity - ) - ); + public bool EntityMatches(string entity) { + if (string.IsNullOrWhiteSpace(entity)) return false; + + if (!string.IsNullOrWhiteSpace(Entity)) { + // Check if entity is equal regardless of glob check + var match = Entity == entity + || (IsGlobRule() && GetEntityRegex()!.IsMatch(entity)); + if (match) return match; + } + + if (Hashes is not null) { + if (!string.IsNullOrWhiteSpace(Hashes.Sha256)) { + var hash = SHA256.HashData(entity.AsBytes().ToArray()); + var match = Convert.ToBase64String(hash) == Hashes.Sha256; + if (match) return match; + } + } + + return false; + } public string? GetNormalizedRecommendation() { if (Recommendation is "m.ban" or "org.matrix.mjolnir.ban") return PolicyRecommendationTypes.Ban; + if (Recommendation is "m.takedown" or "org.matrix.msc4204.takedown") + return "m.takedown"; + return Recommendation; } } @@ -130,6 +136,13 @@ public static class PolicyRecommendationTypes { /// Mute this user /// </summary> public static string Mute = "support.feline.policy.recommendation_mute"; //stable prefix: m.mute, msc pending + + public static string Takedown = "m.takedown"; //unstable prefix: org.matrix.msc4204.takedown +} + +public class PolicyHash { + [JsonPropertyName("sha256")] + public string? Sha256 { get; set; } } // public class PolicySchemaDefinition { diff --git a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomHistoryVisibilityEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomHistoryVisibilityEventContent.cs
index 16cfcb0..8edf4a7 100644 --- a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomHistoryVisibilityEventContent.cs +++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomHistoryVisibilityEventContent.cs
@@ -8,4 +8,10 @@ public class RoomHistoryVisibilityEventContent : EventContent { [JsonPropertyName("history_visibility")] public required string HistoryVisibility { get; set; } + + public static class HistoryVisibilityTypes { + public const string WorldReadable = "world_readable"; + public const string Invited = "invited"; + public const string Shared = "shared"; + } } \ No newline at end of file diff --git a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomPolicyServerEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomPolicyServerEventContent.cs new file mode 100644
index 0000000..80e254f --- /dev/null +++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomPolicyServerEventContent.cs
@@ -0,0 +1,11 @@ +using System.Text.Json.Serialization; + +namespace LibMatrix.EventTypes.Spec.State.RoomInfo; + +[MatrixEvent(EventName = EventId)] +public class RoomPolicyServerEventContent : EventContent { + public const string EventId = "org.matrix.msc4284.policy"; + + [JsonPropertyName("via")] + public string? Via { get; set; } +} \ No newline at end of file diff --git a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomRedactionEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomRedactionEventContent.cs new file mode 100644
index 0000000..055f22d --- /dev/null +++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomRedactionEventContent.cs
@@ -0,0 +1,18 @@ +using System.Diagnostics.CodeAnalysis; +using System.Text.Json.Serialization; + +namespace LibMatrix.EventTypes.Spec.State.RoomInfo; + +[MatrixEvent(EventName = EventId)] +public class RoomRedactionEventContent : EventContent { + public const string EventId = "m.room.redaction"; + + [JsonPropertyName("reason")] + public string? Reason { get; set; } + + /// <summary> + /// Required in room version 11 + /// </summary> + [JsonPropertyName("redacts")] + public string? Redacts { get; set; } +} \ No newline at end of file diff --git a/LibMatrix.Federation/AuthenticatedFederationClient.cs b/LibMatrix.Federation/AuthenticatedFederationClient.cs new file mode 100644
index 0000000..6f8d44b --- /dev/null +++ b/LibMatrix.Federation/AuthenticatedFederationClient.cs
@@ -0,0 +1,20 @@ +using LibMatrix.Homeservers; + +namespace LibMatrix.Federation; + +public class AuthenticatedFederationClient : FederationClient { + + public class AuthenticatedFederationConfiguration { + + } + public AuthenticatedFederationClient(string federationEndpoint, AuthenticatedFederationConfiguration config, string? proxy = null) : base(federationEndpoint, proxy) + { + + } + + // public async Task<UserDeviceListResponse> GetUserDevicesAsync(string userId) { + // var response = await GetAsync<UserDeviceListResponse>($"/_matrix/federation/v1/user/devices/{userId}", accessToken); + // return response; + // } + +} \ No newline at end of file diff --git a/LibMatrix.Federation/Extensions/Ed25519Extensions.cs b/LibMatrix.Federation/Extensions/Ed25519Extensions.cs new file mode 100644
index 0000000..69baf58 --- /dev/null +++ b/LibMatrix.Federation/Extensions/Ed25519Extensions.cs
@@ -0,0 +1,8 @@ +using LibMatrix.FederationTest.Utilities; +using Org.BouncyCastle.Crypto.Parameters; + +namespace LibMatrix.Federation.Extensions; + +public static class Ed25519Extensions { + public static string ToUnpaddedBase64(this Ed25519PublicKeyParameters key) => UnpaddedBase64.Encode(key.GetEncoded()); +} \ No newline at end of file diff --git a/LibMatrix.Federation/LibMatrix.Federation.csproj b/LibMatrix.Federation/LibMatrix.Federation.csproj new file mode 100644
index 0000000..78086bb --- /dev/null +++ b/LibMatrix.Federation/LibMatrix.Federation.csproj
@@ -0,0 +1,19 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net9.0</TargetFramework> + <LangVersion>preview</LangVersion> + <ImplicitUsings>enable</ImplicitUsings> + <Nullable>enable</Nullable> + </PropertyGroup> + + <ItemGroup> + <ProjectReference Include="..\LibMatrix\LibMatrix.csproj" /> + </ItemGroup> + + <ItemGroup> + <PackageReference Include="BouncyCastle.Cryptography" Version="2.6.1" /> + <PackageReference Include="Microsoft.Extensions.Primitives" Version="10.0.0-preview.5.25277.114" /> + </ItemGroup> + +</Project> diff --git a/LibMatrix.Federation/Utilities/JsonSigning.cs b/LibMatrix.Federation/Utilities/JsonSigning.cs new file mode 100644
index 0000000..c727cde --- /dev/null +++ b/LibMatrix.Federation/Utilities/JsonSigning.cs
@@ -0,0 +1,108 @@ +using System.Globalization; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; +using ArcaneLibs.Extensions; +using LibMatrix.Extensions; +using LibMatrix.FederationTest.Utilities; +using LibMatrix.Homeservers; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math.EC.Rfc8032; + +namespace LibMatrix.Federation.Utilities; + +public static class JsonSigning { } + +[JsonConverter(typeof(SignedObjectConverterFactory))] +public class SignedObject<T> { + [JsonPropertyName("signatures")] + public Dictionary<string, Dictionary<string, string>> Signatures { get; set; } = new(); + + [JsonIgnore] + public Dictionary<string, Dictionary<ServerKeysResponse.VersionedKeyId, string>> VerifyKeysById { + get => Signatures.ToDictionary(server => server.Key, server => server.Value.ToDictionary(key => (ServerKeysResponse.VersionedKeyId)key.Key, key => key.Value)); + set => Signatures = value.ToDictionary(server => server.Key, server => server.Value.ToDictionary(key => (string)key.Key, key => key.Value)); + } + + [JsonExtensionData] + public required JsonObject Content { get; set; } + + [JsonIgnore] + public T TypedContent { + get => Content.Deserialize<T>() ?? throw new JsonException("Failed to deserialize TypedContent from Content."); + set => Content = JsonSerializer.Deserialize<JsonObject>(JsonSerializer.Serialize(value)) ?? new JsonObject(); + } +} + +// Content needs to be merged at toplevel +public class SignedObjectConverter<T> : JsonConverter<SignedObject<T>> { + public override SignedObject<T> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { + var jsonObject = JsonSerializer.Deserialize<JsonObject>(ref reader, options); + if (jsonObject == null) { + throw new JsonException("Failed to deserialize SignedObject, JSON object is null."); + } + + var signatures = jsonObject["signatures"] ?? throw new JsonException("Failed to find 'signatures' property in JSON object."); + jsonObject.Remove("signatures"); + + var signedObject = new SignedObject<T> { + Content = jsonObject, + Signatures = signatures.Deserialize<Dictionary<string, Dictionary<string, string>>>() + ?? throw new JsonException("Failed to deserialize 'signatures' property into Dictionary<string, Dictionary<string, string>>.") + }; + + return signedObject; + } + + public override void Write(Utf8JsonWriter writer, SignedObject<T> value, JsonSerializerOptions options) { + var targetObj = value.Content.DeepClone(); + targetObj["signatures"] = value.Signatures.ToJsonNode(); + JsonSerializer.Serialize(writer, targetObj, options); + } +} + +internal class SignedObjectConverterFactory : JsonConverterFactory { + public override bool CanConvert(Type typeToConvert) { + if (!typeToConvert.IsGenericType) return false; + return typeToConvert.GetGenericTypeDefinition() == typeof(SignedObject<>); + } + + public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) { + var wrappedType = typeToConvert.GetGenericArguments()[0]; + var converter = (JsonConverter)Activator.CreateInstance(typeof(SignedObjectConverter<>).MakeGenericType(wrappedType))!; + return converter; + } +} + +public static class ObjectExtensions { + public static SignedObject<T> Sign<T>(this SignedObject<T> content, string serverName, string keyName, Ed25519PrivateKeyParameters key) { + var signResult = Sign(content.Content, serverName, keyName, key); + var signedObject = new SignedObject<T> { + Signatures = content.Signatures, + Content = signResult.Content + }; + + if (!signedObject.Signatures.ContainsKey(serverName)) + signedObject.Signatures[serverName] = new Dictionary<string, string>(); + + signedObject.Signatures[serverName][keyName] = signResult.Signatures[serverName][keyName]; + return signedObject; + } + + public static SignedObject<T> Sign<T>(this T content, string serverName, string keyName, Ed25519PrivateKeyParameters key) { + SignedObject<T> signedObject = new() { + Signatures = [], + Content = JsonSerializer.Deserialize<JsonObject>(JsonSerializer.Serialize(content)) ?? new JsonObject(), + }; + + var contentBytes = CanonicalJsonSerializer.SerializeToUtf8Bytes(signedObject.Content); + var signature = new byte[Ed25519.SignatureSize]; + key.Sign(Ed25519.Algorithm.Ed25519, null, contentBytes, 0, contentBytes.Length, signature, 0); + + if (!signedObject.Signatures.ContainsKey(serverName)) + signedObject.Signatures[serverName] = new Dictionary<string, string>(); + + signedObject.Signatures[serverName][keyName] = UnpaddedBase64.Encode(signature); + return signedObject; + } +} \ No newline at end of file diff --git a/LibMatrix.Federation/Utilities/UnpaddedBase64.cs b/LibMatrix.Federation/Utilities/UnpaddedBase64.cs new file mode 100644
index 0000000..06f84b2 --- /dev/null +++ b/LibMatrix.Federation/Utilities/UnpaddedBase64.cs
@@ -0,0 +1,17 @@ +namespace LibMatrix.FederationTest.Utilities; + +public static class UnpaddedBase64 { + public static string Encode(byte[] data) { + return Convert.ToBase64String(data).TrimEnd('='); + } + + public static byte[] Decode(string base64) { + string paddedBase64 = base64; + switch (paddedBase64.Length % 4) { + case 2: paddedBase64 += "=="; break; + case 3: paddedBase64 += "="; break; + } + + return Convert.FromBase64String(paddedBase64); + } +} \ No newline at end of file diff --git a/LibMatrix.Federation/XMatrixAuthorizationScheme.cs b/LibMatrix.Federation/XMatrixAuthorizationScheme.cs new file mode 100644
index 0000000..fc402b7 --- /dev/null +++ b/LibMatrix.Federation/XMatrixAuthorizationScheme.cs
@@ -0,0 +1,71 @@ +using System.Net; +using System.Net.Http.Headers; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; +using ArcaneLibs.Extensions; +using Microsoft.Extensions.Primitives; + +namespace LibMatrix.Federation; + +public class XMatrixAuthorizationScheme { + public class XMatrixAuthorizationHeader { + public const string Scheme = "X-Matrix"; + + [JsonPropertyName("origin")] + public required string Origin { get; set; } + + [JsonPropertyName("destination")] + public required string Destination { get; set; } + + [JsonPropertyName("key")] + public required string Key { get; set; } + + [JsonPropertyName("sig")] + public required string Signature { get; set; } + + public static XMatrixAuthorizationHeader FromHeaderValue(AuthenticationHeaderValue header) { + if (header.Scheme != Scheme) + throw new LibMatrixException() { + Error = $"Expected authentication scheme of {Scheme}, got {header.Scheme}", + ErrorCode = MatrixException.ErrorCodes.M_UNAUTHORIZED + }; + + if (string.IsNullOrWhiteSpace(header.Parameter)) + throw new LibMatrixException() { + Error = $"Expected authentication header to have a value.", + ErrorCode = MatrixException.ErrorCodes.M_UNAUTHORIZED + }; + + var headerValues = new StringValues(header.Parameter); + foreach (var value in headerValues) { + Console.WriteLine(headerValues.ToJson()); + } + + return new() { + Destination = "", + Key = "", + Origin = "", + Signature = "" + }; + } + + public string ToHeaderValue() => $"{Scheme} origin=\"{Origin}\", destination=\"{Destination}\", key=\"{Key}\", sig=\"{Signature}\""; + } + + public class XMatrixRequestSignature { + [JsonPropertyName("method")] + public required string Method { get; set; } + + [JsonPropertyName("uri")] + public required string Uri { get; set; } + + [JsonPropertyName("origin")] + public required string OriginServerName { get; set; } + + [JsonPropertyName("destination")] + public required string DestinationServerName { get; set; } + + [JsonPropertyName("content"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public JsonObject? Content { get; set; } + } +} \ No newline at end of file diff --git a/LibMatrix.sln b/LibMatrix.sln
index 5feeeee..d6c1c0f 100644 --- a/LibMatrix.sln +++ b/LibMatrix.sln
@@ -1,148 +1,310 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31903.59 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ArcaneLibs", "ArcaneLibs", "{32C0F9AC-AF7D-4476-A269-99ACA000EF9F}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Blazor.Components", "ArcaneLibs\ArcaneLibs.Blazor.Components\ArcaneLibs.Blazor.Components.csproj", "{93D00F03-02FF-4C2A-8917-6863D6E633D9}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Legacy", "ArcaneLibs\ArcaneLibs.Legacy\ArcaneLibs.Legacy.csproj", "{14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Logging", "ArcaneLibs\ArcaneLibs.Logging\ArcaneLibs.Logging.csproj", "{51F770AF-C0C6-4247-A358-82DF323F473D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.StringNormalisation", "ArcaneLibs\ArcaneLibs.StringNormalisation\ArcaneLibs.StringNormalisation.csproj", "{8A8E8B67-9351-471E-A502-AF7FAE596CB4}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Tests", "ArcaneLibs\ArcaneLibs.Tests\ArcaneLibs.Tests.csproj", "{5DE8D878-EEBD-44DF-A8FC-0B4866B807FB}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Timings", "ArcaneLibs\ArcaneLibs.Timings\ArcaneLibs.Timings.csproj", "{567DABFB-611E-4779-9F39-1D4A5B8F0247}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.UsageTest", "ArcaneLibs\ArcaneLibs.UsageTest\ArcaneLibs.UsageTest.csproj", "{28AFDFEF-7597-4450-B999-87C22C24DCD8}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs", "ArcaneLibs\ArcaneLibs\ArcaneLibs.csproj", "{F80D5395-28E3-4C34-9662-A890A215DDA2}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.EventTypes", "LibMatrix.EventTypes\LibMatrix.EventTypes.csproj", "{90A38896-993A-49F1-903B-8C989D8C9B3A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix", "LibMatrix\LibMatrix.csproj", "{6AE504F8-734B-456B-8BEA-DE37639CB3A7}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{AAAA5609-D8C1-401E-BB5C-724EFE3955FB}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.Tests", "Tests\LibMatrix.Tests\LibMatrix.Tests.csproj", "{7E15D295-B938-409C-98F6-A22ABC7F3005}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Utilities", "Utilities", "{FBC6F613-4E0B-4A90-8854-31887A796EEF}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.DebugDataValidationApi", "Utilities\LibMatrix.DebugDataValidationApi\LibMatrix.DebugDataValidationApi.csproj", "{D632374A-70A1-4954-8F2D-C0B511A24426}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.DevTestBot", "Utilities\LibMatrix.DevTestBot\LibMatrix.DevTestBot.csproj", "{91E41EE3-687B-4CFD-94C4-028D09BACFAB}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.E2eeTestKit", "Utilities\LibMatrix.E2eeTestKit\LibMatrix.E2eeTestKit.csproj", "{4D411F2D-A97D-4485-A318-ED98B96B6CF6}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.HomeserverEmulator", "Utilities\LibMatrix.HomeserverEmulator\LibMatrix.HomeserverEmulator.csproj", "{7F36D200-FE91-4761-B681-BEE091FB953F}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.JsonSerializerContextGenerator", "Utilities\LibMatrix.JsonSerializerContextGenerator\LibMatrix.JsonSerializerContextGenerator.csproj", "{69CCB780-50CC-4E75-B794-932E44ACA80F}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.TestDataGenerator", "Utilities\LibMatrix.TestDataGenerator\LibMatrix.TestDataGenerator.csproj", "{C087F8AC-B438-4980-9A79-1E16D0CFB67A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.Utilities.Bot", "Utilities\LibMatrix.Utilities.Bot\LibMatrix.Utilities.Bot.csproj", "{E04E5F37-8F13-4000-ABA1-DAC0D795EA6A}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {93D00F03-02FF-4C2A-8917-6863D6E633D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {93D00F03-02FF-4C2A-8917-6863D6E633D9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {93D00F03-02FF-4C2A-8917-6863D6E633D9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {93D00F03-02FF-4C2A-8917-6863D6E633D9}.Release|Any CPU.Build.0 = Release|Any CPU - {14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB}.Release|Any CPU.Build.0 = Release|Any CPU - {51F770AF-C0C6-4247-A358-82DF323F473D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {51F770AF-C0C6-4247-A358-82DF323F473D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {51F770AF-C0C6-4247-A358-82DF323F473D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {51F770AF-C0C6-4247-A358-82DF323F473D}.Release|Any CPU.Build.0 = Release|Any CPU - {8A8E8B67-9351-471E-A502-AF7FAE596CB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8A8E8B67-9351-471E-A502-AF7FAE596CB4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8A8E8B67-9351-471E-A502-AF7FAE596CB4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8A8E8B67-9351-471E-A502-AF7FAE596CB4}.Release|Any CPU.Build.0 = Release|Any CPU - {5DE8D878-EEBD-44DF-A8FC-0B4866B807FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5DE8D878-EEBD-44DF-A8FC-0B4866B807FB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5DE8D878-EEBD-44DF-A8FC-0B4866B807FB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5DE8D878-EEBD-44DF-A8FC-0B4866B807FB}.Release|Any CPU.Build.0 = Release|Any CPU - {567DABFB-611E-4779-9F39-1D4A5B8F0247}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {567DABFB-611E-4779-9F39-1D4A5B8F0247}.Debug|Any CPU.Build.0 = Debug|Any CPU - {567DABFB-611E-4779-9F39-1D4A5B8F0247}.Release|Any CPU.ActiveCfg = Release|Any CPU - {567DABFB-611E-4779-9F39-1D4A5B8F0247}.Release|Any CPU.Build.0 = Release|Any CPU - {28AFDFEF-7597-4450-B999-87C22C24DCD8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {28AFDFEF-7597-4450-B999-87C22C24DCD8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {28AFDFEF-7597-4450-B999-87C22C24DCD8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {28AFDFEF-7597-4450-B999-87C22C24DCD8}.Release|Any CPU.Build.0 = Release|Any CPU - {F80D5395-28E3-4C34-9662-A890A215DDA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F80D5395-28E3-4C34-9662-A890A215DDA2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F80D5395-28E3-4C34-9662-A890A215DDA2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F80D5395-28E3-4C34-9662-A890A215DDA2}.Release|Any CPU.Build.0 = Release|Any CPU - {90A38896-993A-49F1-903B-8C989D8C9B3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {90A38896-993A-49F1-903B-8C989D8C9B3A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {90A38896-993A-49F1-903B-8C989D8C9B3A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {90A38896-993A-49F1-903B-8C989D8C9B3A}.Release|Any CPU.Build.0 = Release|Any CPU - {6AE504F8-734B-456B-8BEA-DE37639CB3A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6AE504F8-734B-456B-8BEA-DE37639CB3A7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6AE504F8-734B-456B-8BEA-DE37639CB3A7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6AE504F8-734B-456B-8BEA-DE37639CB3A7}.Release|Any CPU.Build.0 = Release|Any CPU - {7E15D295-B938-409C-98F6-A22ABC7F3005}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7E15D295-B938-409C-98F6-A22ABC7F3005}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7E15D295-B938-409C-98F6-A22ABC7F3005}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7E15D295-B938-409C-98F6-A22ABC7F3005}.Release|Any CPU.Build.0 = Release|Any CPU - {D632374A-70A1-4954-8F2D-C0B511A24426}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D632374A-70A1-4954-8F2D-C0B511A24426}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D632374A-70A1-4954-8F2D-C0B511A24426}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D632374A-70A1-4954-8F2D-C0B511A24426}.Release|Any CPU.Build.0 = Release|Any CPU - {91E41EE3-687B-4CFD-94C4-028D09BACFAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {91E41EE3-687B-4CFD-94C4-028D09BACFAB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {91E41EE3-687B-4CFD-94C4-028D09BACFAB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {91E41EE3-687B-4CFD-94C4-028D09BACFAB}.Release|Any CPU.Build.0 = Release|Any CPU - {4D411F2D-A97D-4485-A318-ED98B96B6CF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4D411F2D-A97D-4485-A318-ED98B96B6CF6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4D411F2D-A97D-4485-A318-ED98B96B6CF6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4D411F2D-A97D-4485-A318-ED98B96B6CF6}.Release|Any CPU.Build.0 = Release|Any CPU - {7F36D200-FE91-4761-B681-BEE091FB953F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7F36D200-FE91-4761-B681-BEE091FB953F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7F36D200-FE91-4761-B681-BEE091FB953F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7F36D200-FE91-4761-B681-BEE091FB953F}.Release|Any CPU.Build.0 = Release|Any CPU - {69CCB780-50CC-4E75-B794-932E44ACA80F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {69CCB780-50CC-4E75-B794-932E44ACA80F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {69CCB780-50CC-4E75-B794-932E44ACA80F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {69CCB780-50CC-4E75-B794-932E44ACA80F}.Release|Any CPU.Build.0 = Release|Any CPU - {C087F8AC-B438-4980-9A79-1E16D0CFB67A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C087F8AC-B438-4980-9A79-1E16D0CFB67A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C087F8AC-B438-4980-9A79-1E16D0CFB67A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C087F8AC-B438-4980-9A79-1E16D0CFB67A}.Release|Any CPU.Build.0 = Release|Any CPU - {E04E5F37-8F13-4000-ABA1-DAC0D795EA6A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E04E5F37-8F13-4000-ABA1-DAC0D795EA6A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E04E5F37-8F13-4000-ABA1-DAC0D795EA6A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E04E5F37-8F13-4000-ABA1-DAC0D795EA6A}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {93D00F03-02FF-4C2A-8917-6863D6E633D9} = {32C0F9AC-AF7D-4476-A269-99ACA000EF9F} - {14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB} = {32C0F9AC-AF7D-4476-A269-99ACA000EF9F} - {51F770AF-C0C6-4247-A358-82DF323F473D} = {32C0F9AC-AF7D-4476-A269-99ACA000EF9F} - {8A8E8B67-9351-471E-A502-AF7FAE596CB4} = {32C0F9AC-AF7D-4476-A269-99ACA000EF9F} - {5DE8D878-EEBD-44DF-A8FC-0B4866B807FB} = {32C0F9AC-AF7D-4476-A269-99ACA000EF9F} - {567DABFB-611E-4779-9F39-1D4A5B8F0247} = {32C0F9AC-AF7D-4476-A269-99ACA000EF9F} - {28AFDFEF-7597-4450-B999-87C22C24DCD8} = {32C0F9AC-AF7D-4476-A269-99ACA000EF9F} - {F80D5395-28E3-4C34-9662-A890A215DDA2} = {32C0F9AC-AF7D-4476-A269-99ACA000EF9F} - {7E15D295-B938-409C-98F6-A22ABC7F3005} = {AAAA5609-D8C1-401E-BB5C-724EFE3955FB} - {D632374A-70A1-4954-8F2D-C0B511A24426} = {FBC6F613-4E0B-4A90-8854-31887A796EEF} - {91E41EE3-687B-4CFD-94C4-028D09BACFAB} = {FBC6F613-4E0B-4A90-8854-31887A796EEF} - {4D411F2D-A97D-4485-A318-ED98B96B6CF6} = {FBC6F613-4E0B-4A90-8854-31887A796EEF} - {7F36D200-FE91-4761-B681-BEE091FB953F} = {FBC6F613-4E0B-4A90-8854-31887A796EEF} - {69CCB780-50CC-4E75-B794-932E44ACA80F} = {FBC6F613-4E0B-4A90-8854-31887A796EEF} - {C087F8AC-B438-4980-9A79-1E16D0CFB67A} = {FBC6F613-4E0B-4A90-8854-31887A796EEF} - {E04E5F37-8F13-4000-ABA1-DAC0D795EA6A} = {FBC6F613-4E0B-4A90-8854-31887A796EEF} - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ArcaneLibs", "ArcaneLibs", "{32C0F9AC-AF7D-4476-A269-99ACA000EF9F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Blazor.Components", "ArcaneLibs\ArcaneLibs.Blazor.Components\ArcaneLibs.Blazor.Components.csproj", "{93D00F03-02FF-4C2A-8917-6863D6E633D9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Legacy", "ArcaneLibs\ArcaneLibs.Legacy\ArcaneLibs.Legacy.csproj", "{14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Logging", "ArcaneLibs\ArcaneLibs.Logging\ArcaneLibs.Logging.csproj", "{51F770AF-C0C6-4247-A358-82DF323F473D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.StringNormalisation", "ArcaneLibs\ArcaneLibs.StringNormalisation\ArcaneLibs.StringNormalisation.csproj", "{8A8E8B67-9351-471E-A502-AF7FAE596CB4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Tests", "ArcaneLibs\ArcaneLibs.Tests\ArcaneLibs.Tests.csproj", "{5DE8D878-EEBD-44DF-A8FC-0B4866B807FB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Timings", "ArcaneLibs\ArcaneLibs.Timings\ArcaneLibs.Timings.csproj", "{567DABFB-611E-4779-9F39-1D4A5B8F0247}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.UsageTest", "ArcaneLibs\ArcaneLibs.UsageTest\ArcaneLibs.UsageTest.csproj", "{28AFDFEF-7597-4450-B999-87C22C24DCD8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs", "ArcaneLibs\ArcaneLibs\ArcaneLibs.csproj", "{F80D5395-28E3-4C34-9662-A890A215DDA2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.EventTypes", "LibMatrix.EventTypes\LibMatrix.EventTypes.csproj", "{90A38896-993A-49F1-903B-8C989D8C9B3A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix", "LibMatrix\LibMatrix.csproj", "{6AE504F8-734B-456B-8BEA-DE37639CB3A7}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{AAAA5609-D8C1-401E-BB5C-724EFE3955FB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.Tests", "Tests\LibMatrix.Tests\LibMatrix.Tests.csproj", "{7E15D295-B938-409C-98F6-A22ABC7F3005}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Utilities", "Utilities", "{FBC6F613-4E0B-4A90-8854-31887A796EEF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.DebugDataValidationApi", "Utilities\LibMatrix.DebugDataValidationApi\LibMatrix.DebugDataValidationApi.csproj", "{D632374A-70A1-4954-8F2D-C0B511A24426}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.DevTestBot", "Utilities\LibMatrix.DevTestBot\LibMatrix.DevTestBot.csproj", "{91E41EE3-687B-4CFD-94C4-028D09BACFAB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.E2eeTestKit", "Utilities\LibMatrix.E2eeTestKit\LibMatrix.E2eeTestKit.csproj", "{4D411F2D-A97D-4485-A318-ED98B96B6CF6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.HomeserverEmulator", "Utilities\LibMatrix.HomeserverEmulator\LibMatrix.HomeserverEmulator.csproj", "{7F36D200-FE91-4761-B681-BEE091FB953F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.JsonSerializerContextGenerator", "Utilities\LibMatrix.JsonSerializerContextGenerator\LibMatrix.JsonSerializerContextGenerator.csproj", "{69CCB780-50CC-4E75-B794-932E44ACA80F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.TestDataGenerator", "Utilities\LibMatrix.TestDataGenerator\LibMatrix.TestDataGenerator.csproj", "{C087F8AC-B438-4980-9A79-1E16D0CFB67A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.Utilities.Bot", "Utilities\LibMatrix.Utilities.Bot\LibMatrix.Utilities.Bot.csproj", "{E04E5F37-8F13-4000-ABA1-DAC0D795EA6A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.Federation", "LibMatrix.Federation\LibMatrix.Federation.csproj", "{0E6CF267-14FD-43D7-81CB-EE020FD1D106}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {93D00F03-02FF-4C2A-8917-6863D6E633D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {93D00F03-02FF-4C2A-8917-6863D6E633D9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {93D00F03-02FF-4C2A-8917-6863D6E633D9}.Debug|x64.ActiveCfg = Debug|Any CPU + {93D00F03-02FF-4C2A-8917-6863D6E633D9}.Debug|x64.Build.0 = Debug|Any CPU + {93D00F03-02FF-4C2A-8917-6863D6E633D9}.Debug|x86.ActiveCfg = Debug|Any CPU + {93D00F03-02FF-4C2A-8917-6863D6E633D9}.Debug|x86.Build.0 = Debug|Any CPU + {93D00F03-02FF-4C2A-8917-6863D6E633D9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {93D00F03-02FF-4C2A-8917-6863D6E633D9}.Release|Any CPU.Build.0 = Release|Any CPU + {93D00F03-02FF-4C2A-8917-6863D6E633D9}.Release|x64.ActiveCfg = Release|Any CPU + {93D00F03-02FF-4C2A-8917-6863D6E633D9}.Release|x64.Build.0 = Release|Any CPU + {93D00F03-02FF-4C2A-8917-6863D6E633D9}.Release|x86.ActiveCfg = Release|Any CPU + {93D00F03-02FF-4C2A-8917-6863D6E633D9}.Release|x86.Build.0 = Release|Any CPU + {14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB}.Debug|x64.ActiveCfg = Debug|Any CPU + {14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB}.Debug|x64.Build.0 = Debug|Any CPU + {14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB}.Debug|x86.ActiveCfg = Debug|Any CPU + {14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB}.Debug|x86.Build.0 = Debug|Any CPU + {14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB}.Release|Any CPU.Build.0 = Release|Any CPU + {14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB}.Release|x64.ActiveCfg = Release|Any CPU + {14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB}.Release|x64.Build.0 = Release|Any CPU + {14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB}.Release|x86.ActiveCfg = Release|Any CPU + {14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB}.Release|x86.Build.0 = Release|Any CPU + {51F770AF-C0C6-4247-A358-82DF323F473D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {51F770AF-C0C6-4247-A358-82DF323F473D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {51F770AF-C0C6-4247-A358-82DF323F473D}.Debug|x64.ActiveCfg = Debug|Any CPU + {51F770AF-C0C6-4247-A358-82DF323F473D}.Debug|x64.Build.0 = Debug|Any CPU + {51F770AF-C0C6-4247-A358-82DF323F473D}.Debug|x86.ActiveCfg = Debug|Any CPU + {51F770AF-C0C6-4247-A358-82DF323F473D}.Debug|x86.Build.0 = Debug|Any CPU + {51F770AF-C0C6-4247-A358-82DF323F473D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {51F770AF-C0C6-4247-A358-82DF323F473D}.Release|Any CPU.Build.0 = Release|Any CPU + {51F770AF-C0C6-4247-A358-82DF323F473D}.Release|x64.ActiveCfg = Release|Any CPU + {51F770AF-C0C6-4247-A358-82DF323F473D}.Release|x64.Build.0 = Release|Any CPU + {51F770AF-C0C6-4247-A358-82DF323F473D}.Release|x86.ActiveCfg = Release|Any CPU + {51F770AF-C0C6-4247-A358-82DF323F473D}.Release|x86.Build.0 = Release|Any CPU + {8A8E8B67-9351-471E-A502-AF7FAE596CB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8A8E8B67-9351-471E-A502-AF7FAE596CB4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8A8E8B67-9351-471E-A502-AF7FAE596CB4}.Debug|x64.ActiveCfg = Debug|Any CPU + {8A8E8B67-9351-471E-A502-AF7FAE596CB4}.Debug|x64.Build.0 = Debug|Any CPU + {8A8E8B67-9351-471E-A502-AF7FAE596CB4}.Debug|x86.ActiveCfg = Debug|Any CPU + {8A8E8B67-9351-471E-A502-AF7FAE596CB4}.Debug|x86.Build.0 = Debug|Any CPU + {8A8E8B67-9351-471E-A502-AF7FAE596CB4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8A8E8B67-9351-471E-A502-AF7FAE596CB4}.Release|Any CPU.Build.0 = Release|Any CPU + {8A8E8B67-9351-471E-A502-AF7FAE596CB4}.Release|x64.ActiveCfg = Release|Any CPU + {8A8E8B67-9351-471E-A502-AF7FAE596CB4}.Release|x64.Build.0 = Release|Any CPU + {8A8E8B67-9351-471E-A502-AF7FAE596CB4}.Release|x86.ActiveCfg = Release|Any CPU + {8A8E8B67-9351-471E-A502-AF7FAE596CB4}.Release|x86.Build.0 = Release|Any CPU + {5DE8D878-EEBD-44DF-A8FC-0B4866B807FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5DE8D878-EEBD-44DF-A8FC-0B4866B807FB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5DE8D878-EEBD-44DF-A8FC-0B4866B807FB}.Debug|x64.ActiveCfg = Debug|Any CPU + {5DE8D878-EEBD-44DF-A8FC-0B4866B807FB}.Debug|x64.Build.0 = Debug|Any CPU + {5DE8D878-EEBD-44DF-A8FC-0B4866B807FB}.Debug|x86.ActiveCfg = Debug|Any CPU + {5DE8D878-EEBD-44DF-A8FC-0B4866B807FB}.Debug|x86.Build.0 = Debug|Any CPU + {5DE8D878-EEBD-44DF-A8FC-0B4866B807FB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5DE8D878-EEBD-44DF-A8FC-0B4866B807FB}.Release|Any CPU.Build.0 = Release|Any CPU + {5DE8D878-EEBD-44DF-A8FC-0B4866B807FB}.Release|x64.ActiveCfg = Release|Any CPU + {5DE8D878-EEBD-44DF-A8FC-0B4866B807FB}.Release|x64.Build.0 = Release|Any CPU + {5DE8D878-EEBD-44DF-A8FC-0B4866B807FB}.Release|x86.ActiveCfg = Release|Any CPU + {5DE8D878-EEBD-44DF-A8FC-0B4866B807FB}.Release|x86.Build.0 = Release|Any CPU + {567DABFB-611E-4779-9F39-1D4A5B8F0247}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {567DABFB-611E-4779-9F39-1D4A5B8F0247}.Debug|Any CPU.Build.0 = Debug|Any CPU + {567DABFB-611E-4779-9F39-1D4A5B8F0247}.Debug|x64.ActiveCfg = Debug|Any CPU + {567DABFB-611E-4779-9F39-1D4A5B8F0247}.Debug|x64.Build.0 = Debug|Any CPU + {567DABFB-611E-4779-9F39-1D4A5B8F0247}.Debug|x86.ActiveCfg = Debug|Any CPU + {567DABFB-611E-4779-9F39-1D4A5B8F0247}.Debug|x86.Build.0 = Debug|Any CPU + {567DABFB-611E-4779-9F39-1D4A5B8F0247}.Release|Any CPU.ActiveCfg = Release|Any CPU + {567DABFB-611E-4779-9F39-1D4A5B8F0247}.Release|Any CPU.Build.0 = Release|Any CPU + {567DABFB-611E-4779-9F39-1D4A5B8F0247}.Release|x64.ActiveCfg = Release|Any CPU + {567DABFB-611E-4779-9F39-1D4A5B8F0247}.Release|x64.Build.0 = Release|Any CPU + {567DABFB-611E-4779-9F39-1D4A5B8F0247}.Release|x86.ActiveCfg = Release|Any CPU + {567DABFB-611E-4779-9F39-1D4A5B8F0247}.Release|x86.Build.0 = Release|Any CPU + {28AFDFEF-7597-4450-B999-87C22C24DCD8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {28AFDFEF-7597-4450-B999-87C22C24DCD8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {28AFDFEF-7597-4450-B999-87C22C24DCD8}.Debug|x64.ActiveCfg = Debug|Any CPU + {28AFDFEF-7597-4450-B999-87C22C24DCD8}.Debug|x64.Build.0 = Debug|Any CPU + {28AFDFEF-7597-4450-B999-87C22C24DCD8}.Debug|x86.ActiveCfg = Debug|Any CPU + {28AFDFEF-7597-4450-B999-87C22C24DCD8}.Debug|x86.Build.0 = Debug|Any CPU + {28AFDFEF-7597-4450-B999-87C22C24DCD8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {28AFDFEF-7597-4450-B999-87C22C24DCD8}.Release|Any CPU.Build.0 = Release|Any CPU + {28AFDFEF-7597-4450-B999-87C22C24DCD8}.Release|x64.ActiveCfg = Release|Any CPU + {28AFDFEF-7597-4450-B999-87C22C24DCD8}.Release|x64.Build.0 = Release|Any CPU + {28AFDFEF-7597-4450-B999-87C22C24DCD8}.Release|x86.ActiveCfg = Release|Any CPU + {28AFDFEF-7597-4450-B999-87C22C24DCD8}.Release|x86.Build.0 = Release|Any CPU + {F80D5395-28E3-4C34-9662-A890A215DDA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F80D5395-28E3-4C34-9662-A890A215DDA2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F80D5395-28E3-4C34-9662-A890A215DDA2}.Debug|x64.ActiveCfg = Debug|Any CPU + {F80D5395-28E3-4C34-9662-A890A215DDA2}.Debug|x64.Build.0 = Debug|Any CPU + {F80D5395-28E3-4C34-9662-A890A215DDA2}.Debug|x86.ActiveCfg = Debug|Any CPU + {F80D5395-28E3-4C34-9662-A890A215DDA2}.Debug|x86.Build.0 = Debug|Any CPU + {F80D5395-28E3-4C34-9662-A890A215DDA2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F80D5395-28E3-4C34-9662-A890A215DDA2}.Release|Any CPU.Build.0 = Release|Any CPU + {F80D5395-28E3-4C34-9662-A890A215DDA2}.Release|x64.ActiveCfg = Release|Any CPU + {F80D5395-28E3-4C34-9662-A890A215DDA2}.Release|x64.Build.0 = Release|Any CPU + {F80D5395-28E3-4C34-9662-A890A215DDA2}.Release|x86.ActiveCfg = Release|Any CPU + {F80D5395-28E3-4C34-9662-A890A215DDA2}.Release|x86.Build.0 = Release|Any CPU + {90A38896-993A-49F1-903B-8C989D8C9B3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {90A38896-993A-49F1-903B-8C989D8C9B3A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {90A38896-993A-49F1-903B-8C989D8C9B3A}.Debug|x64.ActiveCfg = Debug|Any CPU + {90A38896-993A-49F1-903B-8C989D8C9B3A}.Debug|x64.Build.0 = Debug|Any CPU + {90A38896-993A-49F1-903B-8C989D8C9B3A}.Debug|x86.ActiveCfg = Debug|Any CPU + {90A38896-993A-49F1-903B-8C989D8C9B3A}.Debug|x86.Build.0 = Debug|Any CPU + {90A38896-993A-49F1-903B-8C989D8C9B3A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {90A38896-993A-49F1-903B-8C989D8C9B3A}.Release|Any CPU.Build.0 = Release|Any CPU + {90A38896-993A-49F1-903B-8C989D8C9B3A}.Release|x64.ActiveCfg = Release|Any CPU + {90A38896-993A-49F1-903B-8C989D8C9B3A}.Release|x64.Build.0 = Release|Any CPU + {90A38896-993A-49F1-903B-8C989D8C9B3A}.Release|x86.ActiveCfg = Release|Any CPU + {90A38896-993A-49F1-903B-8C989D8C9B3A}.Release|x86.Build.0 = Release|Any CPU + {6AE504F8-734B-456B-8BEA-DE37639CB3A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6AE504F8-734B-456B-8BEA-DE37639CB3A7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6AE504F8-734B-456B-8BEA-DE37639CB3A7}.Debug|x64.ActiveCfg = Debug|Any CPU + {6AE504F8-734B-456B-8BEA-DE37639CB3A7}.Debug|x64.Build.0 = Debug|Any CPU + {6AE504F8-734B-456B-8BEA-DE37639CB3A7}.Debug|x86.ActiveCfg = Debug|Any CPU + {6AE504F8-734B-456B-8BEA-DE37639CB3A7}.Debug|x86.Build.0 = Debug|Any CPU + {6AE504F8-734B-456B-8BEA-DE37639CB3A7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6AE504F8-734B-456B-8BEA-DE37639CB3A7}.Release|Any CPU.Build.0 = Release|Any CPU + {6AE504F8-734B-456B-8BEA-DE37639CB3A7}.Release|x64.ActiveCfg = Release|Any CPU + {6AE504F8-734B-456B-8BEA-DE37639CB3A7}.Release|x64.Build.0 = Release|Any CPU + {6AE504F8-734B-456B-8BEA-DE37639CB3A7}.Release|x86.ActiveCfg = Release|Any CPU + {6AE504F8-734B-456B-8BEA-DE37639CB3A7}.Release|x86.Build.0 = Release|Any CPU + {7E15D295-B938-409C-98F6-A22ABC7F3005}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7E15D295-B938-409C-98F6-A22ABC7F3005}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7E15D295-B938-409C-98F6-A22ABC7F3005}.Debug|x64.ActiveCfg = Debug|Any CPU + {7E15D295-B938-409C-98F6-A22ABC7F3005}.Debug|x64.Build.0 = Debug|Any CPU + {7E15D295-B938-409C-98F6-A22ABC7F3005}.Debug|x86.ActiveCfg = Debug|Any CPU + {7E15D295-B938-409C-98F6-A22ABC7F3005}.Debug|x86.Build.0 = Debug|Any CPU + {7E15D295-B938-409C-98F6-A22ABC7F3005}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7E15D295-B938-409C-98F6-A22ABC7F3005}.Release|Any CPU.Build.0 = Release|Any CPU + {7E15D295-B938-409C-98F6-A22ABC7F3005}.Release|x64.ActiveCfg = Release|Any CPU + {7E15D295-B938-409C-98F6-A22ABC7F3005}.Release|x64.Build.0 = Release|Any CPU + {7E15D295-B938-409C-98F6-A22ABC7F3005}.Release|x86.ActiveCfg = Release|Any CPU + {7E15D295-B938-409C-98F6-A22ABC7F3005}.Release|x86.Build.0 = Release|Any CPU + {D632374A-70A1-4954-8F2D-C0B511A24426}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D632374A-70A1-4954-8F2D-C0B511A24426}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D632374A-70A1-4954-8F2D-C0B511A24426}.Debug|x64.ActiveCfg = Debug|Any CPU + {D632374A-70A1-4954-8F2D-C0B511A24426}.Debug|x64.Build.0 = Debug|Any CPU + {D632374A-70A1-4954-8F2D-C0B511A24426}.Debug|x86.ActiveCfg = Debug|Any CPU + {D632374A-70A1-4954-8F2D-C0B511A24426}.Debug|x86.Build.0 = Debug|Any CPU + {D632374A-70A1-4954-8F2D-C0B511A24426}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D632374A-70A1-4954-8F2D-C0B511A24426}.Release|Any CPU.Build.0 = Release|Any CPU + {D632374A-70A1-4954-8F2D-C0B511A24426}.Release|x64.ActiveCfg = Release|Any CPU + {D632374A-70A1-4954-8F2D-C0B511A24426}.Release|x64.Build.0 = Release|Any CPU + {D632374A-70A1-4954-8F2D-C0B511A24426}.Release|x86.ActiveCfg = Release|Any CPU + {D632374A-70A1-4954-8F2D-C0B511A24426}.Release|x86.Build.0 = Release|Any CPU + {91E41EE3-687B-4CFD-94C4-028D09BACFAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {91E41EE3-687B-4CFD-94C4-028D09BACFAB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {91E41EE3-687B-4CFD-94C4-028D09BACFAB}.Debug|x64.ActiveCfg = Debug|Any CPU + {91E41EE3-687B-4CFD-94C4-028D09BACFAB}.Debug|x64.Build.0 = Debug|Any CPU + {91E41EE3-687B-4CFD-94C4-028D09BACFAB}.Debug|x86.ActiveCfg = Debug|Any CPU + {91E41EE3-687B-4CFD-94C4-028D09BACFAB}.Debug|x86.Build.0 = Debug|Any CPU + {91E41EE3-687B-4CFD-94C4-028D09BACFAB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {91E41EE3-687B-4CFD-94C4-028D09BACFAB}.Release|Any CPU.Build.0 = Release|Any CPU + {91E41EE3-687B-4CFD-94C4-028D09BACFAB}.Release|x64.ActiveCfg = Release|Any CPU + {91E41EE3-687B-4CFD-94C4-028D09BACFAB}.Release|x64.Build.0 = Release|Any CPU + {91E41EE3-687B-4CFD-94C4-028D09BACFAB}.Release|x86.ActiveCfg = Release|Any CPU + {91E41EE3-687B-4CFD-94C4-028D09BACFAB}.Release|x86.Build.0 = Release|Any CPU + {4D411F2D-A97D-4485-A318-ED98B96B6CF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4D411F2D-A97D-4485-A318-ED98B96B6CF6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4D411F2D-A97D-4485-A318-ED98B96B6CF6}.Debug|x64.ActiveCfg = Debug|Any CPU + {4D411F2D-A97D-4485-A318-ED98B96B6CF6}.Debug|x64.Build.0 = Debug|Any CPU + {4D411F2D-A97D-4485-A318-ED98B96B6CF6}.Debug|x86.ActiveCfg = Debug|Any CPU + {4D411F2D-A97D-4485-A318-ED98B96B6CF6}.Debug|x86.Build.0 = Debug|Any CPU + {4D411F2D-A97D-4485-A318-ED98B96B6CF6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4D411F2D-A97D-4485-A318-ED98B96B6CF6}.Release|Any CPU.Build.0 = Release|Any CPU + {4D411F2D-A97D-4485-A318-ED98B96B6CF6}.Release|x64.ActiveCfg = Release|Any CPU + {4D411F2D-A97D-4485-A318-ED98B96B6CF6}.Release|x64.Build.0 = Release|Any CPU + {4D411F2D-A97D-4485-A318-ED98B96B6CF6}.Release|x86.ActiveCfg = Release|Any CPU + {4D411F2D-A97D-4485-A318-ED98B96B6CF6}.Release|x86.Build.0 = Release|Any CPU + {7F36D200-FE91-4761-B681-BEE091FB953F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7F36D200-FE91-4761-B681-BEE091FB953F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7F36D200-FE91-4761-B681-BEE091FB953F}.Debug|x64.ActiveCfg = Debug|Any CPU + {7F36D200-FE91-4761-B681-BEE091FB953F}.Debug|x64.Build.0 = Debug|Any CPU + {7F36D200-FE91-4761-B681-BEE091FB953F}.Debug|x86.ActiveCfg = Debug|Any CPU + {7F36D200-FE91-4761-B681-BEE091FB953F}.Debug|x86.Build.0 = Debug|Any CPU + {7F36D200-FE91-4761-B681-BEE091FB953F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7F36D200-FE91-4761-B681-BEE091FB953F}.Release|Any CPU.Build.0 = Release|Any CPU + {7F36D200-FE91-4761-B681-BEE091FB953F}.Release|x64.ActiveCfg = Release|Any CPU + {7F36D200-FE91-4761-B681-BEE091FB953F}.Release|x64.Build.0 = Release|Any CPU + {7F36D200-FE91-4761-B681-BEE091FB953F}.Release|x86.ActiveCfg = Release|Any CPU + {7F36D200-FE91-4761-B681-BEE091FB953F}.Release|x86.Build.0 = Release|Any CPU + {69CCB780-50CC-4E75-B794-932E44ACA80F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {69CCB780-50CC-4E75-B794-932E44ACA80F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {69CCB780-50CC-4E75-B794-932E44ACA80F}.Debug|x64.ActiveCfg = Debug|Any CPU + {69CCB780-50CC-4E75-B794-932E44ACA80F}.Debug|x64.Build.0 = Debug|Any CPU + {69CCB780-50CC-4E75-B794-932E44ACA80F}.Debug|x86.ActiveCfg = Debug|Any CPU + {69CCB780-50CC-4E75-B794-932E44ACA80F}.Debug|x86.Build.0 = Debug|Any CPU + {69CCB780-50CC-4E75-B794-932E44ACA80F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {69CCB780-50CC-4E75-B794-932E44ACA80F}.Release|Any CPU.Build.0 = Release|Any CPU + {69CCB780-50CC-4E75-B794-932E44ACA80F}.Release|x64.ActiveCfg = Release|Any CPU + {69CCB780-50CC-4E75-B794-932E44ACA80F}.Release|x64.Build.0 = Release|Any CPU + {69CCB780-50CC-4E75-B794-932E44ACA80F}.Release|x86.ActiveCfg = Release|Any CPU + {69CCB780-50CC-4E75-B794-932E44ACA80F}.Release|x86.Build.0 = Release|Any CPU + {C087F8AC-B438-4980-9A79-1E16D0CFB67A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C087F8AC-B438-4980-9A79-1E16D0CFB67A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C087F8AC-B438-4980-9A79-1E16D0CFB67A}.Debug|x64.ActiveCfg = Debug|Any CPU + {C087F8AC-B438-4980-9A79-1E16D0CFB67A}.Debug|x64.Build.0 = Debug|Any CPU + {C087F8AC-B438-4980-9A79-1E16D0CFB67A}.Debug|x86.ActiveCfg = Debug|Any CPU + {C087F8AC-B438-4980-9A79-1E16D0CFB67A}.Debug|x86.Build.0 = Debug|Any CPU + {C087F8AC-B438-4980-9A79-1E16D0CFB67A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C087F8AC-B438-4980-9A79-1E16D0CFB67A}.Release|Any CPU.Build.0 = Release|Any CPU + {C087F8AC-B438-4980-9A79-1E16D0CFB67A}.Release|x64.ActiveCfg = Release|Any CPU + {C087F8AC-B438-4980-9A79-1E16D0CFB67A}.Release|x64.Build.0 = Release|Any CPU + {C087F8AC-B438-4980-9A79-1E16D0CFB67A}.Release|x86.ActiveCfg = Release|Any CPU + {C087F8AC-B438-4980-9A79-1E16D0CFB67A}.Release|x86.Build.0 = Release|Any CPU + {E04E5F37-8F13-4000-ABA1-DAC0D795EA6A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E04E5F37-8F13-4000-ABA1-DAC0D795EA6A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E04E5F37-8F13-4000-ABA1-DAC0D795EA6A}.Debug|x64.ActiveCfg = Debug|Any CPU + {E04E5F37-8F13-4000-ABA1-DAC0D795EA6A}.Debug|x64.Build.0 = Debug|Any CPU + {E04E5F37-8F13-4000-ABA1-DAC0D795EA6A}.Debug|x86.ActiveCfg = Debug|Any CPU + {E04E5F37-8F13-4000-ABA1-DAC0D795EA6A}.Debug|x86.Build.0 = Debug|Any CPU + {E04E5F37-8F13-4000-ABA1-DAC0D795EA6A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E04E5F37-8F13-4000-ABA1-DAC0D795EA6A}.Release|Any CPU.Build.0 = Release|Any CPU + {E04E5F37-8F13-4000-ABA1-DAC0D795EA6A}.Release|x64.ActiveCfg = Release|Any CPU + {E04E5F37-8F13-4000-ABA1-DAC0D795EA6A}.Release|x64.Build.0 = Release|Any CPU + {E04E5F37-8F13-4000-ABA1-DAC0D795EA6A}.Release|x86.ActiveCfg = Release|Any CPU + {E04E5F37-8F13-4000-ABA1-DAC0D795EA6A}.Release|x86.Build.0 = Release|Any CPU + {0E6CF267-14FD-43D7-81CB-EE020FD1D106}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0E6CF267-14FD-43D7-81CB-EE020FD1D106}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0E6CF267-14FD-43D7-81CB-EE020FD1D106}.Debug|x64.ActiveCfg = Debug|Any CPU + {0E6CF267-14FD-43D7-81CB-EE020FD1D106}.Debug|x64.Build.0 = Debug|Any CPU + {0E6CF267-14FD-43D7-81CB-EE020FD1D106}.Debug|x86.ActiveCfg = Debug|Any CPU + {0E6CF267-14FD-43D7-81CB-EE020FD1D106}.Debug|x86.Build.0 = Debug|Any CPU + {0E6CF267-14FD-43D7-81CB-EE020FD1D106}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0E6CF267-14FD-43D7-81CB-EE020FD1D106}.Release|Any CPU.Build.0 = Release|Any CPU + {0E6CF267-14FD-43D7-81CB-EE020FD1D106}.Release|x64.ActiveCfg = Release|Any CPU + {0E6CF267-14FD-43D7-81CB-EE020FD1D106}.Release|x64.Build.0 = Release|Any CPU + {0E6CF267-14FD-43D7-81CB-EE020FD1D106}.Release|x86.ActiveCfg = Release|Any CPU + {0E6CF267-14FD-43D7-81CB-EE020FD1D106}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {93D00F03-02FF-4C2A-8917-6863D6E633D9} = {32C0F9AC-AF7D-4476-A269-99ACA000EF9F} + {14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB} = {32C0F9AC-AF7D-4476-A269-99ACA000EF9F} + {51F770AF-C0C6-4247-A358-82DF323F473D} = {32C0F9AC-AF7D-4476-A269-99ACA000EF9F} + {8A8E8B67-9351-471E-A502-AF7FAE596CB4} = {32C0F9AC-AF7D-4476-A269-99ACA000EF9F} + {5DE8D878-EEBD-44DF-A8FC-0B4866B807FB} = {32C0F9AC-AF7D-4476-A269-99ACA000EF9F} + {567DABFB-611E-4779-9F39-1D4A5B8F0247} = {32C0F9AC-AF7D-4476-A269-99ACA000EF9F} + {28AFDFEF-7597-4450-B999-87C22C24DCD8} = {32C0F9AC-AF7D-4476-A269-99ACA000EF9F} + {F80D5395-28E3-4C34-9662-A890A215DDA2} = {32C0F9AC-AF7D-4476-A269-99ACA000EF9F} + {7E15D295-B938-409C-98F6-A22ABC7F3005} = {AAAA5609-D8C1-401E-BB5C-724EFE3955FB} + {D632374A-70A1-4954-8F2D-C0B511A24426} = {FBC6F613-4E0B-4A90-8854-31887A796EEF} + {91E41EE3-687B-4CFD-94C4-028D09BACFAB} = {FBC6F613-4E0B-4A90-8854-31887A796EEF} + {4D411F2D-A97D-4485-A318-ED98B96B6CF6} = {FBC6F613-4E0B-4A90-8854-31887A796EEF} + {7F36D200-FE91-4761-B681-BEE091FB953F} = {FBC6F613-4E0B-4A90-8854-31887A796EEF} + {69CCB780-50CC-4E75-B794-932E44ACA80F} = {FBC6F613-4E0B-4A90-8854-31887A796EEF} + {C087F8AC-B438-4980-9A79-1E16D0CFB67A} = {FBC6F613-4E0B-4A90-8854-31887A796EEF} + {E04E5F37-8F13-4000-ABA1-DAC0D795EA6A} = {FBC6F613-4E0B-4A90-8854-31887A796EEF} + EndGlobalSection +EndGlobal diff --git a/LibMatrix/Extensions/CanonicalJsonSerializer.cs b/LibMatrix/Extensions/CanonicalJsonSerializer.cs
index 55a4b1a..ae535aa 100644 --- a/LibMatrix/Extensions/CanonicalJsonSerializer.cs +++ b/LibMatrix/Extensions/CanonicalJsonSerializer.cs
@@ -1,6 +1,7 @@ using System.Collections.Frozen; using System.Reflection; using System.Text.Json; +using System.Text.Json.Nodes; using System.Text.Json.Serialization.Metadata; using ArcaneLibs.Extensions; @@ -57,6 +58,14 @@ public static class CanonicalJsonSerializer { // public static String Serialize<TValue>(TValue value, JsonTypeInfo<TValue> jsonTypeInfo) => JsonSerializer.Serialize(value, jsonTypeInfo, _options); // public static String Serialize(Object value, JsonTypeInfo jsonTypeInfo) + public static byte[] SerializeToUtf8Bytes<T>(T value, JsonSerializerOptions? options = null) { + var newOptions = MergeOptions(null); + return JsonSerializer.SerializeToNode(value, options) // We want to allow passing custom converters for eg. double/float -> string here... + .SortProperties()! + .CanonicalizeNumbers()! + .ToJsonString(newOptions).AsBytes().ToArray(); + } + #endregion // ReSharper disable once UnusedType.Local diff --git a/LibMatrix/Extensions/MatrixHttpClient.Single.cs b/LibMatrix/Extensions/MatrixHttpClient.Single.cs
index baa4a2c..671566f 100644 --- a/LibMatrix/Extensions/MatrixHttpClient.Single.cs +++ b/LibMatrix/Extensions/MatrixHttpClient.Single.cs
@@ -101,22 +101,22 @@ public class MatrixHttpClient { responseMessage = await Client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken); } catch (Exception e) { - if (attempt >= 5) { - Console.WriteLine( - $"Failed to send request {request.Method} {BaseAddress}{request.RequestUri} ({Util.BytesToString(request.Content?.Headers.ContentLength ?? 0)}):\n{e}"); - throw; - } - - if (e is TaskCanceledException or TimeoutException or HttpRequestException) { - if (request.Method == HttpMethod.Get && !cancellationToken.IsCancellationRequested) { - await Task.Delay(Random.Shared.Next(500, 2500), cancellationToken); - request.ResetSendStatus(); - return await SendUnhandledAsync(request, cancellationToken, attempt + 1); - } - } - else if (!e.ToString().StartsWith("TypeError: NetworkError")) - Console.WriteLine( - $"Failed to send request {request.Method} {BaseAddress}{request.RequestUri} ({Util.BytesToString(request.Content?.Headers.ContentLength ?? 0)}):\n{e}"); + // if (attempt >= 5) { + // Console.WriteLine( + // $"Failed to send request {request.Method} {BaseAddress}{request.RequestUri} ({Util.BytesToString(request.Content?.Headers.ContentLength ?? 0)}):\n{e}"); + // throw; + // } + // + // if (e is TaskCanceledException or TimeoutException or HttpRequestException) { + // if (request.Method == HttpMethod.Get && !cancellationToken.IsCancellationRequested) { + // await Task.Delay(Random.Shared.Next(100, 1000), cancellationToken); + // request.ResetSendStatus(); + // return await SendUnhandledAsync(request, cancellationToken, attempt + 1); + // } + // } + // else if (!e.ToString().StartsWith("TypeError: NetworkError")) + // Console.WriteLine( + // $"Failed to send request {request.Method} {BaseAddress}{request.RequestUri} ({Util.BytesToString(request.Content?.Headers.ContentLength ?? 0)}):\n{e}"); throw; } @@ -286,9 +286,9 @@ public class MatrixHttpClient { return await SendAsync(request, cancellationToken); } - public async Task DeleteAsync(string url) { + public async Task<HttpResponseMessage> DeleteAsync(string url) { var request = new HttpRequestMessage(HttpMethod.Delete, url); - await SendAsync(request); + return await SendAsync(request); } public async Task<HttpResponseMessage> DeleteAsJsonAsync<T>(string url, T payload) { diff --git a/LibMatrix/Helpers/MessageBuilder.cs b/LibMatrix/Helpers/MessageBuilder.cs
index 5e2b1b7..6f55739 100644 --- a/LibMatrix/Helpers/MessageBuilder.cs +++ b/LibMatrix/Helpers/MessageBuilder.cs
@@ -37,6 +37,10 @@ public class MessageBuilder(string msgType = "m.text", string format = "org.matr return this; } + public static string GetColoredBody(string color, string body) { + return $"<font color=\"{color}\">{body}</font>"; + } + public MessageBuilder WithColoredBody(string color, string body) { Content.Body += body; Content.FormattedBody += $"<font color=\"{color}\">{body}</font>"; @@ -94,6 +98,16 @@ public class MessageBuilder(string msgType = "m.text", string format = "org.matr public MessageBuilder WithMention(string id, string? displayName = null) { Content.Body += $"@{displayName ?? id}"; Content.FormattedBody += $"<a href=\"https://matrix.to/#/{id}\">{displayName ?? id}</a>"; + if (id == "@room") { + Content.Mentions ??= new(); + Content.Mentions.Room = true; + } + else if (id.StartsWith('@')) { + Content.Mentions ??= new(); + Content.Mentions.Users ??= new(); + Content.Mentions.Users.Add(id); + } + return this; } diff --git a/LibMatrix/Helpers/RoomBuilder.cs b/LibMatrix/Helpers/RoomBuilder.cs new file mode 100644
index 0000000..bef7568 --- /dev/null +++ b/LibMatrix/Helpers/RoomBuilder.cs
@@ -0,0 +1,173 @@ +using System.Runtime.Intrinsics.X86; +using LibMatrix.EventTypes.Spec.State.RoomInfo; +using LibMatrix.Homeservers; +using LibMatrix.Responses; +using LibMatrix.RoomTypes; + +namespace LibMatrix.Helpers; + +public class RoomBuilder { + public string? Type { get; set; } + public string Version { get; set; } = "11"; + public RoomNameEventContent Name { get; set; } = new(); + public RoomTopicEventContent Topic { get; set; } = new(); + public RoomAvatarEventContent Avatar { get; set; } = new(); + public RoomCanonicalAliasEventContent CanonicalAlias { get; set; } = new(); + public string AliasLocalPart { get; set; } = string.Empty; + public bool IsFederatable { get; set; } = true; + public long OwnPowerLevel { get; set; } = MatrixConstants.MaxSafeJsonInteger; + + public RoomJoinRulesEventContent JoinRules { get; set; } = new() { + JoinRule = RoomJoinRulesEventContent.JoinRules.Public + }; + + public RoomHistoryVisibilityEventContent HistoryVisibility { get; set; } = new() { + HistoryVisibility = RoomHistoryVisibilityEventContent.HistoryVisibilityTypes.Shared + }; + + /// <summary> + /// State events to be sent *before* room access is configured. Keep this small! + /// </summary> + public List<StateEvent> ImportantState { get; set; } = []; + + /// <summary> + /// State events to be sent *after* room access is configured, but before invites are sent. + /// </summary> + public List<StateEvent> InitialState { get; set; } = []; + + /// <summary> + /// Users to invite, with optional reason + /// </summary> + public Dictionary<string, string?> Invites { get; set; } = new(); + + public RoomPowerLevelEventContent PowerLevels { get; init; } = new() { + EventsDefault = 0, + UsersDefault = 0, + Kick = 50, + Invite = 50, + Ban = 50, + Redact = 50, + StateDefault = 50, + NotificationsPl = new() { + Room = 50 + }, + Users = [], + Events = new Dictionary<string, long> { + { RoomAvatarEventContent.EventId, 50 }, + { RoomCanonicalAliasEventContent.EventId, 50 }, + { RoomEncryptionEventContent.EventId, 100 }, + { RoomHistoryVisibilityEventContent.EventId, 100 }, + { RoomNameEventContent.EventId, 50 }, + { RoomPowerLevelEventContent.EventId, 100 }, + { RoomServerAclEventContent.EventId, 100 }, + { RoomTombstoneEventContent.EventId, 100 }, + { RoomPolicyServerEventContent.EventId, 100 } + } + }; + + public async Task<GenericRoom> Create(AuthenticatedHomeserverGeneric homeserver) { + var crq = new CreateRoomRequest() { + PowerLevelContentOverride = new() { + EventsDefault = 1000000, + UsersDefault = 1000000, + Kick = 1000000, + Invite = 1000000, + Ban = 1000000, + Redact = 1000000, + StateDefault = 1000000, + NotificationsPl = new() { + Room = 1000000 + }, + Users = new Dictionary<string, long>() { + { homeserver.WhoAmI.UserId, MatrixConstants.MaxSafeJsonInteger } + }, + Events = new Dictionary<string, long> { + { RoomAvatarEventContent.EventId, 1000000 }, + { RoomCanonicalAliasEventContent.EventId, 1000000 }, + { RoomEncryptionEventContent.EventId, 1000000 }, + { RoomHistoryVisibilityEventContent.EventId, 1000000 }, + { RoomNameEventContent.EventId, 1000000 }, + { RoomPowerLevelEventContent.EventId, 1000000 }, + { RoomServerAclEventContent.EventId, 1000000 }, + { RoomTombstoneEventContent.EventId, 1000000 }, + { RoomPolicyServerEventContent.EventId, 1000000 } + } + }, + Visibility = "private", + RoomVersion = Version + }; + + if (!string.IsNullOrWhiteSpace(Type)) + crq.CreationContent.Add("type", Type); + + if (!IsFederatable) + crq.CreationContent.Add("m.federate", false); + + var room = await homeserver.CreateRoom(crq); + + await SetBasicRoomInfoAsync(room); + await SetStatesAsync(room, ImportantState); + await SetAccessAsync(room); + await SetStatesAsync(room, InitialState); + await SendInvites(room); + + return room; + } + + private async Task SendInvites(GenericRoom room) { + if (Invites.Count == 0) return; + + var inviteTasks = Invites.Select(async kvp => { + try { + await room.InviteUserAsync(kvp.Key, kvp.Value); + } + catch (MatrixException e) { + Console.Error.WriteLine("Failed to invite {0} to {1}: {2}", kvp.Key, room.RoomId, e.Message); + } + }); + + await Task.WhenAll(inviteTasks); + } + + private async Task SetStatesAsync(GenericRoom room, List<StateEvent> state) { + foreach (var ev in state) { + await (string.IsNullOrWhiteSpace(ev.StateKey) + ? room.SendStateEventAsync(ev.Type, ev.RawContent) + : room.SendStateEventAsync(ev.Type, ev.StateKey, ev.RawContent)); + } + } + + private async Task SetBasicRoomInfoAsync(GenericRoom room) { + if (!string.IsNullOrWhiteSpace(Name.Name)) + await room.SendStateEventAsync(RoomNameEventContent.EventId, Name); + + if (!string.IsNullOrWhiteSpace(Topic.Topic)) + await room.SendStateEventAsync(RoomTopicEventContent.EventId, Topic); + + if (!string.IsNullOrWhiteSpace(Avatar.Url)) + await room.SendStateEventAsync(RoomAvatarEventContent.EventId, Avatar); + + if (!string.IsNullOrWhiteSpace(AliasLocalPart)) + CanonicalAlias.Alias = $"#{AliasLocalPart}:{room.Homeserver.ServerName}"; + + if (!string.IsNullOrWhiteSpace(CanonicalAlias.Alias)) { + await room.Homeserver.SetRoomAliasAsync(CanonicalAlias.Alias!, room.RoomId); + await room.SendStateEventAsync(RoomCanonicalAliasEventContent.EventId, CanonicalAlias); + } + } + + private async Task SetAccessAsync(GenericRoom room) { + PowerLevels.Users![room.Homeserver.WhoAmI.UserId] = OwnPowerLevel; + await room.SendStateEventAsync(RoomPowerLevelEventContent.EventId, PowerLevels); + + if (!string.IsNullOrWhiteSpace(HistoryVisibility.HistoryVisibility)) + await room.SendStateEventAsync(RoomHistoryVisibilityEventContent.EventId, HistoryVisibility); + + if (!string.IsNullOrWhiteSpace(JoinRules.JoinRuleValue)) + await room.SendStateEventAsync(RoomJoinRulesEventContent.EventId, JoinRules); + } +} + +public class MatrixConstants { + public const long MaxSafeJsonInteger = 9007199254740991L; // 2^53 - 1 +} \ No newline at end of file diff --git a/LibMatrix/Helpers/SyncHelper.cs b/LibMatrix/Helpers/SyncHelper.cs
index 6f2cacc..c8e2928 100644 --- a/LibMatrix/Helpers/SyncHelper.cs +++ b/LibMatrix/Helpers/SyncHelper.cs
@@ -15,7 +15,7 @@ using Microsoft.Extensions.Logging; namespace LibMatrix.Helpers; public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logger = null, IStorageProvider? storageProvider = null) { - private readonly Func<SyncResponse?, Task<SyncResponse?>> _msc4222EmulationSyncProcessor = new Msc4222EmulationSyncProcessor(homeserver).EmulateMsc4222; + private readonly Func<SyncResponse?, Task<SyncResponse?>> _msc4222EmulationSyncProcessor = new Msc4222EmulationSyncProcessor(homeserver, logger).EmulateMsc4222; private SyncFilter? _filter; private string? _namedFilterName; @@ -25,7 +25,7 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logg public string? Since { get; set; } public int Timeout { get; set; } = 30000; public string? SetPresence { get; set; } - + /// <summary> /// Disabling this uses a technically slower code path, useful for checking whether delay comes from waiting for server or deserialising responses /// </summary> @@ -37,11 +37,11 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logg field = value; if (value) { AsyncSyncPreprocessors.Add(_msc4222EmulationSyncProcessor); - Console.WriteLine($"Added MSC4222 emulation sync processor"); + logger?.LogInformation($"Added MSC4222 emulation sync processor"); } else { AsyncSyncPreprocessors.Remove(_msc4222EmulationSyncProcessor); - Console.WriteLine($"Removed MSC4222 emulation sync processor"); + logger?.LogInformation($"Removed MSC4222 emulation sync processor"); } } } = false; @@ -121,7 +121,7 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logg } if (storageProvider is null) { - var res = await SyncAsyncInternal(cancellationToken, noDelay); + var res = await SyncAsyncInternal(cancellationToken, noDelay); if (res is null) return null; if (UseMsc4222StateAfter) res.Msc4222Method = SyncResponse.Msc4222SyncType.Server; @@ -186,13 +186,12 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logg else { var httpResp = await homeserver.ClientHttpClient.GetAsync(url, cancellationToken ?? CancellationToken.None); if (httpResp is null) throw new NullReferenceException("Failed to send HTTP request"); - logger?.LogInformation("Got sync response: {} bytes, {} elapsed", httpResp.GetContentLength(), sw.Elapsed); + var receivedTime = sw.Elapsed; var deserializeSw = Stopwatch.StartNew(); - // var jsonResp = await httpResp.Content.ReadFromJsonAsync<JsonObject>(cancellationToken: cancellationToken ?? CancellationToken.None); - // var resp = jsonResp.Deserialize<SyncResponse>(); resp = await httpResp.Content.ReadFromJsonAsync(cancellationToken: cancellationToken ?? CancellationToken.None, jsonTypeInfo: SyncResponseSerializerContext.Default.SyncResponse); - logger?.LogInformation("Deserialized sync response: {} bytes, {} elapsed, {} total", httpResp.GetContentLength(), deserializeSw.Elapsed, sw.Elapsed); + logger?.LogInformation("Deserialized sync response: {} bytes, {} response time, {} deserialize time, {} total", httpResp.GetContentLength(), receivedTime, + deserializeSw.Elapsed, sw.Elapsed); } var timeToWait = MinimumDelay.Subtract(sw.Elapsed); diff --git a/LibMatrix/Helpers/SyncProcessors/Msc4222EmulationSyncProcessor.cs b/LibMatrix/Helpers/SyncProcessors/Msc4222EmulationSyncProcessor.cs
index 6cb42ca..e34b5cf 100644 --- a/LibMatrix/Helpers/SyncProcessors/Msc4222EmulationSyncProcessor.cs +++ b/LibMatrix/Helpers/SyncProcessors/Msc4222EmulationSyncProcessor.cs
@@ -1,11 +1,13 @@ using System.Diagnostics; +using System.Timers; using ArcaneLibs.Extensions; using LibMatrix.Homeservers; using LibMatrix.Responses; +using Microsoft.Extensions.Logging; namespace LibMatrix.Helpers.SyncProcessors; -public class Msc4222EmulationSyncProcessor(AuthenticatedHomeserverGeneric homeserver) { +public class Msc4222EmulationSyncProcessor(AuthenticatedHomeserverGeneric homeserver, ILogger? logger) { private static bool StateEventsMatch(StateEventResponse a, StateEventResponse b) { return a.Type == b.Type && a.StateKey == b.StateKey; } @@ -22,12 +24,13 @@ public class Msc4222EmulationSyncProcessor(AuthenticatedHomeserverGeneric homese resp.Rooms.Join?.Any(x => x.Value.StateAfter is { Events.Count: > 0 }) == true || resp.Rooms.Leave?.Any(x => x.Value.StateAfter is { Events.Count: > 0 }) == true ) { - Console.WriteLine($"Msc4222EmulationSyncProcessor.EmulateMsc4222 determined that no emulation is needed in {sw.Elapsed}"); + logger?.Log(sw.ElapsedMilliseconds > 100 ? LogLevel.Warning : LogLevel.Debug, + "Msc4222EmulationSyncProcessor.EmulateMsc4222 determined that no emulation is needed in {elapsed}", sw.Elapsed); return resp; } resp = await EmulateMsc4222Internal(resp, sw); - + return SimpleSyncProcessors.FillRoomIds(resp); } @@ -49,7 +52,10 @@ public class Msc4222EmulationSyncProcessor(AuthenticatedHomeserverGeneric homese } } - Console.WriteLine($"Msc4222EmulationSyncProcessor.EmulateMsc4222 processed {resp.Rooms?.Join?.Count}/{resp.Rooms?.Leave?.Count} rooms in {sw.Elapsed} (modified: {modified})"); + logger?.Log(sw.ElapsedMilliseconds > 100 ? LogLevel.Warning : LogLevel.Debug, + "Msc4222EmulationSyncProcessor.EmulateMsc4222 processed {joinCount}/{leaveCount} rooms in {elapsed} (modified: {modified})", + resp.Rooms?.Join?.Count ?? 0, resp.Rooms?.Leave?.Count ?? 0, sw.Elapsed, modified); + if (modified) resp.Msc4222Method = SyncResponse.Msc4222SyncType.Emulated; @@ -90,7 +96,7 @@ public class Msc4222EmulationSyncProcessor(AuthenticatedHomeserverGeneric homese } } catch (Exception e) { - Console.WriteLine($"Msc4222Emulation: Failed to get timeline for room {roomId}, state may be incomplete!\n{e}"); + logger?.LogWarning("Msc4222Emulation: Failed to get timeline for room {roomId}, state may be incomplete!\n{exception}", roomId, e); } } @@ -103,12 +109,12 @@ public class Msc4222EmulationSyncProcessor(AuthenticatedHomeserverGeneric homese // .Join(oldState, x => (x.Type, x.StateKey), y => (y.Type, y.StateKey), (x, y) => x) .IntersectBy(oldState.Select(s => (s.Type, s.StateKey)), s => (s.Type, s.StateKey)) .ToList(); - + data.State = null; return true; } catch (Exception e) { - Console.WriteLine($"Msc4222Emulation: Failed to get full state for room {roomId}, state may be incomplete!\n{e}"); + logger?.LogWarning("Msc4222Emulation: Failed to get full state for room {roomId}, state may be incomplete!\n{exception}", roomId, e); } var tasks = oldState @@ -117,7 +123,8 @@ public class Msc4222EmulationSyncProcessor(AuthenticatedHomeserverGeneric homese return await room.GetStateEventAsync(oldEvt.Type, oldEvt.StateKey!); } catch (Exception e) { - Console.WriteLine($"Msc4222Emulation: Failed to get state event {oldEvt.Type}/{oldEvt.StateKey} for room {roomId}, state may be incomplete!\n{e}"); + logger?.LogWarning("Msc4222Emulation: Failed to get state event {type}/{stateKey} for room {roomId}, state may be incomplete!\n{exception}", + oldEvt.Type, oldEvt.StateKey, roomId, e); return oldEvt; } }); @@ -150,7 +157,7 @@ public class Msc4222EmulationSyncProcessor(AuthenticatedHomeserverGeneric homese return true; } catch (Exception e) { - Console.WriteLine($"Msc4222Emulation: Failed to get full state for room {roomId}, state may be incomplete!\n{e}"); + logger?.LogWarning("Msc4222Emulation: Failed to get full state for room {roomId}, state may be incomplete!\n{exception}", roomId, e); } var oldState = new List<StateEventResponse>(); @@ -173,7 +180,7 @@ public class Msc4222EmulationSyncProcessor(AuthenticatedHomeserverGeneric homese } } catch (Exception e) { - Console.WriteLine($"Msc4222Emulation: Failed to get timeline for room {roomId}, state may be incomplete!\n{e}"); + logger?.LogWarning("Msc4222Emulation: Failed to get timeline for room {roomId}, state may be incomplete!\n{exception}", roomId, e); } } @@ -185,7 +192,8 @@ public class Msc4222EmulationSyncProcessor(AuthenticatedHomeserverGeneric homese return await room.GetStateEventAsync(oldEvt.Type, oldEvt.StateKey!); } catch (Exception e) { - Console.WriteLine($"Msc4222Emulation: Failed to get state event {oldEvt.Type}/{oldEvt.StateKey} for room {roomId}, state may be incomplete!\n{e}"); + logger?.LogWarning("Msc4222Emulation: Failed to get state event {type}/{stateKey} for room {roomId}, state may be incomplete!\n{exception}", + oldEvt.Type, oldEvt.StateKey, roomId, e); return oldEvt; } }); diff --git a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs
index 55899de..5fd3311 100644 --- a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs +++ b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs
@@ -578,4 +578,41 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeserver { [JsonPropertyName("capabilities")] public Dictionary<string, object>? Capabilities { get; set; } } + +#region Room Directory/aliases + + public async Task SetRoomAliasAsync(string roomAlias, string roomId) { + var resp = await ClientHttpClient.PutAsJsonAsync($"/_matrix/client/v3/directory/room/{HttpUtility.UrlEncode(roomAlias)}", new RoomIdResponse() { + RoomId = roomId + }); + if (!resp.IsSuccessStatusCode) { + Console.WriteLine($"Failed to set room alias: {await resp.Content.ReadAsStringAsync()}"); + throw new InvalidDataException($"Failed to set room alias: {await resp.Content.ReadAsStringAsync()}"); + } + } + + public async Task DeleteRoomAliasAsync(string roomAlias) { + var resp = await ClientHttpClient.DeleteAsync("/_matrix/client/v3/directory/room/" + HttpUtility.UrlEncode(roomAlias)); + if (!resp.IsSuccessStatusCode) { + Console.WriteLine($"Failed to set room alias: {await resp.Content.ReadAsStringAsync()}"); + throw new InvalidDataException($"Failed to set room alias: {await resp.Content.ReadAsStringAsync()}"); + } + } + + public async Task<RoomAliasesResponse> GetLocalRoomAliasesAsync(string roomId) { + var resp = await ClientHttpClient.GetAsync($"/_matrix/client/v3/rooms/{HttpUtility.UrlEncode(roomId)}/aliases"); + if (!resp.IsSuccessStatusCode) { + Console.WriteLine($"Failed to get room aliases: {await resp.Content.ReadAsStringAsync()}"); + throw new InvalidDataException($"Failed to get room aliases: {await resp.Content.ReadAsStringAsync()}"); + } + + return await resp.Content.ReadFromJsonAsync<RoomAliasesResponse>() ?? throw new Exception("Failed to get room aliases?"); + } + + public class RoomAliasesResponse { + [JsonPropertyName("aliases")] + public required List<string> Aliases { get; set; } + } + +#endregion } \ No newline at end of file diff --git a/LibMatrix/Homeservers/FederationClient.cs b/LibMatrix/Homeservers/FederationClient.cs
index 617b737..a2cb12d 100644 --- a/LibMatrix/Homeservers/FederationClient.cs +++ b/LibMatrix/Homeservers/FederationClient.cs
@@ -1,6 +1,7 @@ using System.Text.Json.Serialization; using LibMatrix.Extensions; using LibMatrix.Services; +using Microsoft.VisualBasic.CompilerServices; namespace LibMatrix.Homeservers; @@ -17,6 +18,70 @@ public class FederationClient { public HomeserverResolverService.WellKnownUris WellKnownUris { get; set; } public async Task<ServerVersionResponse> GetServerVersionAsync() => await HttpClient.GetFromJsonAsync<ServerVersionResponse>("/_matrix/federation/v1/version"); + public async Task<ServerKeysResponse> GetServerKeysAsync() => await HttpClient.GetFromJsonAsync<ServerKeysResponse>("/_matrix/key/v2/server"); +} + +public class ServerKeysResponse { + [JsonPropertyName("server_name")] + public string ServerName { get; set; } + + [JsonPropertyName("valid_until_ts")] + public ulong ValidUntilTs { get; set; } + + [JsonIgnore] + public DateTime ValidUntil { + get => DateTimeOffset.FromUnixTimeMilliseconds((long)ValidUntilTs).DateTime; + set => ValidUntilTs = (ulong)new DateTimeOffset(value).ToUnixTimeMilliseconds(); + } + + [JsonPropertyName("verify_keys")] + public Dictionary<string, CurrentVerifyKey> VerifyKeys { get; set; } = new(); + + [JsonIgnore] + public Dictionary<VersionedKeyId, CurrentVerifyKey> VerifyKeysById { + get => VerifyKeys.ToDictionary(key => (VersionedKeyId)key.Key, key => key.Value); + set => VerifyKeys = value.ToDictionary(key => (string)key.Key, key => key.Value); + } + + [JsonPropertyName("old_verify_keys")] + public Dictionary<string, ExpiredVerifyKey> OldVerifyKeys { get; set; } = new(); + + [JsonIgnore] + public Dictionary<VersionedKeyId, ExpiredVerifyKey> OldVerifyKeysById { + get => OldVerifyKeys.ToDictionary(key => (VersionedKeyId)key.Key, key => key.Value); + set => OldVerifyKeys = value.ToDictionary(key => (string)key.Key, key => key.Value); + } + + public class VersionedKeyId { + public required string Algorithm { get; set; } + public required string KeyId { get; set; } + + public static implicit operator VersionedKeyId(string key) { + var parts = key.Split(':', 2); + if (parts.Length != 2) throw new ArgumentException("Invalid key format. Expected 'algorithm:keyId'.", nameof(key)); + return new VersionedKeyId { Algorithm = parts[0], KeyId = parts[1] }; + } + + public static implicit operator string(VersionedKeyId key) => $"{key.Algorithm}:{key.KeyId}"; + public static implicit operator (string, string)(VersionedKeyId key) => (key.Algorithm, key.KeyId); + public static implicit operator VersionedKeyId((string algorithm, string keyId) key) => (key.algorithm, key.keyId); + } + + public class CurrentVerifyKey { + [JsonPropertyName("key")] + public string Key { get; set; } + } + + public class ExpiredVerifyKey : CurrentVerifyKey { + [JsonPropertyName("expired_ts")] + public ulong ExpiredTs { get; set; } + + [JsonIgnore] + public DateTime Expired { + get => DateTimeOffset.FromUnixTimeMilliseconds((long)ExpiredTs).DateTime; + set => ExpiredTs = (ulong)new DateTimeOffset(value).ToUnixTimeMilliseconds(); + } + } } public class ServerVersionResponse { diff --git a/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Filters/SynapseAdminLocalUserQueryFilter.cs b/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Filters/SynapseAdminLocalUserQueryFilter.cs
index 62b291b..5a4acf7 100644 --- a/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Filters/SynapseAdminLocalUserQueryFilter.cs +++ b/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Filters/SynapseAdminLocalUserQueryFilter.cs
@@ -1,27 +1,5 @@ namespace LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Filters; public class SynapseAdminLocalUserQueryFilter { - public string UserIdContains { get; set; } = ""; - public string NameContains { get; set; } = ""; - public string CanonicalAliasContains { get; set; } = ""; - public string VersionContains { get; set; } = ""; - public string CreatorContains { get; set; } = ""; - public string EncryptionContains { get; set; } = ""; - public string JoinRulesContains { get; set; } = ""; - public string GuestAccessContains { get; set; } = ""; - public string HistoryVisibilityContains { get; set; } = ""; - public bool Federatable { get; set; } = true; - public bool Public { get; set; } = true; - - public int JoinedMembersGreaterThan { get; set; } - public int JoinedMembersLessThan { get; set; } = int.MaxValue; - - public int JoinedLocalMembersGreaterThan { get; set; } - public int JoinedLocalMembersLessThan { get; set; } = int.MaxValue; - public int StateEventsGreaterThan { get; set; } - public int StateEventsLessThan { get; set; } = int.MaxValue; - - public bool CheckFederation { get; set; } - public bool CheckPublic { get; set; } } \ No newline at end of file diff --git a/LibMatrix/Homeservers/ImplementationDetails/Synapse/SynapseAdminApiClient.cs b/LibMatrix/Homeservers/ImplementationDetails/Synapse/SynapseAdminApiClient.cs
index 777c04a..cee3d8d 100644 --- a/LibMatrix/Homeservers/ImplementationDetails/Synapse/SynapseAdminApiClient.cs +++ b/LibMatrix/Homeservers/ImplementationDetails/Synapse/SynapseAdminApiClient.cs
@@ -183,6 +183,7 @@ public class SynapseAdminApiClient(AuthenticatedHomeserverSynapse authenticatedH #region Users public async IAsyncEnumerable<SynapseAdminUserListResult.SynapseAdminUserListResultUser> SearchUsersAsync(int limit = int.MaxValue, int chunkLimit = 250, + string orderBy = "name", string dir = "f", SynapseAdminLocalUserQueryFilter? localFilter = null) { // TODO: implement filters string? from = null; @@ -190,6 +191,9 @@ public class SynapseAdminApiClient(AuthenticatedHomeserverSynapse authenticatedH var url = new Uri("/_synapse/admin/v3/users", UriKind.Relative); url = url.AddQuery("limit", Math.Min(limit, chunkLimit).ToString()); if (!string.IsNullOrWhiteSpace(from)) url = url.AddQuery("from", from); + if (!string.IsNullOrWhiteSpace(orderBy)) url = url.AddQuery("order_by", orderBy); + if (!string.IsNullOrWhiteSpace(dir)) url = url.AddQuery("dir", dir); + Console.WriteLine($"--- ADMIN Querying User List with URL: {url} ---"); // TODO: implement URI methods in http client var res = await authenticatedHomeserver.ClientHttpClient.GetFromJsonAsync<SynapseAdminUserListResult>(url.ToString()); diff --git a/LibMatrix/Responses/CreateRoomRequest.cs b/LibMatrix/Responses/CreateRoomRequest.cs
index d9a6acd..6933622 100644 --- a/LibMatrix/Responses/CreateRoomRequest.cs +++ b/LibMatrix/Responses/CreateRoomRequest.cs
@@ -47,6 +47,8 @@ public class CreateRoomRequest { [JsonPropertyName("invite")] public List<string>? Invite { get; set; } + public string? RoomVersion { get; set; } + /// <summary> /// For use only when you can't use the CreationContent property /// </summary> diff --git a/LibMatrix/EventIdResponse.cs b/LibMatrix/Responses/EventIdResponse.cs
index 6a04229..9e23210 100644 --- a/LibMatrix/EventIdResponse.cs +++ b/LibMatrix/Responses/EventIdResponse.cs
@@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace LibMatrix; +namespace LibMatrix.Responses; public class EventIdResponse { [JsonPropertyName("event_id")] diff --git a/LibMatrix/Responses/LoginResponse.cs b/LibMatrix/Responses/LoginResponse.cs
index 2f78932..1944276 100644 --- a/LibMatrix/Responses/LoginResponse.cs +++ b/LibMatrix/Responses/LoginResponse.cs
@@ -19,11 +19,6 @@ public class LoginResponse { [JsonPropertyName("user_id")] public string UserId { get; set; } - - // public async Task<AuthenticatedHomeserverGeneric> GetAuthenticatedHomeserver(string? proxy = null) { - // var urls = await new HomeserverResolverService().ResolveHomeserverFromWellKnown(Homeserver); - // await AuthenticatedHomeserverGeneric.Create<AuthenticatedHomeserverGeneric>(Homeserver, AccessToken, proxy); - // } } public class LoginRequest { diff --git a/LibMatrix/MessagesResponse.cs b/LibMatrix/Responses/MessagesResponse.cs
index 526da74..4912add 100644 --- a/LibMatrix/MessagesResponse.cs +++ b/LibMatrix/Responses/MessagesResponse.cs
@@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace LibMatrix; +namespace LibMatrix.Responses; public class MessagesResponse { [JsonPropertyName("start")] diff --git a/LibMatrix/UserIdAndReason.cs b/LibMatrix/Responses/UserIdAndReason.cs
index 99c9eaf..176cf7c 100644 --- a/LibMatrix/UserIdAndReason.cs +++ b/LibMatrix/Responses/UserIdAndReason.cs
@@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace LibMatrix; +namespace LibMatrix.Responses; internal class UserIdAndReason(string userId = null!, string reason = null!) { [JsonPropertyName("user_id")] diff --git a/LibMatrix/WhoAmIResponse.cs b/LibMatrix/Responses/WhoAmIResponse.cs
index 10fff35..db47152 100644 --- a/LibMatrix/WhoAmIResponse.cs +++ b/LibMatrix/Responses/WhoAmIResponse.cs
@@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace LibMatrix; +namespace LibMatrix.Responses; public class WhoAmIResponse { [JsonPropertyName("user_id")] diff --git a/LibMatrix/RoomTypes/GenericRoom.cs b/LibMatrix/RoomTypes/GenericRoom.cs
index bc1bc90..fd4db4d 100644 --- a/LibMatrix/RoomTypes/GenericRoom.cs +++ b/LibMatrix/RoomTypes/GenericRoom.cs
@@ -1,6 +1,5 @@ using System.Collections.Frozen; using System.Net.Http.Json; -using System.Security.Cryptography; using System.Text.Json; using System.Text.Json.Nodes; using System.Text.Json.Serialization; @@ -12,6 +11,7 @@ using LibMatrix.EventTypes.Spec.State.RoomInfo; using LibMatrix.Filters; using LibMatrix.Helpers; using LibMatrix.Homeservers; +using LibMatrix.Responses; namespace LibMatrix.RoomTypes; @@ -461,8 +461,8 @@ public class GenericRoom { } } - public Task<StateEventResponse> GetEventAsync(string eventId) => - Homeserver.ClientHttpClient.GetFromJsonAsync<StateEventResponse>($"/_matrix/client/v3/rooms/{RoomId}/event/{eventId}"); + public Task<StateEventResponse> GetEventAsync(string eventId, bool includeUnredactedContent = false) => + Homeserver.ClientHttpClient.GetFromJsonAsync<StateEventResponse>($"/_matrix/client/v3/rooms/{RoomId}/event/{eventId}?fi.mau.msc2815.include_unredacted_content={includeUnredactedContent}"); public async Task<EventIdResponse> RedactEventAsync(string eventToRedact, string? reason = null) { var data = new { reason }; diff --git a/LibMatrix/RoomTypes/SpaceRoom.cs b/LibMatrix/RoomTypes/SpaceRoom.cs
index 0c74be5..96abd77 100644 --- a/LibMatrix/RoomTypes/SpaceRoom.cs +++ b/LibMatrix/RoomTypes/SpaceRoom.cs
@@ -1,5 +1,6 @@ using ArcaneLibs.Extensions; using LibMatrix.Homeservers; +using LibMatrix.Responses; namespace LibMatrix.RoomTypes; diff --git a/LibMatrix/Services/HomeserverResolverService.cs b/LibMatrix/Services/HomeserverResolverService.cs
index 53cd2dd..94a3826 100644 --- a/LibMatrix/Services/HomeserverResolverService.cs +++ b/LibMatrix/Services/HomeserverResolverService.cs
@@ -44,7 +44,17 @@ public class HomeserverResolverService { return res; }); } - + + private async Task<T?> GetFromJsonAsync<T>(string url) { + try { + return await _httpClient.GetFromJsonAsync<T>(url); + } + catch (Exception e) { + _logger.LogWarning(e, "Failed to get JSON from {url}", url); + return default; + } + } + private async Task<string?> _tryResolveClientEndpoint(string homeserver) { ArgumentNullException.ThrowIfNull(homeserver); _logger.LogTrace("Resolving client well-known: {homeserver}", homeserver); @@ -52,14 +62,20 @@ public class HomeserverResolverService { homeserver = homeserver.TrimEnd('/'); // check if homeserver has a client well-known if (homeserver.StartsWith("https://")) { - clientWellKnown = await _httpClient.TryGetFromJsonAsync<ClientWellKnown>($"{homeserver}/.well-known/matrix/client"); + clientWellKnown = await GetFromJsonAsync<ClientWellKnown>($"{homeserver}/.well-known/matrix/client"); + + if (clientWellKnown is null && await MatrixHttpClient.CheckSuccessStatus($"{homeserver}/_matrix/client/versions")) + return homeserver; } else if (homeserver.StartsWith("http://")) { - clientWellKnown = await _httpClient.TryGetFromJsonAsync<ClientWellKnown>($"{homeserver}/.well-known/matrix/client"); + clientWellKnown = await GetFromJsonAsync<ClientWellKnown>($"{homeserver}/.well-known/matrix/client"); + + if (clientWellKnown is null && await MatrixHttpClient.CheckSuccessStatus($"{homeserver}/_matrix/client/versions")) + return homeserver; } else { - clientWellKnown ??= await _httpClient.TryGetFromJsonAsync<ClientWellKnown>($"https://{homeserver}/.well-known/matrix/client"); - clientWellKnown ??= await _httpClient.TryGetFromJsonAsync<ClientWellKnown>($"http://{homeserver}/.well-known/matrix/client"); + clientWellKnown ??= await GetFromJsonAsync<ClientWellKnown>($"https://{homeserver}/.well-known/matrix/client"); + clientWellKnown ??= await GetFromJsonAsync<ClientWellKnown>($"http://{homeserver}/.well-known/matrix/client"); if (clientWellKnown is null) { if (await MatrixHttpClient.CheckSuccessStatus($"https://{homeserver}/_matrix/client/versions")) @@ -84,14 +100,14 @@ public class HomeserverResolverService { homeserver = homeserver.TrimEnd('/'); // check if homeserver has a server well-known if (homeserver.StartsWith("https://")) { - serverWellKnown = await _httpClient.TryGetFromJsonAsync<ServerWellKnown>($"{homeserver}/.well-known/matrix/server"); + serverWellKnown = await GetFromJsonAsync<ServerWellKnown>($"{homeserver}/.well-known/matrix/server"); } else if (homeserver.StartsWith("http://")) { - serverWellKnown = await _httpClient.TryGetFromJsonAsync<ServerWellKnown>($"{homeserver}/.well-known/matrix/server"); + serverWellKnown = await GetFromJsonAsync<ServerWellKnown>($"{homeserver}/.well-known/matrix/server"); } else { - serverWellKnown ??= await _httpClient.TryGetFromJsonAsync<ServerWellKnown>($"https://{homeserver}/.well-known/matrix/server"); - serverWellKnown ??= await _httpClient.TryGetFromJsonAsync<ServerWellKnown>($"http://{homeserver}/.well-known/matrix/server"); + serverWellKnown ??= await GetFromJsonAsync<ServerWellKnown>($"https://{homeserver}/.well-known/matrix/server"); + serverWellKnown ??= await GetFromJsonAsync<ServerWellKnown>($"http://{homeserver}/.well-known/matrix/server"); } _logger.LogInformation("Server well-known for {hs}: {json}", homeserver, serverWellKnown?.ToJson() ?? "null"); diff --git a/LibMatrix/StateEvent.cs b/LibMatrix/StateEvent.cs
index e2ac87e..af25805 100644 --- a/LibMatrix/StateEvent.cs +++ b/LibMatrix/StateEvent.cs
@@ -121,7 +121,16 @@ public class StateEvent { public string InternalContentTypeName => TypedContent?.GetType().Name ?? "null"; public static bool TypeKeyPairMatches(StateEventResponse x, StateEventResponse y) => x.Type == y.Type && x.StateKey == y.StateKey; - public static bool Equals(StateEventResponse x, StateEventResponse y) => x.Type == y.Type && x.StateKey == y.StateKey && x.RawContent.Equals(y.RawContent); + public static bool Equals(StateEventResponse x, StateEventResponse y) => x.Type == y.Type && x.StateKey == y.StateKey && x.EventId == y.EventId; + + /// <summary> + /// Compares two state events for deep equality, including type, state key, and raw content. + /// If you trust the server, use Equals instead, as that compares by event ID instead of raw content. + /// </summary> + /// <param name="x"></param> + /// <param name="y"></param> + /// <returns></returns> + public static bool DeepEquals(StateEventResponse x, StateEventResponse y) => x.Type == y.Type && x.StateKey == y.StateKey && JsonNode.DeepEquals(x.RawContent, y.RawContent); } public class StateEventResponse : StateEvent { diff --git a/Tests/LibMatrix.Tests/DataTests/WhoAmITests.cs b/Tests/LibMatrix.Tests/DataTests/WhoAmITests.cs
index e1da3d5..2a25056 100644 --- a/Tests/LibMatrix.Tests/DataTests/WhoAmITests.cs +++ b/Tests/LibMatrix.Tests/DataTests/WhoAmITests.cs
@@ -1,3 +1,5 @@ +using LibMatrix.Responses; + namespace LibMatrix.Tests.DataTests; public static class WhoAmITests { diff --git a/Utilities/LibMatrix.FederationTest/.gitignore b/Utilities/LibMatrix.FederationTest/.gitignore new file mode 100644
index 0000000..4a96773 --- /dev/null +++ b/Utilities/LibMatrix.FederationTest/.gitignore
@@ -0,0 +1 @@ +.keys/ diff --git a/Utilities/LibMatrix.FederationTest/Controllers/FederationKeysController.cs b/Utilities/LibMatrix.FederationTest/Controllers/FederationKeysController.cs new file mode 100644
index 0000000..33d0b99 --- /dev/null +++ b/Utilities/LibMatrix.FederationTest/Controllers/FederationKeysController.cs
@@ -0,0 +1,41 @@ +using LibMatrix.Federation.Extensions; +using LibMatrix.Federation.Utilities; +using LibMatrix.FederationTest.Services; +using LibMatrix.Homeservers; +using Microsoft.AspNetCore.Mvc; + +namespace LibMatrix.FederationTest.Controllers; + +[ApiController] +[Route("_matrix/key/v2/")] +public class FederationKeysController(FederationTestConfiguration config, FederationKeyStore keyStore) { + static FederationKeysController() { + Console.WriteLine("INFO | FederationKeysController initialized."); + } + + private static SignedObject<ServerKeysResponse>? _cachedServerKeysResponse; + private static SemaphoreSlim _serverKeyCacheLock = new SemaphoreSlim(1, 1); + + [HttpGet("server")] + public async Task<SignedObject<ServerKeysResponse>> GetServerKeys() { + await _serverKeyCacheLock.WaitAsync(); + if (_cachedServerKeysResponse == null || _cachedServerKeysResponse.TypedContent.ValidUntil < DateTime.Now + TimeSpan.FromSeconds(30)) { + var keys = keyStore.GetCurrentSigningKey(); + _cachedServerKeysResponse = new ServerKeysResponse() { + ValidUntil = DateTime.Now + TimeSpan.FromMinutes(1), + ServerName = config.ServerName, + OldVerifyKeys = [], + VerifyKeysById = new() { + { + new() { Algorithm = "ed25519", KeyId = "0" }, new ServerKeysResponse.CurrentVerifyKey() { + Key = keys.publicKey.ToUnpaddedBase64(), + } + } + } + }.Sign(config.ServerName, new ServerKeysResponse.VersionedKeyId() { Algorithm = "ed25519", KeyId = "0" }, keys.privateKey); + } + _serverKeyCacheLock.Release(); + + return _cachedServerKeysResponse; + } +} \ No newline at end of file diff --git a/Utilities/LibMatrix.FederationTest/Controllers/FederationVersionController.cs b/Utilities/LibMatrix.FederationTest/Controllers/FederationVersionController.cs new file mode 100644
index 0000000..2c3aaa3 --- /dev/null +++ b/Utilities/LibMatrix.FederationTest/Controllers/FederationVersionController.cs
@@ -0,0 +1,18 @@ +using LibMatrix.Homeservers; +using Microsoft.AspNetCore.Mvc; + +namespace LibMatrix.FederationTest.Controllers; + +[ApiController] +[Route("_matrix/federation/v1/")] +public class FederationVersionController : ControllerBase { + [HttpGet("version")] + public ServerVersionResponse GetVersion() { + return new ServerVersionResponse { + Server = new() { + Name = "LibMatrix.Federation", + Version = "0.0.0", + } + }; + } +} \ No newline at end of file diff --git a/Utilities/LibMatrix.FederationTest/Controllers/TestController.cs b/Utilities/LibMatrix.FederationTest/Controllers/TestController.cs new file mode 100644
index 0000000..4a6bc87 --- /dev/null +++ b/Utilities/LibMatrix.FederationTest/Controllers/TestController.cs
@@ -0,0 +1,52 @@ +using System.Text.Json.Nodes; +using ArcaneLibs.Extensions; +using LibMatrix.Extensions; +using LibMatrix.Federation; +using LibMatrix.Federation.Utilities; +using LibMatrix.FederationTest.Services; +using LibMatrix.Homeservers; +using Microsoft.AspNetCore.Mvc; + +namespace LibMatrix.FederationTest.Controllers; + +[ApiController] +public class TestController(FederationTestConfiguration config, FederationKeyStore keyStore) : ControllerBase { + static TestController() { + Console.WriteLine("INFO | TestController initialized."); + } + + [HttpGet("/test")] + public async Task<JsonObject> GetTest() { + var hc = new MatrixHttpClient() { + BaseAddress = new Uri("https://matrix.rory.gay") + }; + + var keyId = new ServerKeysResponse.VersionedKeyId() { + Algorithm = "ed25519", + KeyId = "0" + }; + + var signatureData = new XMatrixAuthorizationScheme.XMatrixRequestSignature() { + Method = "GET", + Uri = "/_matrix/federation/v1/user/devices/@emma:rory.gay", + OriginServerName = config.ServerName, + DestinationServerName = "rory.gay" + } + .Sign(config.ServerName, keyId, keyStore.GetCurrentSigningKey().privateKey); + + var signature = signatureData.Signatures[config.ServerName][keyId]; + var headerValue = new XMatrixAuthorizationScheme.XMatrixAuthorizationHeader() { + Origin = config.ServerName, + Destination = "rory.gay", + Key = keyId, + Signature = signature + }.ToHeaderValue(); + + var req = new HttpRequestMessage(HttpMethod.Get, "/_matrix/federation/v1/user/devices/@emma:rory.gay"); + req.Headers.Add("Authorization", headerValue); + + var response = await hc.SendAsync(req); + var content = await response.Content.ReadFromJsonAsync<JsonObject>(); + return content!; + } +} \ No newline at end of file diff --git a/Utilities/LibMatrix.FederationTest/Controllers/WellKnownController.cs b/Utilities/LibMatrix.FederationTest/Controllers/WellKnownController.cs new file mode 100644
index 0000000..28fca8d --- /dev/null +++ b/Utilities/LibMatrix.FederationTest/Controllers/WellKnownController.cs
@@ -0,0 +1,19 @@ +using LibMatrix.Services.WellKnownResolver.WellKnownResolvers; +using Microsoft.AspNetCore.Mvc; + +namespace LibMatrix.FederationTest.Controllers; + +[ApiController] +[Route(".well-known/")] +public class WellKnownController(ILogger<WellKnownController> logger) : ControllerBase { + static WellKnownController() { + Console.WriteLine("INFO | WellKnownController initialized."); + } + [HttpGet("matrix/server")] + public ServerWellKnown GetMatrixServerWellKnown() { + // {Request.Headers["X-Forwarded-Proto"].FirstOrDefault(Request.Scheme)}:// + return new() { + Homeserver = $"{Request.Headers["X-Forwarded-Host"].FirstOrDefault(Request.Host.Host)}:{Request.Headers["X-Forwarded-Port"].FirstOrDefault("443")}", + }; + } +} \ No newline at end of file diff --git a/Utilities/LibMatrix.FederationTest/LibMatrix.FederationTest.csproj b/Utilities/LibMatrix.FederationTest/LibMatrix.FederationTest.csproj new file mode 100644
index 0000000..c54ba8d --- /dev/null +++ b/Utilities/LibMatrix.FederationTest/LibMatrix.FederationTest.csproj
@@ -0,0 +1,18 @@ +<Project Sdk="Microsoft.NET.Sdk.Web"> + + <PropertyGroup> + <TargetFramework>net9.0</TargetFramework> + <Nullable>enable</Nullable> + <ImplicitUsings>enable</ImplicitUsings> + <NoDefaultLaunchSettingsFile>True</NoDefaultLaunchSettingsFile> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.5"/> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\..\LibMatrix.Federation\LibMatrix.Federation.csproj" /> + </ItemGroup> + +</Project> diff --git a/Utilities/LibMatrix.FederationTest/Program.cs b/Utilities/LibMatrix.FederationTest/Program.cs new file mode 100644
index 0000000..adc809f --- /dev/null +++ b/Utilities/LibMatrix.FederationTest/Program.cs
@@ -0,0 +1,34 @@ +using LibMatrix.FederationTest.Services; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); +// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi +builder.Services.AddOpenApi(); +builder.Services.AddHttpLogging(options => { + options.LoggingFields = Microsoft.AspNetCore.HttpLogging.HttpLoggingFields.All; + options.RequestHeaders.Add("X-Forwarded-Proto"); + options.RequestHeaders.Add("X-Forwarded-Host"); + options.RequestHeaders.Add("X-Forwarded-Port"); +}); + +builder.Services.AddSingleton<FederationTestConfiguration>(); +builder.Services.AddSingleton<FederationKeyStore>(); + + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (true || app.Environment.IsDevelopment()) { + app.MapOpenApi(); +} + +app.UseAuthorization(); + +app.MapControllers(); +// app.UseHttpLogging(); + + +app.Run(); \ No newline at end of file diff --git a/Utilities/LibMatrix.FederationTest/Services/FederationKeyStore.cs b/Utilities/LibMatrix.FederationTest/Services/FederationKeyStore.cs new file mode 100644
index 0000000..f24d14e --- /dev/null +++ b/Utilities/LibMatrix.FederationTest/Services/FederationKeyStore.cs
@@ -0,0 +1,31 @@ +using LibMatrix.FederationTest.Utilities; +using Org.BouncyCastle.Crypto.Parameters; + +namespace LibMatrix.FederationTest.Services; + +public class FederationKeyStore(FederationTestConfiguration config) { + static FederationKeyStore() { + Console.WriteLine("INFO | FederationKeyStore initialized."); + } + + private static (Ed25519PrivateKeyParameters privateKey, Ed25519PublicKeyParameters publicKey) currentKeyPair = default; + public (Ed25519PrivateKeyParameters privateKey, Ed25519PublicKeyParameters publicKey) GetCurrentSigningKey() { + if (currentKeyPair != default) { + return currentKeyPair; + } + + if(!Directory.Exists(config.KeyStorePath)) Directory.CreateDirectory(config.KeyStorePath); + + var privateKeyPath = Path.Combine(config.KeyStorePath, "signing.key"); + if (!File.Exists(privateKeyPath)) { + var keyPair = Ed25519Utils.GenerateKeyPair(); + File.WriteAllBytes(privateKeyPath, keyPair.privateKey.GetEncoded()); + return keyPair; + } + + var privateKeyBytes = File.ReadAllBytes(privateKeyPath); + var privateKey = Ed25519Utils.LoadPrivateKeyFromEncoded(privateKeyBytes); + var publicKey = privateKey.GeneratePublicKey(); + return currentKeyPair = (privateKey, publicKey); + } +} \ No newline at end of file diff --git a/Utilities/LibMatrix.FederationTest/Services/FederationTestConfiguration.cs b/Utilities/LibMatrix.FederationTest/Services/FederationTestConfiguration.cs new file mode 100644
index 0000000..353ddf5 --- /dev/null +++ b/Utilities/LibMatrix.FederationTest/Services/FederationTestConfiguration.cs
@@ -0,0 +1,10 @@ +namespace LibMatrix.FederationTest.Services; + +public class FederationTestConfiguration { + public FederationTestConfiguration(IConfiguration configurationSection) { + configurationSection.GetRequiredSection("FederationTest").Bind(this); + } + + public string ServerName { get; set; } = "localhost"; + public string KeyStorePath { get; set; } = "./.keys"; +} \ No newline at end of file diff --git a/Utilities/LibMatrix.FederationTest/Utilities/Ed25519Utils.cs b/Utilities/LibMatrix.FederationTest/Utilities/Ed25519Utils.cs new file mode 100644
index 0000000..bb57d51 --- /dev/null +++ b/Utilities/LibMatrix.FederationTest/Utilities/Ed25519Utils.cs
@@ -0,0 +1,28 @@ +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; + +namespace LibMatrix.FederationTest.Utilities; + +public class Ed25519Utils { + public static (Ed25519PrivateKeyParameters privateKey, Ed25519PublicKeyParameters publicKey) GenerateKeyPair() { + Console.WriteLine("Generating new Ed25519 key pair!"); + var keyPairGen = new Ed25519KeyPairGenerator(); + keyPairGen.Init(new Ed25519KeyGenerationParameters(new SecureRandom())); + var keyPair = keyPairGen.GenerateKeyPair(); + + var privateKey = (Ed25519PrivateKeyParameters)keyPair.Private; + var publicKey = (Ed25519PublicKeyParameters)keyPair.Public; + + return (privateKey, publicKey); + } + + public static Ed25519PublicKeyParameters LoadPublicKeyFromEncoded(string key) { + var keyBytes = Convert.FromBase64String(key); + return new Ed25519PublicKeyParameters(keyBytes, 0); + } + + public static Ed25519PrivateKeyParameters LoadPrivateKeyFromEncoded(byte[] key) { + return new Ed25519PrivateKeyParameters(key, 0); + } +} \ No newline at end of file diff --git a/Utilities/LibMatrix.FederationTest/appsettings.Development.json b/Utilities/LibMatrix.FederationTest/appsettings.Development.json new file mode 100644
index 0000000..b6c6151 --- /dev/null +++ b/Utilities/LibMatrix.FederationTest/appsettings.Development.json
@@ -0,0 +1,13 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Information", + "Microsoft.AspNetCore.Routing": "Warning" + } + }, + "FederationTest": { + "ServerName": "libmatrix-fed-test.rory.gay", + "KeyStorePath": "./.keys" + } +} diff --git a/Utilities/LibMatrix.FederationTest/appsettings.json b/Utilities/LibMatrix.FederationTest/appsettings.json new file mode 100644
index 0000000..10f68b8 --- /dev/null +++ b/Utilities/LibMatrix.FederationTest/appsettings.json
@@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomStateController.cs b/Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomStateController.cs
index 74c70a3..485c028 100644 --- a/Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomStateController.cs +++ b/Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomStateController.cs
@@ -2,6 +2,7 @@ using System.Collections.Frozen; using System.Text.Json.Nodes; using LibMatrix.HomeserverEmulator.Extensions; using LibMatrix.HomeserverEmulator.Services; +using LibMatrix.Responses; using Microsoft.AspNetCore.Mvc; namespace LibMatrix.HomeserverEmulator.Controllers.Rooms; diff --git a/Utilities/LibMatrix.HomeserverEmulator/Controllers/Users/UserController.cs b/Utilities/LibMatrix.HomeserverEmulator/Controllers/Users/UserController.cs
index 40f3667..339e686 100644 --- a/Utilities/LibMatrix.HomeserverEmulator/Controllers/Users/UserController.cs +++ b/Utilities/LibMatrix.HomeserverEmulator/Controllers/Users/UserController.cs
@@ -1,6 +1,7 @@ using System.Text.Json.Serialization; using LibMatrix.EventTypes.Spec.State.RoomInfo; using LibMatrix.HomeserverEmulator.Services; +using LibMatrix.Responses; using Microsoft.AspNetCore.Mvc; namespace LibMatrix.HomeserverEmulator.Controllers; diff --git a/Utilities/LibMatrix.Utilities.Bot/Interfaces/CommandContext.cs b/Utilities/LibMatrix.Utilities.Bot/Interfaces/CommandContext.cs
index c6abde2..71ecbed 100644 --- a/Utilities/LibMatrix.Utilities.Bot/Interfaces/CommandContext.cs +++ b/Utilities/LibMatrix.Utilities.Bot/Interfaces/CommandContext.cs
@@ -1,5 +1,6 @@ using LibMatrix.EventTypes.Spec; using LibMatrix.Homeservers; +using LibMatrix.Responses; using LibMatrix.RoomTypes; namespace LibMatrix.Utilities.Bot.Interfaces; diff --git a/Utilities/LibMatrix.Utilities.Bot/Services/CommandListenerHostedService.cs b/Utilities/LibMatrix.Utilities.Bot/Services/CommandListenerHostedService.cs
index 4c6b462..d0a93a4 100644 --- a/Utilities/LibMatrix.Utilities.Bot/Services/CommandListenerHostedService.cs +++ b/Utilities/LibMatrix.Utilities.Bot/Services/CommandListenerHostedService.cs
@@ -25,7 +25,7 @@ public class CommandListenerHostedService( private FrozenSet<ICommand> _commands = null!; private Task? _listenerTask; - private CancellationTokenSource _cts = new(); + private readonly CancellationTokenSource _cts = new(); private long _startupTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); /// <summary>Triggered when the application host is ready to start the service.</summary> @@ -61,7 +61,6 @@ public class CommandListenerHostedService( Timeout = config.SyncConfiguration.Timeout ?? 30_000, MinimumDelay = config.SyncConfiguration.MinimumSyncTime ?? TimeSpan.Zero, SetPresence = config.SyncConfiguration.Presence ?? botConfig.Presence, - }; syncHelper.SyncReceivedHandlers.Add(async sync => {