diff --git a/.editorconfig b/.editorconfig index 49bb255..ac594b1 100644 --- a/.editorconfig +++ b/.editorconfig @@ -37,12 +37,16 @@ dotnet_style_qualification_for_field = false:silent dotnet_style_qualification_for_property = false:silent dotnet_style_qualification_for_method = false:silent dotnet_style_qualification_for_event = false:silent +dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion [*.xml] indent_style = space indent_size = 2 [*.cs] + +file_header_template = ******************************************************************************************************************************\n \nCopyright (c) 2018-2022 InterlockLedger Network\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met\n\n* Redistributions of source code must retain the above copyright notice, this\n list of conditions and the following disclaimer.\n\n* Redistributions in binary form must reproduce the above copyright notice,\n this list of conditions and the following disclaimer in the documentation\n and/or other materials provided with the distribution.\n\n* Neither the name of the copyright holder nor the names of its\n contributors may be used to endorse or promote products derived from\n this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES, LOSS OF USE, DATA, OR PROFITS, OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n****************************************************************************************************************************** + csharp_indent_block_contents = true csharp_indent_braces = false csharp_indent_case_contents = true @@ -146,6 +150,22 @@ dotnet_style_qualification_for_property = false:silent dotnet_style_readonly_field = true:warning dotnet_style_require_accessibility_modifiers = always:silent +csharp_style_namespace_declarations = file_scoped:silent +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_null_check_over_type_check = true:suggestion +csharp_style_prefer_local_over_anonymous_function = true:suggestion +csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion +csharp_style_prefer_tuple_swap = true:suggestion +csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent +csharp_style_prefer_parameter_null_checking = true:suggestion +csharp_style_prefer_pattern_matching = true:silent +csharp_style_prefer_not_pattern = true:suggestion +csharp_style_prefer_extended_property_pattern = true:suggestion +csharp_style_prefer_top_level_statements = true:silent +csharp_style_prefer_primary_constructors = true:suggestion + dotnet_diagnostic.CA1028.severity = none dotnet_diagnostic.CA1031.severity = none dotnet_diagnostic.CA1032.severity = none @@ -159,24 +179,11 @@ dotnet_diagnostic.CA1707.severity = none dotnet_diagnostic.CA1710.severity = none dotnet_diagnostic.CA1815.severity = none dotnet_diagnostic.CA1819.severity = none - -file_header_template = ******************************************************************************************************************************\n \nCopyright (c) 2018-2022 InterlockLedger Network\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met\n\n* Redistributions of source code must retain the above copyright notice, this\n list of conditions and the following disclaimer.\n\n* Redistributions in binary form must reproduce the above copyright notice,\n this list of conditions and the following disclaimer in the documentation\n and/or other materials provided with the distribution.\n\n* Neither the name of the copyright holder nor the names of its\n contributors may be used to endorse or promote products derived from\n this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES, LOSS OF USE, DATA, OR PROFITS, OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n****************************************************************************************************************************** - dotnet_diagnostic.CA2007.severity = none dotnet_diagnostic.CA2225.severity = none dotnet_diagnostic.CA2227.severity = none dotnet_diagnostic.RCS1194.severity = suggestion -csharp_style_namespace_declarations = file_scoped:silent -csharp_style_prefer_method_group_conversion = true:silent -csharp_style_prefer_null_check_over_type_check = true:suggestion -csharp_style_prefer_local_over_anonymous_function = true:suggestion -csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion -csharp_style_prefer_tuple_swap = true:suggestion -csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent -csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent -csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent -csharp_style_prefer_parameter_null_checking = true:suggestion -csharp_style_prefer_pattern_matching = true:silent -csharp_style_prefer_not_pattern = true:suggestion -csharp_style_prefer_extended_property_pattern = true:suggestion +dotnet_diagnostic.MA0076.severity = none +dotnet_diagnostic.MA0042.severity = none +dotnet_diagnostic.MA0023.severity = none diff --git a/InterlockLedger.Rest.Client/Abstractions/RestAbstractChain.cs b/InterlockLedger.Rest.Client/Abstractions/RestAbstractChain.cs index de7e066..5d7dc66 100644 --- a/InterlockLedger.Rest.Client/Abstractions/RestAbstractChain.cs +++ b/InterlockLedger.Rest.Client/Abstractions/RestAbstractChain.cs @@ -64,7 +64,7 @@ public abstract class RestAbstractChain : IRestChain where T : IRestChain internal readonly RestAbstractNode _node; - internal RestAbstractChain(RestAbstractNode node, ChainIdModel chainId) { + protected RestAbstractChain(RestAbstractNode node, ChainIdModel chainId) { _node = node.Required(); Id = chainId.Required().Id.Required(); Name = chainId.Name; diff --git a/InterlockLedger.Rest.Client/Abstractions/RestAbstractNode.cs b/InterlockLedger.Rest.Client/Abstractions/RestAbstractNode.cs index e74df10..6c7de87 100644 --- a/InterlockLedger.Rest.Client/Abstractions/RestAbstractNode.cs +++ b/InterlockLedger.Rest.Client/Abstractions/RestAbstractNode.cs @@ -33,6 +33,7 @@ using System.Diagnostics; +using System.Globalization; using System.Net; using System.Net.Mime; using System.Net.Security; @@ -43,19 +44,19 @@ namespace InterlockLedger.Rest.Client.Abstractions; [DebuggerDisplay("{" + nameof(GetDebuggerDisplay) + "(),nq}")] public abstract class RestAbstractNode where T : IRestChain { - public RestAbstractNode(X509Certificate2 x509Certificate, NetworkPredefinedPorts networkId = NetworkPredefinedPorts.MainNet, string address = "localhost") + protected RestAbstractNode(X509Certificate2 x509Certificate, NetworkPredefinedPorts networkId = NetworkPredefinedPorts.MainNet, string address = "localhost") : this(x509Certificate, (ushort)networkId, address) { } - public RestAbstractNode(X509Certificate2 x509Certificate, ushort port, string address = "localhost") { + protected RestAbstractNode(X509Certificate2 x509Certificate, ushort port, string address = "localhost") { _certificate = x509Certificate; BaseUri = new Uri($"https://{address}:{port}/", UriKind.Absolute); Network = new RestNetwork(this); } - public RestAbstractNode(string certFile, string certPassword, NetworkPredefinedPorts networkId = NetworkPredefinedPorts.MainNet, string address = "localhost") + protected RestAbstractNode(string certFile, string certPassword, NetworkPredefinedPorts networkId = NetworkPredefinedPorts.MainNet, string address = "localhost") : this(certFile, certPassword, (ushort)networkId, address) { } - public RestAbstractNode(string certFile, string certPassword, ushort port, string address = "localhost") + protected RestAbstractNode(string certFile, string certPassword, ushort port, string address = "localhost") : this(GetCertFromFile(certFile, certPassword), port, address) { } public Uri BaseUri { get; } @@ -69,16 +70,16 @@ public RestAbstractNode(string certFile, string certPassword, ushort port, strin public Task CreateChainAsync(ChainCreationModel model) => PostAsync($"/chain", model); - public async Task> GetChainsAsync() => (await GetAsync>("/chain")).Safe().Select(c => BuildChain(c)); + public async Task> GetChainsAsync() => (await GetAsync>("/chain").ConfigureAwait(false)).Safe().Select(c => BuildChain(c)); public Task GetDetailsAsync() => GetAsync("/"); - public async Task> GetMirrorsAsync() => (await GetAsync>("/mirrors")).Safe().Select(c => BuildChain(c)); + public async Task> GetMirrorsAsync() => (await GetAsync>("/mirrors").ConfigureAwait(false)).Safe().Select(c => BuildChain(c)); public Task?> GetPeersAsync() => GetAsync>("/peers"); public async Task?> InterlocksOfAsync(string chain) - => await GetAsync>($"/interlockings/{chain}"); + => await GetAsync>($"/interlockings/{chain}").ConfigureAwait(false); public override string ToString() => $"Node [{BaseUri}] connected with certificate '{CertificateName}'"; @@ -93,27 +94,26 @@ protected internal static async Task GetResponseAsync(HttpWebRe } protected internal static async Task GetStringResponseAsync(HttpWebRequest req) - => (await IfOkOrCreatedReturnAsync(await GetResponseAsync(req), ReadAsStringAsync)).WithDefault(string.Empty); + => (await IfOkOrCreatedReturnAsync(await GetResponseAsync(req).ConfigureAwait(false), ReadAsStringAsync).ConfigureAwait(false)).WithDefault(string.Empty); private static async Task IfOkOrCreatedReturnAsync(HttpWebResponse resp, Func> buildResult) { return resp.StatusCode switch { - HttpStatusCode.OK or HttpStatusCode.Created => await buildResult(resp), + HttpStatusCode.OK or HttpStatusCode.Created => await buildResult(resp).ConfigureAwait(false), HttpStatusCode.NotFound => default, - _ => await CheckMessageAsync(resp) + _ => await CheckMessageAsync(resp).ConfigureAwait(false), }; static async Task CheckMessageAsync(HttpWebResponse resp) { string content = await ReadAsStringAsync(resp).ConfigureAwait(false); if (content.Safe().Contains("outstanding", StringComparison.OrdinalIgnoreCase)) return default; - else { - string exceptionMessage = $"{resp.StatusCode}(#{(int)resp.StatusCode}){Environment.NewLine}{content}"; - return resp.StatusCode switch { - HttpStatusCode.Unauthorized => throw new SecurityException(exceptionMessage), - HttpStatusCode.Forbidden => throw new SecurityException(exceptionMessage), - _ => throw new ApiException(exceptionMessage), - }; - } + + string exceptionMessage = $"{resp.StatusCode}(#{(int)resp.StatusCode}){Environment.NewLine}{content}"; + return resp.StatusCode switch { + HttpStatusCode.Unauthorized => throw new SecurityException(exceptionMessage), + HttpStatusCode.Forbidden => throw new SecurityException(exceptionMessage), + _ => throw new ApiException(exceptionMessage), + }; } } @@ -122,19 +122,19 @@ protected internal static async Task ReadAsStringAsync(HttpWebResponse r if (resp == null) return string.Empty; using var readStream = new StreamReader(resp.GetResponseStream()); - return await readStream.ReadToEndAsync(); + return await readStream.ReadToEndAsync().ConfigureAwait(false); } protected internal abstract T BuildChain(ChainIdModel c); protected internal async Task CallApiAsync(string url, string method, string accept = "application/json") - => await GetStringResponseAsync(PrepareRequest(url, method, accept)); + => await GetStringResponseAsync(PrepareRequest(url, method, accept)).ConfigureAwait(false); protected internal async Task CallApiPlainDocAsync(string url, string method, string accept = "plain/text") - => await GetStringResponseAsync(PrepareRequest(url, method, accept)); + => await GetStringResponseAsync(PrepareRequest(url, method, accept)).ConfigureAwait(false); protected internal async Task CallApiRawDocAsync(string url, string method, string accept = "*") - => await GetRawResponseAsync(PrepareRequest(url, method, accept)); + => await GetRawResponseAsync(PrepareRequest(url, method, accept)).ConfigureAwait(false); protected internal async Task GetAsync(string url) => Deserialize(await CallApiAsync(url, "GET").ConfigureAwait(false)); @@ -146,26 +146,26 @@ protected internal async Task CallApiPlainDocAsync(string url, string me throw new ArgumentException($"'{nameof(accept)}' cannot be null or empty", nameof(accept)); if (string.IsNullOrWhiteSpace(method)) throw new ArgumentException($"'{nameof(method)}' cannot be null or empty", nameof(method)); - var resp = await GetResponseAsync(PrepareRequest(url, method, accept)); + var resp = await GetResponseAsync(PrepareRequest(url, method, accept)).ConfigureAwait(false); return resp.StatusCode == HttpStatusCode.OK ? (ParseFileName(resp), resp.ContentType, resp.GetResponseStream()) : null; } protected internal async Task<(ulong AppId, ulong PayloadTypeId, DateTimeOffset? CreatedAt, Stream Content)?> GetOpaqueStreamAsync(string url) { - var resp = await GetResponseAsync(PrepareRequest(url.Required(), "GET", "application/octet-stream")); + var resp = await GetResponseAsync(PrepareRequest(url.Required(), "GET", "application/octet-stream")).ConfigureAwait(false); if (resp.StatusCode != HttpStatusCode.OK) return null; - DateTimeOffset? createdAt = DateTimeOffset.TryParse(resp.Headers["x-created-at"], out var parsedCreatedAt) ? parsedCreatedAt : null; + DateTimeOffset? createdAt = DateTimeOffset.TryParse(resp.Headers["x-created-at"], CultureInfo.InvariantCulture, out var parsedCreatedAt) ? parsedCreatedAt : null; return (ulong.Parse(resp.Headers["x-app-id"].WithDefault("0")), ulong.Parse(resp.Headers["x-payload-type-id"].WithDefault("0")), createdAt, resp.GetResponseStream()); } protected internal async Task PostAsync(string url, object? body) - => Deserialize(await GetStringResponseAsync(PreparePostRequest(url, body, accept: "application/json"))); + => Deserialize(await GetStringResponseAsync(PreparePostRequest(url, body, accept: "application/json")).ConfigureAwait(false)); protected internal async Task PostRawAsync(string url, byte[] body, string contentType) - => Deserialize(await GetStringResponseAsync(PreparePostRawRequest(url, body, accept: "application/json", contentType))); + => Deserialize(await GetStringResponseAsync(PreparePostRawRequest(url, body, accept: "application/json", contentType)).ConfigureAwait(false)); protected internal async Task PostStreamAsync(string url, Stream body, string contentType) { - var resp = await GetResponseAsync(PreparePostStreamRequest(url, body, accept: "*/*", contentType)); - return await IfOkOrCreatedReturnAsync(resp, async (rs) => Deserialize(await ReadAsStringAsync(rs))); + var resp = await GetResponseAsync(PreparePostStreamRequest(url, body, accept: "*/*", contentType)).ConfigureAwait(false); + return await IfOkOrCreatedReturnAsync(resp, async (rs) => Deserialize(await ReadAsStringAsync(rs).ConfigureAwait(false))).ConfigureAwait(false); } protected internal HttpWebRequest PrepareRequest(string url, string method, string accept) { @@ -182,10 +182,10 @@ protected internal HttpWebRequest PrepareRequest(string url, string method, stri return req; } - protected internal bool ServerCertificateValidation(object sender, X509Certificate? certificate, X509Chain? chain, SslPolicyErrors sslPolicyErrors) + protected internal static bool ServerCertificateValidation(object sender, X509Certificate? certificate, X509Chain? chain, SslPolicyErrors sslPolicyErrors) => true; - private static readonly Encoding _utf8WithoutBOM = new UTF8Encoding(false); + private static readonly Encoding _utf8WithoutBOM = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); private static byte[] ArrayConcat(byte[] firstBuffer, Memory secondBuffer, int count) { var concatBuffer = new byte[firstBuffer.Length + count]; @@ -209,7 +209,7 @@ private static X509Certificate2 GetCertFromFile(string certPath, string certPass => new(certPath, certPassword, X509KeyStorageFlags.PersistKeySet); private static async Task GetRawResponseAsync(HttpWebRequest req) { - var response = await GetResponseAsync(req); + var response = await GetResponseAsync(req).ConfigureAwait(false); if (response is null) return null; using var resp = response; @@ -217,7 +217,7 @@ private static X509Certificate2 GetCertFromFile(string certPath, string certPass var fullBuffer = Array.Empty(); var buffer = new byte[0x80000].AsMemory(); while (true) { - var readCount = await readStream.ReadAsync(buffer, cancellationToken: CancellationToken.None); + var readCount = await readStream.ReadAsync(buffer, cancellationToken: CancellationToken.None).ConfigureAwait(false); if (readCount == 0) break; fullBuffer = ArrayConcat(fullBuffer, buffer, readCount); @@ -233,7 +233,7 @@ private static string ParseFileName(HttpWebResponse resp) { var filename = header.FileName; if (header.Parameters.ContainsKey("filename*")) { filename = header.Parameters["filename*"]; - if (filename.Safe().StartsWith("UTF-8''")) + if (filename.Safe().StartsWith("UTF-8''", StringComparison.OrdinalIgnoreCase)) return WebUtility.UrlDecode(filename![7..]); } return filename.WithDefault("?"); diff --git a/InterlockLedger.Rest.Client/Implementations/RecordsAsJsonStoreImplementation.cs b/InterlockLedger.Rest.Client/Implementations/RecordsAsJsonStoreImplementation.cs index 2b065fd..ef78d95 100644 --- a/InterlockLedger.Rest.Client/Implementations/RecordsAsJsonStoreImplementation.cs +++ b/InterlockLedger.Rest.Client/Implementations/RecordsAsJsonStoreImplementation.cs @@ -40,15 +40,6 @@ public RecordsAsJsonStoreImplementation(RestAbstractChain parent) { _id = _parent.Id; } - public Task AddAsync(NewRecordModelAsJson model) - => AddAsync(model.ApplicationId, model.PayloadTagId, model.Type, model.Json); - - public Task AddAsync(ulong applicationId, ulong payloadTagId, object? payload) - => AddAsync(applicationId, payloadTagId, RecordType.Data, payload); - - public Task AddAsync(ulong applicationId, ulong payloadTagId, RecordType type, object? payload) - => _node.PostAsync($"records@{_id}/asJson?applicationId={applicationId}&payloadTagId={payloadTagId}&type={type}", payload); - public Task?> FromAsync(ulong firstSerial, ushort page = 0, byte pageSize = 10, bool lastToFirst = false, bool ommitPayload = false) => _node.GetAsync>($"records@{_id}/asJson?firstSerial={firstSerial}&page={page}&pageSize={pageSize}&lastToFirst={lastToFirst}&ommitPayload={ommitPayload}"); diff --git a/InterlockLedger.Rest.Client/Interfaces/IRecordsAsJsonStore.cs b/InterlockLedger.Rest.Client/Interfaces/IRecordsAsJsonStore.cs index c5250b5..c24342a 100644 --- a/InterlockLedger.Rest.Client/Interfaces/IRecordsAsJsonStore.cs +++ b/InterlockLedger.Rest.Client/Interfaces/IRecordsAsJsonStore.cs @@ -34,12 +34,6 @@ namespace InterlockLedger.Rest.Client; public interface IRecordsAsJsonStore { - Task AddAsync(NewRecordModelAsJson model); - - Task AddAsync(ulong applicationId, ulong payloadTagId, object payload); - - Task AddAsync(ulong applicationId, ulong payloadTagId, RecordType type, object payload); - Task?> FromAsync(ulong firstSerial, ushort page = 0, byte pageSize = 10, bool lastToFirst = false, bool ommitPayload = false); Task?> FromToAsync(ulong firstSerial, ulong lastSerial, ushort page = 0, byte pageSize = 10, bool lastToFirst = false, bool ommitPayload = false); diff --git a/InterlockLedger.Rest.Client/InterlockLedger.Rest.Client.csproj b/InterlockLedger.Rest.Client/InterlockLedger.Rest.Client.csproj index f0246e5..99903c0 100644 --- a/InterlockLedger.Rest.Client/InterlockLedger.Rest.Client.csproj +++ b/InterlockLedger.Rest.Client/InterlockLedger.Rest.Client.csproj @@ -2,7 +2,7 @@ net8.0 - 13.7.3 + 14.2.2 true Copyright (c) 2018-2024 InterlockLedger Network @@ -13,7 +13,7 @@ git true REST Client InterlockLedger - Update to last changes in node v13.7.3 + Update to last changes in node v14.2.2 il2.png README.md BSD-3-Clause @@ -35,13 +35,17 @@ - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/InterlockLedger.Rest.Client/Models/CertificatePermitModel.cs b/InterlockLedger.Rest.Client/Models/CertificatePermitModel.cs index 3cfddb8..c74cad3 100644 --- a/InterlockLedger.Rest.Client/Models/CertificatePermitModel.cs +++ b/InterlockLedger.Rest.Client/Models/CertificatePermitModel.cs @@ -35,10 +35,10 @@ namespace InterlockLedger.Rest.Client; [method: SetsRequiredMembers] -public class CertificatePermitModel(byte[] certificateInX509, IEnumerable permissions, KeyPurpose[] purposes) : KeyPermitBaseModel(permissions, purposes) +public class CertificatePermitModel(X509Certificate2 certificate, IEnumerable permissions, KeyPurpose[] purposes) : KeyPermitBaseModel(permissions, purposes) { /// /// Certificate in X509 bytes /// - public required byte[] CertificateInX509 { get; set; } = certificateInX509; + public required byte[] CertificateInX509 { get; set; } = certificate.RawData; } diff --git a/InterlockLedger.Rest.Client/Models/ChainCreatedModel.cs b/InterlockLedger.Rest.Client/Models/ChainCreatedModel.cs index 6880847..25b94ca 100644 --- a/InterlockLedger.Rest.Client/Models/ChainCreatedModel.cs +++ b/InterlockLedger.Rest.Client/Models/ChainCreatedModel.cs @@ -1,4 +1,4 @@ -// ****************************************************************************************************************************** +// ****************************************************************************************************************************** // // Copyright (c) 2018-2022 InterlockLedger Network // All rights reserved. @@ -40,5 +40,5 @@ public class ChainCreatedModel : ChainIdModel /// /// Emergency key file names /// - public List KeyFiles { get; set; } = []; + public IReadOnlyList KeyFiles { get; set; } = []; } diff --git a/InterlockLedger.Rest.Client/Models/ChainCreationModel.cs b/InterlockLedger.Rest.Client/Models/ChainCreationModel.cs index e95f87b..b19a985 100644 --- a/InterlockLedger.Rest.Client/Models/ChainCreationModel.cs +++ b/InterlockLedger.Rest.Client/Models/ChainCreationModel.cs @@ -40,6 +40,7 @@ public class ChainCreationModel /// /// List of additional apps (only the numeric ids) /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "MA0016:Prefer using collection abstraction instead of implementation", Justification = "")] public List AdditionalApps { get; set; } = []; /// diff --git a/InterlockLedger.Rest.Client/Models/ChainIdModel.cs b/InterlockLedger.Rest.Client/Models/ChainIdModel.cs index f1c3e32..6270583 100644 --- a/InterlockLedger.Rest.Client/Models/ChainIdModel.cs +++ b/InterlockLedger.Rest.Client/Models/ChainIdModel.cs @@ -92,7 +92,7 @@ public class ChainIdModel : IComparable, IEquatable /// /// /// True if other is equal to this - public bool Equals(ChainIdModel? other) => other is not null && Id == other.Id; + public bool Equals(ChainIdModel? other) => other is not null && string.Equals(Id, other.Id, StringComparison.Ordinal); /// Calculate the hash /// A hash code for the current instance diff --git a/InterlockLedger.Rest.Client/Models/ChainSummaryModel.cs b/InterlockLedger.Rest.Client/Models/ChainSummaryModel.cs index 8211e6c..089805c 100644 --- a/InterlockLedger.Rest.Client/Models/ChainSummaryModel.cs +++ b/InterlockLedger.Rest.Client/Models/ChainSummaryModel.cs @@ -37,6 +37,7 @@ public sealed partial class ChainSummaryModel : ChainIdModel /// /// List of active apps (only the numeric ids) /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "MA0016:Prefer using collection abstraction instead of implementation", Justification = "")] public List ActiveApps { get; set; } = []; /// @@ -89,6 +90,6 @@ public ulong[] LicensedApps { /// public ulong? SizeInBytes { get; set; } - [GeneratedRegex(@"Apps: \[([\d,]+)\]")] + [GeneratedRegex(@"Apps: \[([\d,]+)\]", RegexOptions.None, matchTimeoutMilliseconds: 100)] private static partial Regex LicensedAppsRegex(); } \ No newline at end of file diff --git a/InterlockLedger.Rest.Client/Models/ColorJsonConverter.cs b/InterlockLedger.Rest.Client/Models/ColorJsonConverter.cs index d2e5b62..16f3f3f 100644 --- a/InterlockLedger.Rest.Client/Models/ColorJsonConverter.cs +++ b/InterlockLedger.Rest.Client/Models/ColorJsonConverter.cs @@ -34,7 +34,7 @@ namespace InterlockLedger.Rest.Client; -internal class ColorJsonConverter : JsonConverter +internal sealed class ColorJsonConverter : JsonConverter { public override Color Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => reader.TokenType switch { diff --git a/InterlockLedger.Rest.Client/Models/Enumerations/Algorithms.cs b/InterlockLedger.Rest.Client/Models/Enumerations/Algorithms.cs index ab1a34a..4aa96b1 100644 --- a/InterlockLedger.Rest.Client/Models/Enumerations/Algorithms.cs +++ b/InterlockLedger.Rest.Client/Models/Enumerations/Algorithms.cs @@ -40,5 +40,5 @@ public enum Algorithms : ushort DSA, ElGamal, // Signature EcDSA, - EdDSA + EdDSA, } \ No newline at end of file diff --git a/InterlockLedger.Rest.Client/Models/Enumerations/HashAlgorithms.cs b/InterlockLedger.Rest.Client/Models/Enumerations/HashAlgorithms.cs index 240a960..efc50dd 100644 --- a/InterlockLedger.Rest.Client/Models/Enumerations/HashAlgorithms.cs +++ b/InterlockLedger.Rest.Client/Models/Enumerations/HashAlgorithms.cs @@ -40,5 +40,5 @@ public enum HashAlgorithms : ushort SHA512, SHA3_256, SHA3_512, - Copy + Copy, } \ No newline at end of file diff --git a/InterlockLedger.Rest.Client/Models/Enumerations/KeyStrength.cs b/InterlockLedger.Rest.Client/Models/Enumerations/KeyStrength.cs index 6024cae..1b3a0f7 100644 --- a/InterlockLedger.Rest.Client/Models/Enumerations/KeyStrength.cs +++ b/InterlockLedger.Rest.Client/Models/Enumerations/KeyStrength.cs @@ -41,5 +41,5 @@ public enum KeyStrength MegaStrong, // RSA 5120 SuperStrong, // RSA 6144 HyperStrong, // RSA 7172 - UltraStrong // RSA 8192 + UltraStrong, // RSA 8192 } \ No newline at end of file diff --git a/InterlockLedger.Rest.Client/Models/Enumerations/NetworkPredefinedPorts.cs b/InterlockLedger.Rest.Client/Models/Enumerations/NetworkPredefinedPorts.cs index 22d08ee..40680fe 100644 --- a/InterlockLedger.Rest.Client/Models/Enumerations/NetworkPredefinedPorts.cs +++ b/InterlockLedger.Rest.Client/Models/Enumerations/NetworkPredefinedPorts.cs @@ -45,5 +45,5 @@ public enum NetworkPredefinedPorts : ushort Paperless = 29720, // CustomNets TilliT = 32063, - GoldReefCity = 2190 + GoldReefCity = 2190, } \ No newline at end of file diff --git a/InterlockLedger.Rest.Client/Models/Enumerations/RecordType.cs b/InterlockLedger.Rest.Client/Models/Enumerations/RecordType.cs index e39a740..fb6ddcf 100644 --- a/InterlockLedger.Rest.Client/Models/Enumerations/RecordType.cs +++ b/InterlockLedger.Rest.Client/Models/Enumerations/RecordType.cs @@ -39,5 +39,5 @@ public enum RecordType Data = 1, Closing = 2, EmergencyClosing = 3, - Corrupted = 255 + Corrupted = 255, } \ No newline at end of file diff --git a/InterlockLedger.Rest.Client/Models/KeyModel.cs b/InterlockLedger.Rest.Client/Models/KeyModel.cs index afcfa73..8a17db3 100644 --- a/InterlockLedger.Rest.Client/Models/KeyModel.cs +++ b/InterlockLedger.Rest.Client/Models/KeyModel.cs @@ -37,7 +37,7 @@ namespace InterlockLedger.Rest.Client; /// public class KeyModel { - public bool Actionable => Purposes.Contains("Action"); + public bool Actionable => Purposes.Contains("Action", StringComparer.OrdinalIgnoreCase); /// /// Unique key id @@ -70,5 +70,5 @@ public class KeyModel private static readonly string _indent2 = _indent + " "; private string _actionsFor => Permissions is null ? "No actions permitted!" : $"Actions permitted:{_indent2}{Permissions.JoinedBy(_indent2)}"; - private string _displayablePurposes => Purposes.OrderBy(p => p).WithCommas(); + private string _displayablePurposes => Purposes.Order(StringComparer.Ordinal).WithCommas(); } \ No newline at end of file diff --git a/InterlockLedger.Rest.Client/Models/KeyPermitBaseModel.cs b/InterlockLedger.Rest.Client/Models/KeyPermitBaseModel.cs index 5b7f86e..6a6ac0c 100644 --- a/InterlockLedger.Rest.Client/Models/KeyPermitBaseModel.cs +++ b/InterlockLedger.Rest.Client/Models/KeyPermitBaseModel.cs @@ -52,7 +52,7 @@ public abstract class KeyPermitBaseModel public required KeyPurpose[] Purposes { get; set; } [SetsRequiredMembers] - public KeyPermitBaseModel(IEnumerable permissions, KeyPurpose[] purposes, string? name = null) + protected KeyPermitBaseModel(IEnumerable permissions, KeyPurpose[] purposes, string? name = null) { if (permissions.None()) throw new InvalidDataException("This key doesn't have at least one action to be permitted"); diff --git a/InterlockLedger.Rest.Client/Models/NodeDetailsModel.cs b/InterlockLedger.Rest.Client/Models/NodeDetailsModel.cs index dbb34de..b0e07c8 100644 --- a/InterlockLedger.Rest.Client/Models/NodeDetailsModel.cs +++ b/InterlockLedger.Rest.Client/Models/NodeDetailsModel.cs @@ -45,6 +45,7 @@ public class NodeDetailsModel : NodeCommonModel /// /// Other properties the node may have /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "MA0016:Prefer using collection abstraction instead of implementation", Justification = "")] public Dictionary Extensions { get; set; } = []; private static string ToLine(KeyValuePair x) => $"{x.Key}: {x.Value}"; diff --git a/InterlockLedger.Rest.Client/Models/Types/AppPermissions.cs b/InterlockLedger.Rest.Client/Models/Types/AppPermissions.cs index 71c9582..74843de 100644 --- a/InterlockLedger.Rest.Client/Models/Types/AppPermissions.cs +++ b/InterlockLedger.Rest.Client/Models/Types/AppPermissions.cs @@ -95,6 +95,6 @@ internal AppPermissions(string textualRepresentation) { private static ulong AsUlong(string s) => ulong.TryParse(s?.Trim(), out ulong result) ? result : 0; - [GeneratedRegex("^#[0-9]+(,[0-9]+)*$")] + [GeneratedRegex("^#[0-9]+(,[0-9]+)*$", RegexOptions.None, 100)] private static partial Regex AppPermissionsRegex(); } \ No newline at end of file diff --git a/InterlockLedger.Rest.Client/Models/Types/PublishedApp.cs b/InterlockLedger.Rest.Client/Models/Types/PublishedApp.cs index 94f68d0..d5132b4 100644 --- a/InterlockLedger.Rest.Client/Models/Types/PublishedApp.cs +++ b/InterlockLedger.Rest.Client/Models/Types/PublishedApp.cs @@ -62,7 +62,8 @@ public partial class PublishedApp : IComparable, IEquatable=(PublishedApp left, PublishedApp right) => left is null ? right is null : left.CompareTo(right) >= 0; - public int CompareTo(PublishedApp? other) { + public int CompareTo(PublishedApp? other) + { if (other is null) return 1; var idCompare = Id.CompareTo(other.Id); return idCompare != 0 ? idCompare : AppVersion?.CompareTo(other.AppVersion) ?? -1; @@ -78,6 +79,6 @@ public int CompareTo(PublishedApp? other) { private static string Safe(string name) => UnsafeCharsRegex().Replace(name, "_"); - [GeneratedRegex(@"[\s\\/:""<>|\*\?]+")] + [GeneratedRegex(@"[\s\\/:""<>|\*\?]", RegexOptions.None, 100)] private static partial Regex UnsafeCharsRegex(); } \ No newline at end of file diff --git a/InterlockLedger.Rest.Client/Models/VersionJsonConverter.cs b/InterlockLedger.Rest.Client/Models/VersionJsonConverter.cs index e8f3b28..ac8b511 100644 --- a/InterlockLedger.Rest.Client/Models/VersionJsonConverter.cs +++ b/InterlockLedger.Rest.Client/Models/VersionJsonConverter.cs @@ -32,7 +32,7 @@ namespace InterlockLedger.Rest.Client; -internal class VersionJsonConverter : JsonConverter +internal sealed class VersionJsonConverter : JsonConverter { public override Version? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => reader.TokenType switch { diff --git a/InterlockLedger.Rest.Client/V13_7/Extensions/X509Certificate2Extensions.cs b/InterlockLedger.Rest.Client/V14_2_2/Extensions/X509Certificate2Extensions.cs similarity index 98% rename from InterlockLedger.Rest.Client/V13_7/Extensions/X509Certificate2Extensions.cs rename to InterlockLedger.Rest.Client/V14_2_2/Extensions/X509Certificate2Extensions.cs index 52c1238..ea7265b 100644 --- a/InterlockLedger.Rest.Client/V13_7/Extensions/X509Certificate2Extensions.cs +++ b/InterlockLedger.Rest.Client/V14_2_2/Extensions/X509Certificate2Extensions.cs @@ -30,7 +30,7 @@ // // ****************************************************************************************************************************** -namespace InterlockLedger.Rest.Client.V13_7; +namespace InterlockLedger.Rest.Client.V14_2_2; public static class X509Certificate2Extensions { diff --git a/InterlockLedger.Rest.Client/V13_7/Implementations/DocumentRegistryImplementation.cs b/InterlockLedger.Rest.Client/V14_2_2/Implementations/DocumentRegistryImplementation.cs similarity index 54% rename from InterlockLedger.Rest.Client/V13_7/Implementations/DocumentRegistryImplementation.cs rename to InterlockLedger.Rest.Client/V14_2_2/Implementations/DocumentRegistryImplementation.cs index 58880b6..74887b4 100644 --- a/InterlockLedger.Rest.Client/V13_7/Implementations/DocumentRegistryImplementation.cs +++ b/InterlockLedger.Rest.Client/V14_2_2/Implementations/DocumentRegistryImplementation.cs @@ -31,14 +31,20 @@ // ****************************************************************************************************************************** -namespace InterlockLedger.Rest.Client.V13_7; +using System.Globalization; +using System.Linq; +using Microsoft.CodeAnalysis; + +namespace InterlockLedger.Rest.Client.V14_2_2; internal class DocumentRegistryImplementation(RestAbstractNode node) : IDocumentRegistry where ChainType : IRestChain { + private const string _pathPrefix = "/documents"; + private const string _transactionPrefix = $"{_pathPrefix}/transaction"; private readonly RestAbstractNode _node = node.Required(); public Task GetDocumentsUploadConfigurationAsync() - => _node.GetAsync("/documents/configuration"); + => _node.GetAsync(UrlFromParts(_pathPrefix, "configuration")); public async Task?> ListKnownChainsAcceptingTransactionsAsync() { var result = new List(); @@ -57,35 +63,62 @@ internal class DocumentRegistryImplementation(RestAbstractNode?> ListKnownChainsAsync() => (await _node.GetChainsAsync().ConfigureAwait(false)).Safe().Select(ch => ch.Id); + public async Task TransactionBeginAsync(DocumentsBeginTransactionModel transactionStart) { + await ValidateChainAsync(transactionStart.Required().Chain).ConfigureAwait(false); + return await _node.PostAsync(_transactionPrefix, transactionStart).ConfigureAwait(false); + } + public Task TransactionAddItemAsync(string transactionId, string path, string name, string? comment, string contentType, Stream source) + => _node.PostStreamAsync(UrlFromParts(_transactionPrefix, transactionId.Required(), queryItems: BuildAddItemQueryItems(name, comment)), source.Required(), contentType.Required()); + public Task TransactionCommitAsync(string transactionId) + => _node.PostAsync(UrlFromParts(_transactionPrefix, transactionId.Required(), "commit"), body: null); + public Task TransactionStatusAsync(string transactionId) + => _node.GetAsync(UrlFromParts(_transactionPrefix, transactionId.Required())); + public Task RetrieveMetadataAsync(string locator) - => _node.GetAsync(FromLocator(locator.Required(), "metadata")); + => _node.GetAsync(UrlFromParts(_pathPrefix, locator.Required(), "metadata")); Task<(string Name, string ContentType, Stream Content)?> IDocumentRegistry.RetrieveSingleAsync(string locator, int index) - => _node.GetFileReadStreamAsync(FromLocator(locator.Required(), index)); - - Task<(string Name, string ContentType, Stream Content)?> IDocumentRegistry.RetrieveZipAsync(string locator) - => _node.GetFileReadStreamAsync(FromLocator(locator.Required(), "zip"), accept: "application/zip"); + => _node.GetFileReadStreamAsync(UrlFromParts(_pathPrefix, locator.Required(), index.ToString("0", CultureInfo.InvariantCulture))); - public Task TransactionAddItemAsync(string transactionId, string path, string name, string? comment, string contentType, Stream source) - => _node.PostStreamAsync($"/documents/transaction/{transactionId.Required()}?name={HttpUtility.UrlEncode(name.Required())}&comment={HttpUtility.UrlEncode(comment)}", source.Required(), contentType.Required()); + Task<(string Name, string ContentType, Stream Content)?> IDocumentRegistry.RetrieveZipAsync(string locator, bool omitFromParentControlFile, bool omitToChildrenControlFile) + => _node.GetFileReadStreamAsync(UrlFromParts(_pathPrefix, locator, "zip", BuildZipQueryItems(omitFromParentControlFile, omitToChildrenControlFile)), accept: "application/zip"); - public async Task TransactionBeginAsync(DocumentsBeginTransactionModel transactionStart) { - await ValidateChainAsync(transactionStart.Required().Chain); - return await _node.PostAsync("/documents/transaction", transactionStart); + public static string UrlFromParts(string urlPrefix, string id, string? selector = null, Dictionary? queryItems = null) { + var urlBuilder = new StringBuilder(urlPrefix).Append('/') + .Append(HttpUtility.UrlEncode(id.Required())); + if (selector.IsNonBlank()) + urlBuilder.Append('/') + .Append(HttpUtility.UrlEncode(selector)); + if (queryItems.SafeAny()) { + char c = '?'; + foreach (var item in queryItems) { + urlBuilder.Append(c) + .Append(HttpUtility.UrlEncode(item.Key)) + .Append('=') + .Append(HttpUtility.UrlEncode(item.Value)); + c = '&'; + } + } + return urlBuilder.ToString(); + } + private static Dictionary BuildAddItemQueryItems(string name, string? comment) + => new(StringComparer.Ordinal) { + { nameof(name), name }, + { nameof(comment), comment!}, + }; + private static Dictionary BuildZipQueryItems(bool omitFromParentControlFile, bool omitToChildrenControlFile) { + var queryItems = new Dictionary(StringComparer.Ordinal); + if (omitFromParentControlFile) + queryItems.Add(nameof(omitFromParentControlFile), "true"); + if (omitToChildrenControlFile) + queryItems.Add(nameof(omitToChildrenControlFile), "true"); + return queryItems; } - - public Task TransactionCommitAsync(string transactionId) - => _node.PostAsync($"/documents/transaction/{transactionId.Required()}/commit", null); - - public Task TransactionStatusAsync(string transactionId) - => _node.GetAsync($"/documents/transaction/{transactionId.Required()}"); - - private static string FromLocator(string locator, T selector) => $"/documents/{HttpUtility.UrlEncode(locator)}/{selector}"; private async Task ValidateChainAsync(string chain) { - if ((await ListKnownChainsAsync()).None(id => id == chain)) - throw new ArgumentException($"Chain '{chain}' is unknown"); - if ((await ListKnownChainsAcceptingTransactionsAsync()).None(id => id == chain)) - throw new ArgumentException($"Chain '{chain}' can't accept document transactions"); + if ((await ListKnownChainsAsync().ConfigureAwait(false)).None(id => string.Equals(id, chain, StringComparison.Ordinal))) + throw new ArgumentException($"Chain '{chain}' is unknown", nameof(chain)); + if ((await ListKnownChainsAcceptingTransactionsAsync().ConfigureAwait(false)).None(id => string.Equals(id, chain, StringComparison.Ordinal))) + throw new ArgumentException($"Chain '{chain}' can't accept document transactions", nameof(chain)); } } \ No newline at end of file diff --git a/InterlockLedger.Rest.Client/V13_7/Implementations/JsonStoreImplementation.cs b/InterlockLedger.Rest.Client/V14_2_2/Implementations/JsonStoreImplementation.cs similarity index 88% rename from InterlockLedger.Rest.Client/V13_7/Implementations/JsonStoreImplementation.cs rename to InterlockLedger.Rest.Client/V14_2_2/Implementations/JsonStoreImplementation.cs index 99b0612..80b1a3f 100644 --- a/InterlockLedger.Rest.Client/V13_7/Implementations/JsonStoreImplementation.cs +++ b/InterlockLedger.Rest.Client/V14_2_2/Implementations/JsonStoreImplementation.cs @@ -30,11 +30,11 @@ // // ****************************************************************************************************************************** -namespace InterlockLedger.Rest.Client.V13_7; +namespace InterlockLedger.Rest.Client.V14_2_2; -internal class JsonStoreImplementation : IJsonStore +internal sealed class JsonStoreImplementation : IJsonStore { - public JsonStoreImplementation(RestChainV13_7 parent) { + public JsonStoreImplementation(RestChainV14_2_2 parent) { _parent = parent.Required(); _node = _parent._node; _id = _parent.Id; @@ -46,7 +46,7 @@ public JsonStoreImplementation(RestChainV13_7 parent) { public Task RetrieveAllowedReadersAsync(string chain, string? contextId = null, bool lastToFirst = false, int page = 0, int pageSize = 10) => _node.GetAsync($"/jsonDocuments@{_id}/allow?lastToFirst={lastToFirst}&page={page}&pageSize={pageSize}&contextId={contextId}"); - protected readonly string _id; - private readonly RestChainV13_7 _parent; - protected readonly RestAbstractNode _node; + private readonly string _id; + private readonly RestChainV14_2_2 _parent; + private readonly RestAbstractNode _node; } \ No newline at end of file diff --git a/InterlockLedger.Rest.Client/V13_7/Implementations/OpaqueStoreImplementation.cs b/InterlockLedger.Rest.Client/V14_2_2/Implementations/OpaqueStoreImplementation.cs similarity index 93% rename from InterlockLedger.Rest.Client/V13_7/Implementations/OpaqueStoreImplementation.cs rename to InterlockLedger.Rest.Client/V14_2_2/Implementations/OpaqueStoreImplementation.cs index 846c865..b790996 100644 --- a/InterlockLedger.Rest.Client/V13_7/Implementations/OpaqueStoreImplementation.cs +++ b/InterlockLedger.Rest.Client/V14_2_2/Implementations/OpaqueStoreImplementation.cs @@ -31,11 +31,11 @@ // ****************************************************************************************************************************** -namespace InterlockLedger.Rest.Client.V13_7; +namespace InterlockLedger.Rest.Client.V14_2_2; -internal class OpaqueStoreImplementation : IOpaqueStore +internal sealed class OpaqueStoreImplementation : IOpaqueStore { - public OpaqueStoreImplementation(RestAbstractNode node, RestChainV13_7 chain) { + public OpaqueStoreImplementation(RestAbstractNode node, RestChainV14_2_2 chain) { _chain = chain.Required(); _node = node; _id = _chain.Id; @@ -66,6 +66,6 @@ public OpaqueStoreImplementation(RestAbstractNode node, RestChai private readonly string _id; - private readonly RestChainV13_7 _chain; - private readonly RestAbstractNode _node; + private readonly RestChainV14_2_2 _chain; + private readonly RestAbstractNode _node; } diff --git a/InterlockLedger.Rest.Client/V13_7/Interfaces/IDocumentRegistry.cs b/InterlockLedger.Rest.Client/V14_2_2/Interfaces/IDocumentRegistry.cs similarity index 95% rename from InterlockLedger.Rest.Client/V13_7/Interfaces/IDocumentRegistry.cs rename to InterlockLedger.Rest.Client/V14_2_2/Interfaces/IDocumentRegistry.cs index e172f2f..c5f2c10 100644 --- a/InterlockLedger.Rest.Client/V13_7/Interfaces/IDocumentRegistry.cs +++ b/InterlockLedger.Rest.Client/V14_2_2/Interfaces/IDocumentRegistry.cs @@ -30,7 +30,7 @@ // // ****************************************************************************************************************************** -namespace InterlockLedger.Rest.Client.V13_7; +namespace InterlockLedger.Rest.Client.V14_2_2; public interface IDocumentRegistry { @@ -44,13 +44,13 @@ public interface IDocumentRegistry Task<(string Name, string ContentType, Stream Content)?> RetrieveSingleAsync(string locator, int index); - Task<(string Name, string ContentType, Stream Content)?> RetrieveZipAsync(string locator); + Task<(string Name, string ContentType, Stream Content)?> RetrieveZipAsync(string locator, bool omitFromParentControlFile = false, bool omitToChildrenControlFile = false); Task TransactionAddItemAsync(string transactionId, string path, string name, string? comment, string contentType, Stream source); Task TransactionAddItemAsync(string transactionId, DirectoryInfo baseDirectory, FileInfo file, string? comment, string contentType) => !file.Required().FullName.StartsWith(baseDirectory.Required().FullName, StringComparison.InvariantCultureIgnoreCase) - ? throw new ArgumentException(message: $"File ´{file.FullName}´ is not inside directory '{baseDirectory.FullName}' ") + ? throw new ArgumentException(message: $"File ´{file.FullName}´ is not inside directory '{baseDirectory.FullName}' ", nameof(file)) : TransactionAddItemAsync(transactionId.Required(), Path.GetRelativePath(baseDirectory.FullName, file.DirectoryName.Required()), file.Name, diff --git a/InterlockLedger.Rest.Client/V13_7/Interfaces/IJsonStore.cs b/InterlockLedger.Rest.Client/V14_2_2/Interfaces/IJsonStore.cs similarity index 97% rename from InterlockLedger.Rest.Client/V13_7/Interfaces/IJsonStore.cs rename to InterlockLedger.Rest.Client/V14_2_2/Interfaces/IJsonStore.cs index 5a5cd60..9642e70 100644 --- a/InterlockLedger.Rest.Client/V13_7/Interfaces/IJsonStore.cs +++ b/InterlockLedger.Rest.Client/V14_2_2/Interfaces/IJsonStore.cs @@ -30,7 +30,7 @@ // // ****************************************************************************************************************************** -namespace InterlockLedger.Rest.Client.V13_7; +namespace InterlockLedger.Rest.Client.V14_2_2; public interface IJsonStore { diff --git a/InterlockLedger.Rest.Client/V13_7/Interfaces/INodeWithDocumentRegistry.cs b/InterlockLedger.Rest.Client/V14_2_2/Interfaces/INodeWithDocumentRegistry.cs similarity index 97% rename from InterlockLedger.Rest.Client/V13_7/Interfaces/INodeWithDocumentRegistry.cs rename to InterlockLedger.Rest.Client/V14_2_2/Interfaces/INodeWithDocumentRegistry.cs index 559749e..87b940b 100644 --- a/InterlockLedger.Rest.Client/V13_7/Interfaces/INodeWithDocumentRegistry.cs +++ b/InterlockLedger.Rest.Client/V14_2_2/Interfaces/INodeWithDocumentRegistry.cs @@ -30,7 +30,7 @@ // // ****************************************************************************************************************************** -namespace InterlockLedger.Rest.Client.V13_7; +namespace InterlockLedger.Rest.Client.V14_2_2; public interface INodeWithDocumentRegistry { diff --git a/InterlockLedger.Rest.Client/V13_7/Interfaces/IOpaqueStore.cs b/InterlockLedger.Rest.Client/V14_2_2/Interfaces/IOpaqueStore.cs similarity index 98% rename from InterlockLedger.Rest.Client/V13_7/Interfaces/IOpaqueStore.cs rename to InterlockLedger.Rest.Client/V14_2_2/Interfaces/IOpaqueStore.cs index ce99966..49654d1 100644 --- a/InterlockLedger.Rest.Client/V13_7/Interfaces/IOpaqueStore.cs +++ b/InterlockLedger.Rest.Client/V14_2_2/Interfaces/IOpaqueStore.cs @@ -31,7 +31,7 @@ // ****************************************************************************************************************************** -namespace InterlockLedger.Rest.Client.V13_7; +namespace InterlockLedger.Rest.Client.V14_2_2; public interface IOpaqueStore { diff --git a/InterlockLedger.Rest.Client/V13_7/Models/AllowedReadersRecordModel.cs b/InterlockLedger.Rest.Client/V14_2_2/Models/AllowedReadersRecordModel.cs similarity index 97% rename from InterlockLedger.Rest.Client/V13_7/Models/AllowedReadersRecordModel.cs rename to InterlockLedger.Rest.Client/V14_2_2/Models/AllowedReadersRecordModel.cs index 71a1cab..9037150 100644 --- a/InterlockLedger.Rest.Client/V13_7/Models/AllowedReadersRecordModel.cs +++ b/InterlockLedger.Rest.Client/V14_2_2/Models/AllowedReadersRecordModel.cs @@ -30,7 +30,7 @@ // // ****************************************************************************************************************************** -namespace InterlockLedger.Rest.Client.V13_7; +namespace InterlockLedger.Rest.Client.V14_2_2; /// /// Simplified model for allowed readers record diff --git a/InterlockLedger.Rest.Client/V13_7/Models/DocumentsBeginTransactionModel.cs b/InterlockLedger.Rest.Client/V14_2_2/Models/DocumentsBeginTransactionModel.cs similarity index 93% rename from InterlockLedger.Rest.Client/V13_7/Models/DocumentsBeginTransactionModel.cs rename to InterlockLedger.Rest.Client/V14_2_2/Models/DocumentsBeginTransactionModel.cs index 0395db8..a539e1b 100644 --- a/InterlockLedger.Rest.Client/V13_7/Models/DocumentsBeginTransactionModel.cs +++ b/InterlockLedger.Rest.Client/V14_2_2/Models/DocumentsBeginTransactionModel.cs @@ -32,7 +32,7 @@ -namespace InterlockLedger.Rest.Client.V13_7; +namespace InterlockLedger.Rest.Client.V14_2_2; /// /// To specify parameters for starting a transaction to store many documents in a single InterlockLedger record @@ -100,4 +100,9 @@ public sealed class DocumentsBeginTransactionModel /// If AllowChildren is true the textual comment to be contained in the '.to-children' control file /// public string? ToChildrenComment { get; set; } + + /// + /// Content bytes for the .from-parent control file when a full locator is NOT passed in Previous. Otherwise content bytes are retrieved from the previous version and this data is ignored + /// + public byte[]? FromParentContent { get; set; } } \ No newline at end of file diff --git a/InterlockLedger.Rest.Client/V13_7/Models/DocumentsMetadataModel.cs b/InterlockLedger.Rest.Client/V14_2_2/Models/DocumentsMetadataModel.cs similarity index 77% rename from InterlockLedger.Rest.Client/V13_7/Models/DocumentsMetadataModel.cs rename to InterlockLedger.Rest.Client/V14_2_2/Models/DocumentsMetadataModel.cs index 46d0917..e39824c 100644 --- a/InterlockLedger.Rest.Client/V13_7/Models/DocumentsMetadataModel.cs +++ b/InterlockLedger.Rest.Client/V14_2_2/Models/DocumentsMetadataModel.cs @@ -32,7 +32,7 @@ -namespace InterlockLedger.Rest.Client.V13_7; +namespace InterlockLedger.Rest.Client.V14_2_2; /// /// Model for metadata associated to a Multi-Document Storage Locator @@ -42,7 +42,7 @@ public class DocumentsMetadataModel /// /// Replicates RecordReference record info to simplify REST API usage /// - public string? RecordReference { get; } + public string? RecordReference { get; set; } /// /// Replicates CreationTime record info to simplify REST API usage @@ -80,13 +80,18 @@ public class DocumentsMetadataModel /// public IEnumerable? PublicDirectory { get; set; } - public override string ToString() => $@"{nameof(DocumentsMetadataModel)} - {nameof(Comment)} : {Comment} - {nameof(Compression)} : {Compression} - {nameof(Encryption)} : {Encryption} - {nameof(EncryptionParameters)} : {EncryptionParameters} - {nameof(PublicDirectory)} : [{_joiner}{PublicDirectory.JoinedBy(_joiner)} - ]"; + public override string ToString() => $$""" + {{nameof(DocumentsMetadataModel)}} : { + {{nameof(Comment)}} : {{Comment}} + {{nameof(Compression)}} : {{Compression}} + {{nameof(Encryption)}} : {{Encryption}} + {{nameof(EncryptionParameters)}} : {{EncryptionParameters}} + {{nameof(PublicDirectory)}} : [{{_joiner}}{{PublicDirectory.JoinedBy(_joiner)}} + ] + {{nameof(CreationTime)}} : {{CreationTime:u}} + {{nameof(RecordReference)}} : {{RecordReference}} + } + """; /// /// Entry for each stored document in this MultiDocument set @@ -116,15 +121,23 @@ public class DirectoryEntry /// /// Hash SHA256 of Document (file) content /// - public byte[]? HashSHA256 { get; } + public byte[]? HashSHA256 { get; set; } /// /// Size in bytes of Document (file) /// - public ulong? Size { get; } - - public override string ToString() => $@"{nameof(DirectoryEntry)} {{{_joiner} {nameof(Comment)} : {Comment}{_joiner} {nameof(MimeType)} : {MimeType}{_joiner} {nameof(Name)} : {Name}{_joiner} {nameof(Path)} : {Path}{_joiner}}} -"; + public ulong? Size { get; set; } + + public override string ToString() => $$""" + {{nameof(DirectoryEntry)}}: { + {{nameof(Comment)}} : {{Comment}} + {{nameof(MimeType)}} : {{MimeType}} + {{nameof(Name)}} : {{Name}} + {{nameof(Path)}} : {{Path}} + {{nameof(HashSHA256)}} : {{HashSHA256}} + {{nameof(Size)}} : {{Size}} + } + """; } /// @@ -142,8 +155,12 @@ public class Parameters /// public byte[]? Salt { get; set; } - public override string ToString() => $@"{nameof(Parameters)} {{{_joiner} {nameof(Iterations)} : {Iterations}{_joiner} {nameof(Salt)} : '{Convert.ToBase64String(Salt ?? [])}'{_joiner}}} -"; + public override string ToString() => $$""" + {{nameof(Parameters)}} { + {{nameof(Iterations)}} : {{Iterations}} + {{nameof(Salt)}} : '{{Convert.ToBase64String(Salt ?? [])}}' + } + """; } private static readonly string _joiner = Environment.NewLine + " "; diff --git a/InterlockLedger.Rest.Client/V13_7/Models/DocumentsTransactionModel.cs b/InterlockLedger.Rest.Client/V14_2_2/Models/DocumentsTransactionModel.cs similarity index 83% rename from InterlockLedger.Rest.Client/V13_7/Models/DocumentsTransactionModel.cs rename to InterlockLedger.Rest.Client/V14_2_2/Models/DocumentsTransactionModel.cs index 5ed46d8..91a1f27 100644 --- a/InterlockLedger.Rest.Client/V13_7/Models/DocumentsTransactionModel.cs +++ b/InterlockLedger.Rest.Client/V14_2_2/Models/DocumentsTransactionModel.cs @@ -30,7 +30,7 @@ // // ****************************************************************************************************************************** -namespace InterlockLedger.Rest.Client.V13_7; +namespace InterlockLedger.Rest.Client.V14_2_2; public class DocumentsTransactionModel { @@ -94,4 +94,21 @@ public class DocumentsTransactionModel /// Id of the transaction to use when uploading each file and committing the transaction /// public string? TransactionId { get; set; } + + public override string ToString() => $$""" + {{nameof(DocumentsTransactionModel)}} : { + {{nameof(CanCommitNow)}} : {{CanCommitNow}} + {{nameof(Chain)}} : {{Chain}} + {{nameof(Comment)}} : {{Comment}} + {{nameof(Compression)}} : {{Compression}} + {{nameof(CountOfUploadedDocuments)}} : {{CountOfUploadedDocuments}} + {{nameof(DocumentNames)}} : [{{DocumentNames.JoinedBy(", ")}}] + {{nameof(Encryption)}} : {{Encryption}} + {{nameof(GeneratePublicDirectory)}} : {{GeneratePublicDirectory}} + {{nameof(Previous)}} : {{Previous}} + {{nameof(TimeOutLimit)}} : {{TimeOutLimit:u}} + {{nameof(TransactionId)}} : {{TransactionId}} + } + """; + } \ No newline at end of file diff --git a/InterlockLedger.Rest.Client/V13_7/Models/DocumentsUploadConfiguration.cs b/InterlockLedger.Rest.Client/V14_2_2/Models/DocumentsUploadConfiguration.cs similarity index 98% rename from InterlockLedger.Rest.Client/V13_7/Models/DocumentsUploadConfiguration.cs rename to InterlockLedger.Rest.Client/V14_2_2/Models/DocumentsUploadConfiguration.cs index ed0d1dc..b8e3816 100644 --- a/InterlockLedger.Rest.Client/V13_7/Models/DocumentsUploadConfiguration.cs +++ b/InterlockLedger.Rest.Client/V14_2_2/Models/DocumentsUploadConfiguration.cs @@ -30,7 +30,7 @@ // // ****************************************************************************************************************************** -namespace InterlockLedger.Rest.Client.V13_7; +namespace InterlockLedger.Rest.Client.V14_2_2; public sealed record DocumentsUploadConfiguration { diff --git a/InterlockLedger.Rest.Client/V13_7/Models/EncryptedTextModel.cs b/InterlockLedger.Rest.Client/V14_2_2/Models/EncryptedTextModel.cs similarity index 97% rename from InterlockLedger.Rest.Client/V13_7/Models/EncryptedTextModel.cs rename to InterlockLedger.Rest.Client/V14_2_2/Models/EncryptedTextModel.cs index a846171..4341757 100644 --- a/InterlockLedger.Rest.Client/V13_7/Models/EncryptedTextModel.cs +++ b/InterlockLedger.Rest.Client/V14_2_2/Models/EncryptedTextModel.cs @@ -30,7 +30,7 @@ // // ****************************************************************************************************************************** -namespace InterlockLedger.Rest.Client.V13_7; +namespace InterlockLedger.Rest.Client.V14_2_2; public sealed class EncryptedTextModel { diff --git a/InterlockLedger.Rest.Client/V13_7/Models/EncryptedTextModelExtensions.cs b/InterlockLedger.Rest.Client/V14_2_2/Models/EncryptedTextModelExtensions.cs similarity index 70% rename from InterlockLedger.Rest.Client/V13_7/Models/EncryptedTextModelExtensions.cs rename to InterlockLedger.Rest.Client/V14_2_2/Models/EncryptedTextModelExtensions.cs index c60c703..fccfcbc 100644 --- a/InterlockLedger.Rest.Client/V13_7/Models/EncryptedTextModelExtensions.cs +++ b/InterlockLedger.Rest.Client/V14_2_2/Models/EncryptedTextModelExtensions.cs @@ -30,7 +30,7 @@ // // ****************************************************************************************************************************** -namespace InterlockLedger.Rest.Client.V13_7; +namespace InterlockLedger.Rest.Client.V14_2_2; public static class EncryptedTextModelExtensions { @@ -45,11 +45,11 @@ public static class EncryptedTextModelExtensions return "ERROR: Non-RSA certificate is not currently supported"; if (model.ReadingKeys.SkipNulls().None()) return "ERROR: No reading keys able to decode EncryptedText"; - var authorizedKey = model.ReadingKeys.FirstOrDefault(rk => rk.PublicKeyHash == pubKeyHash && rk.ReaderId == certKeyId); + var authorizedKey = model.ReadingKeys.FirstOrDefault(rk => string.Equals(rk.PublicKeyHash, pubKeyHash, StringComparison.OrdinalIgnoreCase) && string.Equals(rk.ReaderId, certKeyId, StringComparison.OrdinalIgnoreCase)); if (authorizedKey is null) return "ERROR: Your key does not match one of the authorized reading keys"; string cipher = model.Cipher.WithDefault("AES256").ToUpperInvariant(); - if (cipher != "AES256") + if (!string.Equals(cipher, "AES256", StringComparison.OrdinalIgnoreCase)) return $"ERROR: Cipher {cipher} is not currently supported"; if (model.CipherText.None()) return null; @@ -63,35 +63,35 @@ public static class EncryptedTextModelExtensions return "ERROR: Something went wrong while decrypting the content. Unexpected initial bytes"; var skipTagAndSize = jsonBytes[1..].ILIntDecode().ILIntSize() + 1; return jsonBytes[skipTagAndSize..].AsUTF8String(); + } + + private static byte[] AES256Decrypt(byte[] cipherData, byte[] key, byte[] iv) { + using var source = new MemoryStream(cipherData.Required()); + using var algorithm = Aes.Create(); + algorithm.KeySize = 256; + algorithm.BlockSize = 128; + algorithm.Mode = CipherMode.CBC; + algorithm.Key = key; + algorithm.IV = iv; + algorithm.Padding = PaddingMode.Zeros; + using var cs = new CryptoStream(source, algorithm.CreateDecryptor(), CryptoStreamMode.Read); + using var dest = new MemoryStream(); + cs.CopyTo(dest); + return dest.ToArray(); + } - static byte[] RSADecrypt(RSA rsaAlgo, byte[] data, int maxRetries = 3) { - int retries = maxRetries; - while (true) + private static byte[] RSADecrypt(RSA rsaAlgo, byte[] data, int maxRetries = 3) { + int retries = maxRetries; + while (true) + try { try { - try { - return rsaAlgo.Decrypt(data, RSAEncryptionPadding.OaepSHA1); - } catch (CryptographicException) { - return rsaAlgo.Decrypt(data, RSAEncryptionPadding.CreateOaep(HashAlgorithmName.MD5)); - } - } catch (CryptographicException e) { - if (retries-- <= 0) - throw new InvalidOperationException($"Failed to decrypt data with current parameters after {maxRetries} retries", e); + return rsaAlgo.Decrypt(data, RSAEncryptionPadding.OaepSHA1); + } catch (CryptographicException) { + return rsaAlgo.Decrypt(data, RSAEncryptionPadding.CreateOaep(HashAlgorithmName.MD5)); } - } - - static byte[] AES256Decrypt(byte[] cipherData, byte[] key, byte[] iv) { - using var source = new MemoryStream(cipherData.Required()); - using var algorithm = Aes.Create(); - algorithm.KeySize = 256; - algorithm.BlockSize = 128; - algorithm.Mode = CipherMode.CBC; - algorithm.Key = key; - algorithm.IV = iv; - algorithm.Padding = PaddingMode.Zeros; - using var cs = new CryptoStream(source, algorithm.CreateDecryptor(), CryptoStreamMode.Read); - using var dest = new MemoryStream(); - cs.CopyTo(dest); - return dest.ToArray(); - } + } catch (CryptographicException e) { + if (retries-- <= 0) + throw new InvalidOperationException($"Failed to decrypt data with current parameters after {maxRetries} retries", e); + } } } \ No newline at end of file diff --git a/InterlockLedger.Rest.Client/V13_7/Models/JsonDocumentModel.cs b/InterlockLedger.Rest.Client/V14_2_2/Models/JsonDocumentModel.cs similarity index 97% rename from InterlockLedger.Rest.Client/V13_7/Models/JsonDocumentModel.cs rename to InterlockLedger.Rest.Client/V14_2_2/Models/JsonDocumentModel.cs index a8612a4..205e427 100644 --- a/InterlockLedger.Rest.Client/V13_7/Models/JsonDocumentModel.cs +++ b/InterlockLedger.Rest.Client/V14_2_2/Models/JsonDocumentModel.cs @@ -30,7 +30,7 @@ // // ****************************************************************************************************************************** -namespace InterlockLedger.Rest.Client.V13_7; +namespace InterlockLedger.Rest.Client.V14_2_2; public sealed class JsonDocumentModel : RecordModelBase { diff --git a/InterlockLedger.Rest.Client/V13_7/Models/OpaqueRecordModel.cs b/InterlockLedger.Rest.Client/V14_2_2/Models/OpaqueRecordModel.cs similarity index 98% rename from InterlockLedger.Rest.Client/V13_7/Models/OpaqueRecordModel.cs rename to InterlockLedger.Rest.Client/V14_2_2/Models/OpaqueRecordModel.cs index 14fa54c..88b9de3 100644 --- a/InterlockLedger.Rest.Client/V13_7/Models/OpaqueRecordModel.cs +++ b/InterlockLedger.Rest.Client/V14_2_2/Models/OpaqueRecordModel.cs @@ -31,7 +31,7 @@ // ****************************************************************************************************************************** -namespace InterlockLedger.Rest.Client.V13_7; +namespace InterlockLedger.Rest.Client.V14_2_2; /// /// Generic opaque record diff --git a/InterlockLedger.Rest.Client/V13_7/Models/PageOfAllowedReadersRecordModel.cs b/InterlockLedger.Rest.Client/V14_2_2/Models/PageOfAllowedReadersRecordModel.cs similarity index 97% rename from InterlockLedger.Rest.Client/V13_7/Models/PageOfAllowedReadersRecordModel.cs rename to InterlockLedger.Rest.Client/V14_2_2/Models/PageOfAllowedReadersRecordModel.cs index 757f1a5..6984409 100644 --- a/InterlockLedger.Rest.Client/V13_7/Models/PageOfAllowedReadersRecordModel.cs +++ b/InterlockLedger.Rest.Client/V14_2_2/Models/PageOfAllowedReadersRecordModel.cs @@ -30,7 +30,7 @@ // // ****************************************************************************************************************************** -namespace InterlockLedger.Rest.Client.V13_7; +namespace InterlockLedger.Rest.Client.V14_2_2; public class PageOfAllowedReadersRecordModel : PageOf { diff --git a/InterlockLedger.Rest.Client/V13_7/Models/PageOfOpaqueRecordsModel.cs b/InterlockLedger.Rest.Client/V14_2_2/Models/PageOfOpaqueRecordsModel.cs similarity index 97% rename from InterlockLedger.Rest.Client/V13_7/Models/PageOfOpaqueRecordsModel.cs rename to InterlockLedger.Rest.Client/V14_2_2/Models/PageOfOpaqueRecordsModel.cs index 7102a0c..cc0b610 100644 --- a/InterlockLedger.Rest.Client/V13_7/Models/PageOfOpaqueRecordsModel.cs +++ b/InterlockLedger.Rest.Client/V14_2_2/Models/PageOfOpaqueRecordsModel.cs @@ -31,7 +31,7 @@ // ****************************************************************************************************************************** -namespace InterlockLedger.Rest.Client.V13_7; +namespace InterlockLedger.Rest.Client.V14_2_2; public class PageOfOpaqueRecordsModel : PageOf { diff --git a/InterlockLedger.Rest.Client/V13_7/Models/ReaderModel.cs b/InterlockLedger.Rest.Client/V14_2_2/Models/ReaderModel.cs similarity index 97% rename from InterlockLedger.Rest.Client/V13_7/Models/ReaderModel.cs rename to InterlockLedger.Rest.Client/V14_2_2/Models/ReaderModel.cs index 684fcfe..de605ff 100644 --- a/InterlockLedger.Rest.Client/V13_7/Models/ReaderModel.cs +++ b/InterlockLedger.Rest.Client/V14_2_2/Models/ReaderModel.cs @@ -30,7 +30,7 @@ // // ****************************************************************************************************************************** -namespace InterlockLedger.Rest.Client.V13_7; +namespace InterlockLedger.Rest.Client.V14_2_2; public class ReaderModel { diff --git a/InterlockLedger.Rest.Client/V13_7/Models/ReadingKeyModel.cs b/InterlockLedger.Rest.Client/V14_2_2/Models/ReadingKeyModel.cs similarity index 97% rename from InterlockLedger.Rest.Client/V13_7/Models/ReadingKeyModel.cs rename to InterlockLedger.Rest.Client/V14_2_2/Models/ReadingKeyModel.cs index 328a6a2..96bc6d9 100644 --- a/InterlockLedger.Rest.Client/V13_7/Models/ReadingKeyModel.cs +++ b/InterlockLedger.Rest.Client/V14_2_2/Models/ReadingKeyModel.cs @@ -30,7 +30,7 @@ // // ****************************************************************************************************************************** -namespace InterlockLedger.Rest.Client.V13_7; +namespace InterlockLedger.Rest.Client.V14_2_2; public sealed class ReadingKeyModel { diff --git a/InterlockLedger.Rest.Client/V13_7/RestChainV13_7.cs b/InterlockLedger.Rest.Client/V14_2_2/RestChainV14_2_2.cs similarity index 90% rename from InterlockLedger.Rest.Client/V13_7/RestChainV13_7.cs rename to InterlockLedger.Rest.Client/V14_2_2/RestChainV14_2_2.cs index 700d416..e3d5cc6 100644 --- a/InterlockLedger.Rest.Client/V13_7/RestChainV13_7.cs +++ b/InterlockLedger.Rest.Client/V14_2_2/RestChainV14_2_2.cs @@ -30,15 +30,15 @@ // // ****************************************************************************************************************************** -namespace InterlockLedger.Rest.Client.V13_7; +namespace InterlockLedger.Rest.Client.V14_2_2; -public class RestChainV13_7 : RestAbstractChain +public class RestChainV14_2_2 : RestAbstractChain { public IJsonStore JsonStore { get; } public IOpaqueStore OpaqueStore { get; } - internal RestChainV13_7(RestNodeV13_7 node, ChainIdModel chainId) : base(node, chainId) { + internal RestChainV14_2_2(RestNodeV14_2_2 node, ChainIdModel chainId) : base(node, chainId) { JsonStore = new JsonStoreImplementation(this); OpaqueStore = new OpaqueStoreImplementation(node, this); } diff --git a/InterlockLedger.Rest.Client/V13_7/RestNodeV13_7.cs b/InterlockLedger.Rest.Client/V14_2_2/RestNodeV14_2_2.cs similarity index 75% rename from InterlockLedger.Rest.Client/V13_7/RestNodeV13_7.cs rename to InterlockLedger.Rest.Client/V14_2_2/RestNodeV14_2_2.cs index f00c783..7846ab5 100644 --- a/InterlockLedger.Rest.Client/V13_7/RestNodeV13_7.cs +++ b/InterlockLedger.Rest.Client/V14_2_2/RestNodeV14_2_2.cs @@ -30,23 +30,23 @@ // // ****************************************************************************************************************************** -namespace InterlockLedger.Rest.Client.V13_7; +namespace InterlockLedger.Rest.Client.V14_2_2; -public class RestNodeV13_7 : RestAbstractNode, INodeWithDocumentRegistry +public class RestNodeV14_2_2 : RestAbstractNode, INodeWithDocumentRegistry { - public RestNodeV13_7(X509Certificate2 x509Certificate, NetworkPredefinedPorts networkId, string address) + public RestNodeV14_2_2(X509Certificate2 x509Certificate, NetworkPredefinedPorts networkId, string address) : base(x509Certificate, networkId, address) { } - public RestNodeV13_7(X509Certificate2 x509Certificate, ushort port, string address) + public RestNodeV14_2_2(X509Certificate2 x509Certificate, ushort port, string address) : base(x509Certificate, port, address) { } - public RestNodeV13_7(string certFile, string certPassword, NetworkPredefinedPorts networkId, string address) + public RestNodeV14_2_2(string certFile, string certPassword, NetworkPredefinedPorts networkId, string address) : base(certFile, certPassword, networkId, address) { } - public RestNodeV13_7(string certFile, string certPassword, ushort port, string address) + public RestNodeV14_2_2(string certFile, string certPassword, ushort port, string address) : base(certFile, certPassword, port, address) { } - protected internal override RestChainV13_7 BuildChain(ChainIdModel c) => new(this, c.Required()); - public IDocumentRegistry DocumentRegistry => _documentRegistry ??= new DocumentRegistryImplementation(this); + protected internal override RestChainV14_2_2 BuildChain(ChainIdModel c) => new(this, c.Required()); + public IDocumentRegistry DocumentRegistry => _documentRegistry ??= new DocumentRegistryImplementation(this); private IDocumentRegistry? _documentRegistry; diff --git a/rest_client/AbstractUsing.cs b/rest_client/AbstractUsing.cs index 9b5aa87..2e7601a 100644 --- a/rest_client/AbstractUsing.cs +++ b/rest_client/AbstractUsing.cs @@ -33,19 +33,15 @@ using System.IO.Compression; using InterlockLedger.Rest.Client; using InterlockLedger.Rest.Client.Abstractions; -using InterlockLedger.Rest.Client.V13_7; +using InterlockLedger.Rest.Client.V14_2_2; namespace rest_client; public abstract class AbstractUsing(RestAbstractNode node) where T : IRestChain { - public static byte[] Content { get; } = Encoding.UTF8.GetBytes("Nothing to see here"); protected readonly RestAbstractNode _node = node.Required(); protected abstract string Version { get; } - protected Task AddRecordAsJsonAsync(T chain, ulong appId, ulong action, object json) - => chain.RecordsAsJson.AddAsync(new NewRecordModelAsJson() { ApplicationId = appId, PayloadTagId = action, Json = json }); - protected Task AddRecordAsync(T chain, ulong appId, params byte[] payload) => chain.Records.AddRecordAsync(new NewRecordModel() { ApplicationId = appId, PayloadBytes = payload }); @@ -60,43 +56,50 @@ protected Task AddRecordAsync(T chain, ulong appId, params byte[] p protected internal async Task ExerciseAsync(bool write) { Console.WriteLine($"Client connected to {_node.BaseUri} using certificate '{_node.CertificateName}' via API version {Version}"); + var apiCertificate = new CertificatePermitModel(_node.Certificate, + [new AppPermissions(1), new AppPermissions(2), new AppPermissions(3), new AppPermissions(4), new AppPermissions(5), new AppPermissions(8), new AppPermissions(13)], + [KeyPurpose.Protocol, KeyPurpose.Action, KeyPurpose.KeyManagement, KeyPurpose.ForceInterlock]); var documentRegistry = (_node is INodeWithDocumentRegistry node) ? node.DocumentRegistry : null; - if (write) - await CreateOneChainAsync(); - Console.WriteLine(await _node.GetDetailsAsync()); - if (documentRegistry is not null) - Console.WriteLine($" {documentRegistry.GetDocumentsUploadConfigurationAsync().Result}"); - var apps = await _node.Network.GetAppsAsync(); + ChainCreatedModel createdChain = null; + if (write) + createdChain = await CreateOneChainAsync(apiCertificate).ConfigureAwait(false); + Console.WriteLine(await _node.GetDetailsAsync().ConfigureAwait(false)); + if (documentRegistry is not null) { + Console.WriteLine($" {await documentRegistry.GetDocumentsUploadConfigurationAsync().ConfigureAwait(false)}"); + Console.WriteLine($"MultiDocument Chains\n-- {(await documentRegistry.ListKnownChainsAcceptingTransactionsAsync().ConfigureAwait(false)).JoinedBy("\n-- ")}"); + } + var apps = await _node.Network.GetAppsAsync().ConfigureAwait(false); Console.WriteLine($"-- Valid apps for network {apps.Network}:"); foreach (var app in apps.ValidApps.OrderByDescending(a => (a.Id, a.AppVersion)).DistinctBy(a => a.Id).OrderBy(a => a.Id)) Console.WriteLine(app); Console.WriteLine(); - var peers = await _node.GetPeersAsync(); + var peers = await _node.GetPeersAsync().ConfigureAwait(false); Console.WriteLine($"-- Known peers:"); - foreach (var peer in peers.OrderBy(a => a.Name)) { + foreach (var peer in peers.OrderBy(a => a.Name, StringComparer.OrdinalIgnoreCase)) { Console.WriteLine(peer); Console.WriteLine("---------------------------------"); } Console.WriteLine(); Console.WriteLine("-- Chains:"); - foreach (var chain in await _node.GetChainsAsync()) - await ExerciseChainAsync(_node, chain, write); + foreach (var chain in (await _node.GetChainsAsync().ConfigureAwait(false)).SkipWhile(c => createdChain is not null && !c.Id.SafeEqualsTo(createdChain.Id)).Take(3)) + await ExerciseChainAsync(_node, chain, write).ConfigureAwait(false); Console.WriteLine(); Console.WriteLine("-- Mirrors:"); - foreach (var chain in await _node.GetMirrorsAsync()) - await ExerciseChainAsync(_node, chain, write: false); + foreach (var chain in (await _node.GetMirrorsAsync().ConfigureAwait(false)).Take(3)) + await ExerciseChainAsync(_node, chain, write: false).ConfigureAwait(false); Console.WriteLine(); if (write) { - await CreateMirrorsAsync(); + await CreateMirrorsAsync().ConfigureAwait(false); if (documentRegistry is not null) - await ExerciseDocumentRegistryAsync(documentRegistry); + await ExerciseDocumentRegistryAsync(documentRegistry, createdChain).ConfigureAwait(false); } } private async Task CreateMirrorsAsync() { Console.WriteLine("-- Create Mirror:"); try { - foreach (var chain in await _node.AddMirrorsOfAsync(_newMirrors)) + var chains = await _node.AddMirrorsOfAsync(_newMirrors).ConfigureAwait(false); + foreach (var chain in chains.Safe()) Console.WriteLine(chain); } catch (Exception e) { Console.WriteLine(e.Message); @@ -104,7 +107,7 @@ private async Task CreateMirrorsAsync() { Console.WriteLine(); } - private async Task CreateOneChainAsync() { + private async Task CreateOneChainAsync(CertificatePermitModel ApiCertificate) { Console.WriteLine("-- Create Chain:"); try { var chain = await _node.CreateChainAsync(new ChainCreationModel { @@ -114,171 +117,175 @@ private async Task CreateOneChainAsync() { ManagementKeyPassword = "password", ManagementKeyStrength = KeyStrength.ExtraStrong, KeysAlgorithm = Algorithms.RSA, - AdditionalApps = [4, 8, 13] - }); + AdditionalApps = [4, 8, 13], + ApiCertificates = [ApiCertificate], + }).ConfigureAwait(false); Console.WriteLine(chain); - } catch (Exception e) { + return chain; + } catch (Exception e) { Console.WriteLine(e.Message); } Console.WriteLine(); + return null; } - private static async Task ExerciseDocumentRegistryAsync(IDocumentRegistry documentRegistry) { - var chains = await documentRegistry.ListKnownChainsAcceptingTransactionsAsync(); + private static async Task ExerciseDocumentRegistryAsync(IDocumentRegistry documentRegistry, ChainCreatedModel createdChain) { + var chains = await documentRegistry.ListKnownChainsAcceptingTransactionsAsync().ConfigureAwait(false); Console.WriteLine($"-- Exercise Document Registry (MultiDocuments)"); Console.WriteLine($"---- {chains.Count()} chains accepting MD transactions"); - var chainId = chains.FirstOrDefault(); - if (chainId is not null) { - try { - Console.WriteLine(); - Console.WriteLine(" Trying to begin a transaction:"); - var trx = await documentRegistry.TransactionBeginAsync(new DocumentsBeginTransactionModel { - Chain = chainId, - Comment = "C# REST client testing", - Compression = "BROTLI", - Encryption = "PBKDF2-SHA256-AES256-LOW", - Password = _password - }); - Console.WriteLine(trx); - Console.WriteLine(" Trying to store a nice document:"); - await documentRegistry.TransactionAddItemAsync(trx.TransactionId, "/", "Simple Test 1 Razão.txt", "First file", "text/plain", new MemoryStream(Content, writable: false)); - Console.WriteLine(await documentRegistry.TransactionStatusAsync(trx.TransactionId)); - await documentRegistry.TransactionAddItemAsync(trx.TransactionId, "/", "Simple Test 2 Emoção.txt", "Second file", "text/plain", new MemoryStream(Content, writable: false)); - Console.WriteLine(await documentRegistry.TransactionStatusAsync(trx.TransactionId)); - var locator = await documentRegistry.TransactionCommitAsync(trx.TransactionId); - Console.WriteLine($" Documents locator: '{locator}'"); - Console.WriteLine($" {documentRegistry.RetrieveMetadataAsync(locator)}"); - var secondFile = await documentRegistry.RetrieveSingleAsync(locator, 1); - if (secondFile is null) - Console.WriteLine("Could not retrieve second file"); - else { - using var streamReader = new StreamReader(secondFile.Value.Content); - Console.WriteLine($" Retrieved second file {secondFile.Value.Name} : '{streamReader.ReadToEnd()}'"); - var blobFile = await documentRegistry.RetrieveZipAsync(locator); - if (blobFile is null) - Console.WriteLine("Could not retrieve zip file"); + foreach (var chainId in chains) { + if (chainId.SafeEqualsTo(createdChain.Id)) + try { + Console.WriteLine(); + Console.WriteLine($" Trying to begin a transaction at '{chainId}'"); + byte[] bytes = Convert.FromBase64String("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAERvY3VtZW50c1Rlc3Rz"); + var trx = await documentRegistry.TransactionBeginAsync(new DocumentsBeginTransactionModel { + Chain = chainId, + Comment = "C# REST client testing", + Compression = "BROTLI", + Encryption = "PBKDF2-SHA256-AES256-LOW", + Password = _password, + AllowChildren = true, + ToChildrenComment = "Beware my children", + GeneratePublicDirectory = true, + FromParentContent = bytes, + }).ConfigureAwait(false); + Console.WriteLine(trx); + Console.WriteLine(" Trying to store a nice document:"); + await documentRegistry.TransactionAddItemAsync(trx.TransactionId, "/", "Simple Test 1 Razão.txt", "First file", "text/plain", new MemoryStream(AbstractUsingHelpers.Content, writable: false)).ConfigureAwait(false); + Console.WriteLine(await documentRegistry.TransactionStatusAsync(trx.TransactionId).ConfigureAwait(false)); + await documentRegistry.TransactionAddItemAsync(trx.TransactionId, "/", "Simple Test 2 Emoção.txt", "Second file", "text/plain", new MemoryStream(AbstractUsingHelpers.Content, writable: false)).ConfigureAwait(false); + Console.WriteLine(await documentRegistry.TransactionStatusAsync(trx.TransactionId).ConfigureAwait(false)); + await documentRegistry.TransactionAddItemAsync(trx.TransactionId, "/", ".from-parent", "Fake Control file", "application/octet-stream", new MemoryStream(bytes, writable: false)).ConfigureAwait(false); + Console.WriteLine(await documentRegistry.TransactionStatusAsync(trx.TransactionId).ConfigureAwait(false)); + var locator = await documentRegistry.TransactionCommitAsync(trx.TransactionId).ConfigureAwait(false); + Console.WriteLine($" Documents locator: '{locator}'"); + Console.WriteLine($" {await documentRegistry.RetrieveMetadataAsync(locator).ConfigureAwait(false)}"); + var secondFile = await documentRegistry.RetrieveSingleAsync(locator, 1).ConfigureAwait(false); + if (secondFile is null) + Console.WriteLine("Could not retrieve second file"); else { - using var stream = blobFile.Value.Content; - var zip = new ZipArchive(stream, ZipArchiveMode.Read, leaveOpen: false, Encoding.UTF8); - Console.WriteLine($" Blob file '{blobFile.Value.Name}' contains {zip.Entries.Count} entries."); - foreach (var entry in zip.Entries) - Console.WriteLine($" - {entry.FullName}"); + using var streamReader = new StreamReader(secondFile.Value.Content); + Console.WriteLine($" Retrieved second file {secondFile.Value.Name} : '{await streamReader.ReadToEndAsync().ConfigureAwait(false)}'"); + foreach ((bool omitFromParent, bool omitToChildren) in AllCombinations) { + var blobFile = await documentRegistry.RetrieveZipAsync(locator, omitFromParent, omitToChildren).ConfigureAwait(false); + if (blobFile is null) + Console.WriteLine($"Could not retrieve zip file with ({omitFromParent},{omitToChildren})"); + else { + using var stream = blobFile.Value.Content; + var zip = new ZipArchive(stream, ZipArchiveMode.Read, leaveOpen: false, Encoding.UTF8); + Console.WriteLine($" Blob file '{blobFile.Value.Name}' with ({omitFromParent},{omitToChildren}) contains {zip.Entries.Count} entries."); + foreach (var entry in zip.Entries) + Console.WriteLine($" - {entry.FullName}"); + } + } } + return; + } catch (Exception e) { + Console.WriteLine(e.Message); } - } catch (Exception e) { - Console.WriteLine(e.Message); - } } } + public static IEnumerable<(bool, bool)> AllCombinations => [(false, false), (false, true), (true, false), (true, true)]; protected async Task ExerciseChainAsync(RestAbstractNode node, T chain, bool write) { Console.WriteLine(chain); - - var summary = await chain.GetSummaryAsync(); - Console.WriteLine($" Summary.ActiveApps: {string.Join(", ", summary.ActiveApps)}"); - Console.WriteLine($" Summary.Description: {summary.Description}"); - Console.WriteLine($" Summary.IsClosedForNewTransactions: {summary.IsClosedForNewTransactions}"); - Console.WriteLine($" Summary.LastRecord: {summary.LastRecord}"); - Console.WriteLine($" Summary.LastUpdate: {summary.LastUpdate}"); - Console.WriteLine($" Summary.SizeInBytes: {summary.SizeInBytes}"); - Console.WriteLine($" Summary.LicensingStatus: {summary.LicensingStatus}"); - Console.WriteLine($" Summary.Licensed: {summary.Licensed}"); - Console.WriteLine($" Summary.LicenseIsExpired: {summary.LicenseIsExpired}"); - Console.WriteLine($" Summary.LicensedApps: {summary.LicensedApps.JoinedBy(",")}"); - Console.WriteLine(); - Console.WriteLine($" Active apps: {string.Join(",", await chain.GetActiveAppsAsync())}"); - Console.WriteLine(); + var summary = await chain.GetSummaryAsync().ConfigureAwait(false); + await DumpSummary(chain, summary).ConfigureAwait(false); Console.WriteLine(" Keys:"); - foreach (var key in await chain.GetPermittedKeysAsync()) + foreach (var key in await chain.GetPermittedKeysAsync().ConfigureAwait(false)) Console.WriteLine($" {key}"); Console.WriteLine(); Console.WriteLine(" Interlocks stored here:"); - foreach (var interlock in (await chain.Interlockings.GetInterlocksAsync()).Safe().Items) + foreach (var interlock in (await chain.Interlockings.GetInterlocksAsync().ConfigureAwait(false)).Safe().Items) Console.WriteLine($" {interlock}"); Console.WriteLine(); Console.WriteLine(" Interlocks of this chain:"); - foreach (var interlock in (await node.InterlocksOfAsync(chain.Id)).Safe().Items) + foreach (var interlock in (await node.InterlocksOfAsync(chain.Id).ConfigureAwait(false)).Safe().Items) Console.WriteLine($" {interlock}"); Console.WriteLine(); Console.WriteLine(" Records:"); - foreach (var record in (await chain.Records.RecordsFromToAsync(0, 1)).Safe().Items) + foreach (var record in (await chain.Records.RecordsFromToAsync(0, 1).ConfigureAwait(false)).Safe().Items) Console.WriteLine($" {record}"); Console.WriteLine(" RecordsAsJson:"); - foreach (var record in (await chain.RecordsAsJson.FromToAsync(0, 2)).Safe().Items) + foreach (var record in (await chain.RecordsAsJson.FromToAsync(0, 2).ConfigureAwait(false)).Safe().Items) Console.WriteLine($" {record}"); if (write) { - await TryToAddNiceRecordAsync(chain); - await TryToAddNiceJsonRecordAsync(chain); - await TryToAddBadRecordAsync(chain); - await TryToPermitApp4Async(chain); - await TryToForceInterlockAsync(chain); - await TryToPermitKeyAsync(chain); + await TryToAddNiceRecordAsync(chain).ConfigureAwait(false); + await TryToAddBadRecordAsync(chain).ConfigureAwait(false); + await TryToPermitApp9Async(chain).ConfigureAwait(false); + await TryToForceInterlockAsync(chain).ConfigureAwait(false); + await TryToPermitKeyAsync(chain).ConfigureAwait(false); } Console.WriteLine(); - await DoExtraExercisesAsync(_node, chain, write); + await DoExtraExercisesAsync(_node, chain, write).ConfigureAwait(false); } + private static async Task DumpSummary(T chain, ChainSummaryModel summary) => + Console.WriteLine($""" + Summary.ActiveApps: {string.Join(", ", summary.ActiveApps)} + Summary.Description: {summary.Description} + Summary.IsClosedForNewTransactions: {summary.IsClosedForNewTransactions} + Summary.LastRecord: {summary.LastRecord} + Summary.LastUpdate: {summary.LastUpdate} + Summary.SizeInBytes: {summary.SizeInBytes} + Summary.LicensingStatus: {summary.LicensingStatus} + Summary.Licensed: {summary.Licensed} + Summary.LicenseIsExpired: {summary.LicenseIsExpired} + Summary.LicensedApps: {summary.LicensedApps.JoinedBy(",")} + + Active apps: {string.Join(',', await chain.GetActiveAppsAsync().ConfigureAwait(false))} + """); protected async Task TryToAddBadRecordAsync(T chain) { try { Console.WriteLine(); Console.WriteLine(" Trying to add a bad record:"); - var record = await AddRecordAsync(chain, 1, 30); + var record = await AddRecordAsync(chain, 1, 30).ConfigureAwait(false); Console.WriteLine($" {record}"); } catch (Exception e) { Console.WriteLine(e.Message); } } - protected async Task TryToAddNiceJsonRecordAsync(T chain) { + protected async Task TryToAddNiceRecordAsync(T chain) { try { Console.WriteLine(); - Console.WriteLine(" Trying to add a nice JSON record:"); - var record = await AddRecordAsJsonAsync(chain, 1, 300, new { TagId = 300, Version = 1, Apps = new ulong[] { 1, 2, 3 } }); + Console.WriteLine(" Trying to add a nice record:"); + var record = await AddRecordAsync(chain, 1, 248, 52, 7, 5, 0, 0, 20, 2, 1, 100).ConfigureAwait(false); Console.WriteLine($" {record}"); } catch (Exception e) { Console.WriteLine(e.Message); } } - protected async Task TryToAddNiceRecordAsync(T chain) { + + protected async Task TryToPermitApp9Async(T chain) { try { + var apps = await chain.PermitAppsAsync(9).ConfigureAwait(false); + Console.WriteLine($" Permit app 9: [{string.Join(", ", apps)}]"); Console.WriteLine(); - Console.WriteLine(" Trying to add a nice record:"); - var record = await AddRecordAsync(chain, 1, 248, 52, 10, 5, 0, 0, 20, 5, 4, 0, 1, 2, 3); - Console.WriteLine($" {record}"); } catch (Exception e) { Console.WriteLine(e.Message); } } - protected async Task TryToForceInterlockAsync(T chain) { try { Console.WriteLine(); Console.WriteLine(" Trying to force an interlock:"); - var interlock = await chain.Interlockings.ForceInterlockAsync(new ForceInterlockModel("72_1DyspOtgOpg5XG2ihe7M0xCb2DhrZIQWv3-Bivy4") { HashAlgorithm = HashAlgorithms.Copy, MinSerial = 1 }); + var interlock = await chain.Interlockings.ForceInterlockAsync(new ForceInterlockModel("cE98F3JNgzDBOEjCwRLGMgFujHEPCJpKunPUO4fvWoo") { HashAlgorithm = HashAlgorithms.SHA256, MinSerial = 1 }).ConfigureAwait(false); Console.WriteLine($" {interlock}"); } catch (Exception e) { Console.WriteLine(e.Message); } } - protected async Task TryToPermitApp4Async(T chain) { - try { - var apps = await chain.PermitAppsAsync(4); - Console.WriteLine($" Permit app 4: [{string.Join(", ", apps)}]"); - Console.WriteLine(); - } catch (Exception e) { - Console.WriteLine(e.Message); - } - } - protected async Task TryToPermitKeyAsync(T chain) { try { Console.WriteLine(); Console.WriteLine($" Trying to permit key: {_keyToPermit.Name} ({_keyToPermit.Id})"); - foreach (var key in await chain.PermitKeysAsync(_keyToPermit)) + foreach (var key in await chain.PermitKeysAsync(_keyToPermit).ConfigureAwait(false)) Console.WriteLine($" {key}"); } catch (Exception e) { Console.WriteLine(e.Message); @@ -286,18 +293,18 @@ protected async Task TryToPermitKeyAsync(T chain) { } protected static async Task ReadSomeJsonDocumentsRecordsAsync(X509Certificate2 certificate, TJson jsonStore, IRecordsStore recordsStore, string chainId, ulong payloadTagId, string payloadTypeName, Func retrieveAndDumpAsync) where TJson : IJsonStore { - ulong[] filteredRecords = await FindJsonRecords(recordsStore, payloadTagId); + ulong[] filteredRecords = await FindJsonRecords(recordsStore, payloadTagId).ConfigureAwait(false); if (filteredRecords.SafeAny()) { Console.WriteLine($"{Environment.NewLine} ==== {payloadTypeName} from {chainId}: {filteredRecords.Length} records"); foreach (var serial in filteredRecords) { - await retrieveAndDumpAsync(certificate, jsonStore, chainId, serial); + await retrieveAndDumpAsync(certificate, jsonStore, chainId, serial).ConfigureAwait(false); } } else { Console.WriteLine($" No {payloadTypeName} found in {chainId}"); } static async Task FindJsonRecords(IRecordsStore recordsStore, ulong payloadTagId) { - var records = (await recordsStore.RecordsFromAsync(0, pageSize: 0, lastToFirst: true, ommitPayload: true))?.Items; + var records = (await recordsStore.RecordsFromAsync(0, pageSize: 0, lastToFirst: true, ommitPayload: true).ConfigureAwait(false))?.Items; return records.Safe() .Where(r => r.ApplicationId == 8ul && r.PayloadTagId == payloadTagId) .Take(3) @@ -308,7 +315,7 @@ static async Task FindJsonRecords(IRecordsStore recordsStore, ulong pay } protected static async Task RetrieveAndDumpJsonDocumentsAsync(X509Certificate2 certificate, TJson jsonStore, string chainId, ulong serial) where TJson : IJsonStore { - var json = await jsonStore.RetrieveAsync(serial); + var json = await jsonStore.RetrieveAsync(serial).ConfigureAwait(false); Console.WriteLine($"{Environment.NewLine} Json at {chainId}@{serial}:"); if (json is null) Console.WriteLine(" -- Could not retrieve it!"); @@ -323,5 +330,5 @@ protected static async Task RetrieveAndDumpJsonDocumentsAsync(X509Certifi } private static readonly byte[] _password = Encoding.UTF8.GetBytes("LongEnoughPassword"); - private static readonly string[] _newMirrors = ["72_1DyspOtgOpg5XG2ihe7M0xCb2DhrZIQWv3-Bivy4"]; + private static readonly string[] _newMirrors = ["cE98F3JNgzDBOEjCwRLGMgFujHEPCJpKunPUO4fvWoo"]; } \ No newline at end of file diff --git a/rest_client/AbstractUsingHelpers.cs b/rest_client/AbstractUsingHelpers.cs new file mode 100644 index 0000000..09bbc55 --- /dev/null +++ b/rest_client/AbstractUsingHelpers.cs @@ -0,0 +1,38 @@ +// ****************************************************************************************************************************** +// +// Copyright (c) 2018-2022 InterlockLedger Network +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met +// +// * Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES, LOSS OF USE, DATA, OR PROFITS, OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// ****************************************************************************************************************************** + +namespace rest_client; + +internal static class AbstractUsingHelpers +{ + public static byte[] Content { get; } = Encoding.UTF8.GetBytes("Nothing to see here"); +} \ No newline at end of file diff --git a/rest_client/Program.cs b/rest_client/Program.cs index 103a950..8dfdf87 100644 --- a/rest_client/Program.cs +++ b/rest_client/Program.cs @@ -31,30 +31,30 @@ // ****************************************************************************************************************************** using Cocona; -using InterlockLedger.Rest.Client.V13_7; +using InterlockLedger.Rest.Client.V14_2_2; using rest_client; - -CoconaLiteApp.Run(([Argument(Description = "Path to file containing client certificate in .pfx format")] string pathToCertificatePfxFile, - [Argument(Description = "Password to open file containing client certificate")] string certificatePassword, - [Argument(Description = "Port to access the node API (ex.: 32032)")] ushort apiPort, - [Argument(Description = "Address to access the node API (ex.: node.il2, localhost)")] string restURL, - [Option(Description = "Try to write into the node (Default: false)")] bool writeable = false) => { - try { - var pathToCertificatePfxFileInfo = new FileInfo(pathToCertificatePfxFile); - if (!pathToCertificatePfxFileInfo.Exists) { - Console.WriteLine($"File {pathToCertificatePfxFile} not found!"); - return 2; - } - var client = new RestNodeV13_7(pathToCertificatePfxFileInfo.FullName, certificatePassword, apiPort, restURL); - new UsingV13_7(client).ExerciseAsync(writeable).Wait(); - return 0; - } catch (Exception e) { - Console.WriteLine(e); - return 1; - } - }, configureOptions: options => { - options.EnableConvertArgumentNameToLowerCase = false; - options.EnableConvertOptionNameToLowerCase = false; - options.TreatPublicMethodsAsCommands = false; - }); +await CoconaLiteApp.RunAsync(( + [Argument(Description = "Path to file containing client certificate in .pfx format")] string pathToCertificatePfxFile, + [Argument(Description = "Password to open file containing client certificate")] string certificatePassword, + [Argument(Description = "Port to access the node API (ex.: 32032)")] ushort apiPort, + [Argument(Description = "Address to access the node API (ex.: node.il2, localhost)")] string restURL, + [Option(Description = "Try to write into the node (Default: false)")] bool writeable = false) => { + try { + var pathToCertificatePfxFileInfo = new FileInfo(pathToCertificatePfxFile); + if (!pathToCertificatePfxFileInfo.Exists) { + Console.WriteLine($"File {pathToCertificatePfxFile} not found!"); + return 2; + } + var client = new RestNodeV14_2_2(pathToCertificatePfxFileInfo.FullName, certificatePassword, apiPort, restURL); + new UsingV14_2_2(client).ExerciseAsync(writeable).Wait(); + return 0; + } catch (Exception e) { + Console.WriteLine(e); + return 1; + } + }, configureOptions: options => { + options.EnableConvertArgumentNameToLowerCase = false; + options.EnableConvertOptionNameToLowerCase = false; + options.TreatPublicMethodsAsCommands = false; + }).ConfigureAwait(false); diff --git a/rest_client/Properties/launchSettings.json b/rest_client/Properties/launchSettings.json index cfbcfcb..9a34baa 100644 --- a/rest_client/Properties/launchSettings.json +++ b/rest_client/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "rest_client": { "commandName": "Project", - "commandLineArgs": "D:\\vault\\clients\\minerva-debug\\certificates\\rest.api.pfx DebugKey 32024 node.il2 --writeable" + "commandLineArgs": "D:\\vault\\clients\\minerva-debug\\certificates\\rest.api.pfx DebugKey 32024 node.il2 \r\n--writeable" }, "rest_client Help": { "commandName": "Project", diff --git a/rest_client/UsingV13_7.cs b/rest_client/UsingV14_2_2.cs similarity index 82% rename from rest_client/UsingV13_7.cs rename to rest_client/UsingV14_2_2.cs index bcf0c08..5f9d9f9 100644 --- a/rest_client/UsingV13_7.cs +++ b/rest_client/UsingV14_2_2.cs @@ -31,27 +31,27 @@ // ****************************************************************************************************************************** using InterlockLedger.Rest.Client.Abstractions; -using InterlockLedger.Rest.Client.V13_7; +using InterlockLedger.Rest.Client.V14_2_2; namespace rest_client; -public class UsingV13_7(RestAbstractNode node) : AbstractUsing(node) +public class UsingV14_2_2(RestAbstractNode node) : AbstractUsing(node) { - protected override string Version => "13.7"; + protected override string Version => "14.2.2"; - protected override async Task DoExtraExercisesAsync(RestAbstractNode node, RestChainV13_7 chain, bool write) { - await ExerciseJsonDocumentsV13_7Async(chain, node.Certificate, write); - await ExerciseOpaqueRecordsAsync(chain, write); + protected override async Task DoExtraExercisesAsync(RestAbstractNode node, RestChainV14_2_2 chain, bool write) { + await ExerciseJsonDocumentsV14_2_2Async(chain, node.Certificate, write).ConfigureAwait(false); + await ExerciseOpaqueRecordsAsync(chain, write).ConfigureAwait(false); } - private static async Task ExerciseJsonDocumentsV13_7Async(RestChainV13_7 chain, X509Certificate2 certificate, bool write) { + private static async Task ExerciseJsonDocumentsV14_2_2Async(RestChainV14_2_2 chain, X509Certificate2 certificate, bool write) { try { Console.WriteLine(" JsonDocuments:"); if (write) { // Add something } - await ReadSomeJsonDocumentsRecordsAsync(certificate, chain.JsonStore, chain.Records, chain.Id, 2100, "jsonDocuments", RetrieveAndDumpJsonDocumentsAsync); - var query = await chain.JsonStore.RetrieveAllowedReadersAsync(chain.Id); + await ReadSomeJsonDocumentsRecordsAsync(certificate, chain.JsonStore, chain.Records, chain.Id, 2100, "jsonDocuments", RetrieveAndDumpJsonDocumentsAsync).ConfigureAwait(false); + var query = await chain.JsonStore.RetrieveAllowedReadersAsync(chain.Id).ConfigureAwait(false); if (query.TotalNumberOfPages > 0) { Console.WriteLine($" RetrieveAllowedReadersAsync retrieved first page of {query.TotalNumberOfPages} pages with {query.Items.Count()} items"); Console.WriteLine(query.First()?.AsJson()); @@ -64,21 +64,21 @@ private static async Task ExerciseJsonDocumentsV13_7Async(RestChainV13_7 chain, Console.WriteLine(); } - private static async Task ExerciseOpaqueRecordsAsync(RestChainV13_7 chain, bool write) { + private static async Task ExerciseOpaqueRecordsAsync(RestChainV14_2_2 chain, bool write) { Console.WriteLine(" OpaqueRecords:"); var opaqueStore = chain.OpaqueStore; - var query = await opaqueStore.QueryRecordsFromAsync(appId: 13); - Console.WriteLine($" LastChangedRecordSerial {query.LastChangedRecordSerial} for {chain.Id}"); + var query = await opaqueStore.QueryRecordsFromAsync(appId: 13).ConfigureAwait(false); + Console.WriteLine($" LastChangedRecordSerial {query?.LastChangedRecordSerial} for {chain.Id}"); try { ulong serialToRetrieve = 0; if (write) { Console.WriteLine(" Trying to add an opaque payload #13,100"); - var result = await opaqueStore.AddRecordAsync(appId: 13, payloadTypeId: 100, query.LastChangedRecordSerial, [1, 2, 3, 4]); + var result = await opaqueStore.AddRecordAsync(appId: 13, payloadTypeId: 100, query.LastChangedRecordSerial, [1, 2, 3, 4]).ConfigureAwait(false); serialToRetrieve = result.Serial; } else { serialToRetrieve = query.First()?.Serial ?? 0; } - var response = await opaqueStore.RetrieveSinglePayloadAsync(serialToRetrieve); + var response = await opaqueStore.RetrieveSinglePayloadAsync(serialToRetrieve).ConfigureAwait(false); if (response.HasValue) { Console.WriteLine($" Retrieved AppId: {response.Value.AppId}"); Console.WriteLine($" Retrieved PayloadTypeId: {response.Value.PayloadTypeId}"); diff --git a/rest_client/rest_client.csproj b/rest_client/rest_client.csproj index e173da1..32f64e9 100644 --- a/rest_client/rest_client.csproj +++ b/rest_client/rest_client.csproj @@ -24,11 +24,15 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + all runtime; build; native; contentfiles; analyzers; buildtransitive - +