From 8835b23ed3fcb172c3f4d1d068e6142d53223137 Mon Sep 17 00:00:00 2001
From: Pavel Gross
Date: Thu, 27 Feb 2025 17:35:19 +0300
Subject: [PATCH] [#34] Client: Add rules deserialization
Signed-off-by: Pavel Gross
---
src/FrostFS.SDK.Client/ApeRules/Actions.cs | 2 +-
.../ApeRules/FrostFsRule.cs | 2 +-
src/FrostFS.SDK.Client/ApeRules/Resources.cs | 12 +-
.../ApeRules/RuleSerializer.cs | 243 +++++++++++++++++-
src/FrostFS.SDK.Client/FrostFSClient.cs | 4 +-
.../Interfaces/IFrostFSClient.cs | 4 +-
.../Models/Session/FrostFsSessionToken.cs | 1 -
.../Parameters/PrmApeChainAdd.cs | 4 +-
.../Parameters/PrmApeChainList.cs | 4 +-
.../Parameters/PrmApeChainRemove.cs | 5 +-
src/FrostFS.SDK.Client/Pool/Pool.cs | 4 +-
.../Services/ApeManagerServiceProvider.cs | 7 +-
.../Smoke/Client/MiscTests/InterceptorTest.cs | 2 +
.../Smoke/Client/ObjectTests/ObjectTests.cs | 5 +
.../MultithreadSmokeClientTests.cs | 2 -
src/FrostFS.SDK.Tests/Smoke/SmokeTestsBase.cs | 4 +-
src/FrostFS.SDK.Tests/Unit/ApeTests.cs | 167 ++++++++++++
src/FrostFS.SDK.Tests/Unit/ObjectTest.cs | 6 +-
18 files changed, 426 insertions(+), 52 deletions(-)
create mode 100644 src/FrostFS.SDK.Tests/Unit/ApeTests.cs
diff --git a/src/FrostFS.SDK.Client/ApeRules/Actions.cs b/src/FrostFS.SDK.Client/ApeRules/Actions.cs
index 28600b8..71888b9 100644
--- a/src/FrostFS.SDK.Client/ApeRules/Actions.cs
+++ b/src/FrostFS.SDK.Client/ApeRules/Actions.cs
@@ -6,7 +6,7 @@ public struct Actions(bool inverted, string[] names) : System.IEquatable
+public struct Resources(bool inverted, string[] names) : System.IEquatable
{
public bool Inverted { get; set; } = inverted;
@@ -8,10 +8,10 @@ public struct Resource(bool inverted, string[] names) : System.IEquatable(byte[] buf, int offset, T[] slice, Func marshalT)
+ private static int SliceMarshal(byte[] buf, int offset, T[] slice, Func marshalT)
{
if (slice == null)
{
@@ -269,7 +305,7 @@ internal static class RuleSerializer
return SliceMarshal(buf, offset, rule.Conditions!, MarshalCondition);
}
- private static int MarshalResources(byte[] buf, int offset, Resource resources)
+ private static int MarshalResources(byte[] buf, int offset, Resources resources)
{
offset = BoolMarshal(buf, offset, resources.Inverted);
@@ -283,4 +319,187 @@ internal static class RuleSerializer
throw new FrostFsException("actual data size differs from expected");
}
}
+
+ private static (int, bool) BoolUnmarshal(byte[] buf, int offset)
+ {
+ (offset, byte val) = UInt8Unmarshal(buf, offset);
+ return (offset, val == ByteTrue);
+ }
+
+ private static (int, string) StringUnmarshal(byte[] buf, int offset)
+ {
+ (offset, long size) = Int64Unmarshal(buf, offset);
+
+ if (size == 0)
+ {
+ return (offset, string.Empty);
+ }
+
+ if (size > MaxSliceLen)
+ {
+ throw new FrostFsException($"string is too long: '{size}'");
+ }
+ if (size < 0)
+ {
+ throw new FrostFsException($"invalid string size: '{size}'");
+ }
+
+ if (buf.Length - offset < size)
+ {
+ throw new FrostFsException($"not enough bytes left to string value");
+ }
+
+ return (offset + (int)size, System.Text.Encoding.UTF8.GetString(buf, offset, (int)size));
+ }
+
+ private static (int, Actions) UnmarshalActions(byte[] buf, int offset)
+ {
+ Actions action = new();
+ (offset, action.Inverted) = BoolUnmarshal(buf, offset);
+
+ (offset, action.Names) = SliceUnmarshal(buf, offset, StringUnmarshal);
+
+ return (offset, action);
+ }
+
+ private static (int, Resources) UnmarshalResources(byte[] buf, int offset)
+ {
+ Resources res = new();
+
+ (offset, res.Inverted) = BoolUnmarshal(buf, offset);
+ (offset, res.Names) = SliceUnmarshal(buf, offset, StringUnmarshal);
+
+ return (offset, res);
+ }
+
+ private static (int, Condition) UnmarshalCondition(byte[] buf, int offset)
+ {
+ Condition cond = new();
+ (offset, var op) = UInt8Unmarshal(buf, offset);
+
+ cond.Op = (ConditionType)op;
+
+ (offset, var kind) = UInt8Unmarshal(buf, offset);
+
+ cond.Kind = (ConditionKindType)kind;
+
+ (offset, cond.Key) = StringUnmarshal(buf, offset);
+
+ (offset, cond.Value) = StringUnmarshal(buf, offset);
+
+ return (offset, cond);
+ }
+
+ private static (int, FrostFsRule) UnmarshalRule(byte[] buf, int offset)
+ {
+ FrostFsRule rule = new();
+
+ (offset, byte statusV) = UInt8Unmarshal(buf, offset);
+ rule.Status = (RuleStatus)statusV;
+
+ (offset, rule.Actions) = UnmarshalActions(buf, offset);
+
+ (offset, rule.Resources) = UnmarshalResources(buf, offset);
+
+ (offset, rule.Any) = BoolUnmarshal(buf, offset);
+
+ (offset, rule.Conditions) = SliceUnmarshal(buf, offset, UnmarshalCondition);
+
+ return (offset, rule);
+ }
+
+ private static (int, byte) UInt8Unmarshal(byte[] buf, int offset)
+ {
+ if (buf.Length - offset < 1)
+ {
+ throw new FrostFsException($"not enough bytes left to read a value of type 'byte' from offset {offset}");
+ }
+
+ return (offset + 1, buf[offset]);
+ }
+
+ private static (int, long) Int64Unmarshal(byte[] buf, int offset)
+ {
+ if (buf.Length - offset < sizeof(long))
+ {
+ throw new FrostFsException($"not enough bytes left to read a value of type 'long' from offset {offset}");
+ }
+
+ return Varint(buf, offset);
+ }
+
+ private static (int, T[]) SliceUnmarshal(byte[] buf, int offset, Func unmarshalT)
+ {
+ var (newOffset, size) = Varint(buf, offset);
+
+ if (size == NullSlice)
+ {
+ return (newOffset, []);
+ }
+
+ if (size > MaxSliceLen)
+ {
+ throw new FrostFsException($"slice size is too big: '{size}'");
+ }
+
+ if (size < 0)
+ {
+ throw new FrostFsException($"invalid slice size: '{size}'");
+ }
+
+ var result = new T[size];
+ for (int i = 0; i < result.Length; i++)
+ {
+ (newOffset, result[i]) = unmarshalT(buf, newOffset);
+ }
+
+ return (newOffset, result);
+ }
+
+ private static void VerifyUnmarshal(byte[] buf, int offset)
+ {
+ if (buf.Length != offset)
+ {
+ throw new FrostFsException("unmarshalled bytes left");
+ }
+ }
+
+ private static int MaxVarIntLen64 = 10;
+
+ public static (int, long) Varint(byte[] buf, int offset)
+ {
+ var (ux, n) = Uvarint(buf, offset); // ok to continue in presence of error
+ long x = (long)ux >> 1;
+ if ((ux & 1) != 0)
+ {
+ x = ~x;
+ }
+ return (n, x);
+ }
+
+ public static (ulong, int) Uvarint(byte[] buf, int offset)
+ {
+ ulong x = 0;
+ int s = 0;
+
+ for (int i = offset; i < buf.Length; i++)
+ {
+ byte b = buf[i];
+ if (i == MaxVarIntLen64)
+ {
+ return (0, -(i + 1)); // overflow
+ }
+ if (b < 0x80)
+ {
+ if (i == MaxVarIntLen64 - 1 && b > 1)
+ {
+ return (0, -(i + 1)); // overflow
+ }
+ return (x | ((ulong)b << s), i + 1);
+ }
+ x |= (ulong)(b & 0x7f) << s;
+ s += 7;
+ }
+ return (0, 0);
+ }
}
diff --git a/src/FrostFS.SDK.Client/FrostFSClient.cs b/src/FrostFS.SDK.Client/FrostFSClient.cs
index 83f917e..f72265c 100644
--- a/src/FrostFS.SDK.Client/FrostFSClient.cs
+++ b/src/FrostFS.SDK.Client/FrostFSClient.cs
@@ -2,8 +2,6 @@
using System.Collections.Generic;
using System.Threading.Tasks;
-using Frostfs.V2.Ape;
-
using FrostFS.SDK.Client.Interfaces;
using FrostFS.SDK.Client.Services;
using FrostFS.SDK.Cryptography;
@@ -195,7 +193,7 @@ public class FrostFSClient : IFrostFSClient
return GetApeManagerService().RemoveChainAsync(args, ctx);
}
- public Task ListChainAsync(PrmApeChainList args, CallContext ctx)
+ public Task ListChainAsync(PrmApeChainList args, CallContext ctx)
{
return GetApeManagerService().ListChainAsync(args, ctx);
}
diff --git a/src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs b/src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs
index 594cc63..d546156 100644
--- a/src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs
+++ b/src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs
@@ -2,8 +2,6 @@ using System;
using System.Collections.Generic;
using System.Threading.Tasks;
-using Frostfs.V2.Ape;
-
namespace FrostFS.SDK.Client.Interfaces;
public interface IFrostFSClient
@@ -25,7 +23,7 @@ public interface IFrostFSClient
Task RemoveChainAsync(PrmApeChainRemove args, CallContext ctx);
- Task ListChainAsync(PrmApeChainList args, CallContext ctx);
+ Task ListChainAsync(PrmApeChainList args, CallContext ctx);
#endregion
#region Container
diff --git a/src/FrostFS.SDK.Client/Models/Session/FrostFsSessionToken.cs b/src/FrostFS.SDK.Client/Models/Session/FrostFsSessionToken.cs
index 5f675ae..708c9f6 100644
--- a/src/FrostFS.SDK.Client/Models/Session/FrostFsSessionToken.cs
+++ b/src/FrostFS.SDK.Client/Models/Session/FrostFsSessionToken.cs
@@ -1,5 +1,4 @@
using System;
-using System.Diagnostics;
using FrostFS.Refs;
using FrostFS.SDK.Client;
diff --git a/src/FrostFS.SDK.Client/Parameters/PrmApeChainAdd.cs b/src/FrostFS.SDK.Client/Parameters/PrmApeChainAdd.cs
index 54b5171..adcc1b9 100644
--- a/src/FrostFS.SDK.Client/Parameters/PrmApeChainAdd.cs
+++ b/src/FrostFS.SDK.Client/Parameters/PrmApeChainAdd.cs
@@ -1,6 +1,4 @@
-using FrostFS.SDK.Client;
-
-namespace FrostFS.SDK.Client;
+namespace FrostFS.SDK.Client;
public readonly struct PrmApeChainAdd(FrostFsChainTarget target, FrostFsChain chain, string[]? xheaders = null) : System.IEquatable
{
diff --git a/src/FrostFS.SDK.Client/Parameters/PrmApeChainList.cs b/src/FrostFS.SDK.Client/Parameters/PrmApeChainList.cs
index 57c90bd..a946840 100644
--- a/src/FrostFS.SDK.Client/Parameters/PrmApeChainList.cs
+++ b/src/FrostFS.SDK.Client/Parameters/PrmApeChainList.cs
@@ -1,6 +1,4 @@
-using FrostFS.SDK.Client;
-
-namespace FrostFS.SDK.Client;
+namespace FrostFS.SDK.Client;
public readonly struct PrmApeChainList(FrostFsChainTarget target, string[]? xheaders = null) : System.IEquatable
{
diff --git a/src/FrostFS.SDK.Client/Parameters/PrmApeChainRemove.cs b/src/FrostFS.SDK.Client/Parameters/PrmApeChainRemove.cs
index 1fc110b..720c3c4 100644
--- a/src/FrostFS.SDK.Client/Parameters/PrmApeChainRemove.cs
+++ b/src/FrostFS.SDK.Client/Parameters/PrmApeChainRemove.cs
@@ -1,7 +1,4 @@
-using System;
-using FrostFS.SDK.Client;
-
-namespace FrostFS.SDK.Client;
+namespace FrostFS.SDK.Client;
public readonly struct PrmApeChainRemove(
FrostFsChainTarget target,
diff --git a/src/FrostFS.SDK.Client/Pool/Pool.cs b/src/FrostFS.SDK.Client/Pool/Pool.cs
index f0b7a6e..cd09d30 100644
--- a/src/FrostFS.SDK.Client/Pool/Pool.cs
+++ b/src/FrostFS.SDK.Client/Pool/Pool.cs
@@ -5,8 +5,6 @@ using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
-using Frostfs.V2.Ape;
-
using FrostFS.Refs;
using FrostFS.SDK.Client.Interfaces;
using FrostFS.SDK.Client.Mappers.GRPC;
@@ -550,7 +548,7 @@ public partial class Pool : IFrostFSClient
await client.Client!.RemoveChainAsync(args, ctx).ConfigureAwait(false);
}
- public async Task ListChainAsync(PrmApeChainList args, CallContext ctx)
+ public async Task ListChainAsync(PrmApeChainList args, CallContext ctx)
{
var client = Connection();
return await client.Client!.ListChainAsync(args, ctx).ConfigureAwait(false);
diff --git a/src/FrostFS.SDK.Client/Services/ApeManagerServiceProvider.cs b/src/FrostFS.SDK.Client/Services/ApeManagerServiceProvider.cs
index 174d460..5331b80 100644
--- a/src/FrostFS.SDK.Client/Services/ApeManagerServiceProvider.cs
+++ b/src/FrostFS.SDK.Client/Services/ApeManagerServiceProvider.cs
@@ -1,7 +1,6 @@
using System;
+using System.Linq;
using System.Threading.Tasks;
-
-using Frostfs.V2.Ape;
using Frostfs.V2.Apemanager;
using Google.Protobuf;
@@ -61,7 +60,7 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor
Verifier.CheckResponse(response);
}
- internal async Task ListChainAsync(PrmApeChainList args, CallContext ctx)
+ internal async Task ListChainAsync(PrmApeChainList args, CallContext ctx)
{
ListChainsRequest request = new()
{
@@ -78,6 +77,6 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor
Verifier.CheckResponse(response);
- return [.. response.Body.Chains];
+ return [.. response.Body.Chains.Select(c => RuleSerializer.Deserialize([.. c.Raw]))];
}
}
\ No newline at end of file
diff --git a/src/FrostFS.SDK.Tests/Smoke/Client/MiscTests/InterceptorTest.cs b/src/FrostFS.SDK.Tests/Smoke/Client/MiscTests/InterceptorTest.cs
index c3df8e5..664d522 100644
--- a/src/FrostFS.SDK.Tests/Smoke/Client/MiscTests/InterceptorTest.cs
+++ b/src/FrostFS.SDK.Tests/Smoke/Client/MiscTests/InterceptorTest.cs
@@ -1,8 +1,10 @@
+using System.Diagnostics.CodeAnalysis;
using FrostFS.SDK.Client;
using FrostFS.SDK.SmokeTests;
namespace FrostFS.SDK.Tests.Smoke;
+[SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")]
public class InterceptorTests() : SmokeTestsBase
{
[Fact]
diff --git a/src/FrostFS.SDK.Tests/Smoke/Client/ObjectTests/ObjectTests.cs b/src/FrostFS.SDK.Tests/Smoke/Client/ObjectTests/ObjectTests.cs
index 98e32f7..b6ae62b 100644
--- a/src/FrostFS.SDK.Tests/Smoke/Client/ObjectTests/ObjectTests.cs
+++ b/src/FrostFS.SDK.Tests/Smoke/Client/ObjectTests/ObjectTests.cs
@@ -124,11 +124,16 @@ public class ObjectTests(ITestOutputHelper testOutputHelper) : SmokeTestsBase
var hashes = await client.GetRangeHashAsync(rangeParam, default);
+ var objectRange = bytes.AsMemory().Slice(100, 64).ToArray();
+ var expectedHash = SHA256.HashData(objectRange);
+
foreach (var h in hashes)
{
var x = h[..32].ToArray();
Assert.NotNull(x);
Assert.True(x.Length > 0);
+
+ // Assert.True(expectedHash.SequenceEqual(h.ToArray()));
}
}
diff --git a/src/FrostFS.SDK.Tests/Smoke/PoolTests/Multithread/MultithreadSmokeClientTests.cs b/src/FrostFS.SDK.Tests/Smoke/PoolTests/Multithread/MultithreadSmokeClientTests.cs
index 32aeb2e..7de8155 100644
--- a/src/FrostFS.SDK.Tests/Smoke/PoolTests/Multithread/MultithreadSmokeClientTests.cs
+++ b/src/FrostFS.SDK.Tests/Smoke/PoolTests/Multithread/MultithreadSmokeClientTests.cs
@@ -6,8 +6,6 @@ using FrostFS.SDK.Client.Interfaces;
using FrostFS.SDK.Cryptography;
using FrostFS.SDK.SmokeTests;
-using Microsoft.Extensions.Options;
-
namespace FrostFS.SDK.Tests.Smoke;
[SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")]
diff --git a/src/FrostFS.SDK.Tests/Smoke/SmokeTestsBase.cs b/src/FrostFS.SDK.Tests/Smoke/SmokeTestsBase.cs
index ac76a54..59099a9 100644
--- a/src/FrostFS.SDK.Tests/Smoke/SmokeTestsBase.cs
+++ b/src/FrostFS.SDK.Tests/Smoke/SmokeTestsBase.cs
@@ -1,7 +1,6 @@
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Security.Cryptography;
-using System.Security.Principal;
using System.Text;
using FrostFS.SDK.Client;
using FrostFS.SDK.Client.Interfaces;
@@ -10,7 +9,6 @@ using FrostFS.SDK.Cryptography;
using Grpc.Core;
using Microsoft.Extensions.Options;
-using Xunit.Abstractions;
namespace FrostFS.SDK.Tests.Smoke;
@@ -121,7 +119,7 @@ public abstract class SmokeTestsBase
{
Status = RuleStatus.Allow,
Actions = new Actions(inverted: false, names: ["*"]),
- Resources = new Resource (inverted: false, names: [$"native:object/*"]),
+ Resources = new Resources (inverted: false, names: [$"native:object/*"]),
Any = false,
Conditions = []
}
diff --git a/src/FrostFS.SDK.Tests/Unit/ApeTests.cs b/src/FrostFS.SDK.Tests/Unit/ApeTests.cs
new file mode 100644
index 0000000..dea17fb
--- /dev/null
+++ b/src/FrostFS.SDK.Tests/Unit/ApeTests.cs
@@ -0,0 +1,167 @@
+using System.Text;
+using FrostFS.SDK.Client;
+
+namespace FrostFS.SDK.Tests.Unit;
+
+public class ApeTests : ContainerTestsBase
+{
+ [Fact]
+ public void ApeRule1Test()
+ {
+ var chain = new FrostFsChain
+ {
+ ID = Encoding.ASCII.GetBytes("chain-id-test"),
+ Rules = [
+ new FrostFsRule
+ {
+ Status = RuleStatus.Allow,
+ Actions = new Actions(inverted: false, names: ["*"]),
+ Resources = new Resources (inverted: false, names: [$"native:object/*"]),
+ Any = false,
+ Conditions = []
+ }
+ ],
+ MatchType = RuleMatchType.DenyPriority
+ };
+
+ var serialized = RuleSerializer.Serialize(chain);
+ var restoredChain = RuleSerializer.Deserialize(serialized);
+
+ Assert.True(chain.ID.SequenceEqual(restoredChain.ID));
+
+ Assert.Equal(chain.MatchType, restoredChain.MatchType);
+ CompareRules(chain.Rules, restoredChain.Rules);
+ }
+
+ [Fact]
+ public void ApeRule2Test()
+ {
+ var chain = new FrostFsChain
+ {
+ ID = Encoding.ASCII.GetBytes("dumptext"),
+ Rules = [
+ new FrostFsRule
+ {
+ Status = RuleStatus.AccessDenied,
+ Actions = new Actions(inverted: true, names: ["put,get"]),
+ Resources = new Resources (inverted: true, names: [$"native:object/*,blablabla"]),
+ Any = true,
+ Conditions = [
+ new () {
+ Key = "key",
+ Value = "value",
+ Kind = ConditionKindType.Resource,
+ Op = ConditionType.CondStringEquals
+ },
+ new () {
+ Key = "key1",
+ Value = "value1",
+ Kind = ConditionKindType.Request,
+ Op = ConditionType.CondNumericGreaterThan
+ }
+ ]
+ }
+ ],
+ MatchType = RuleMatchType.FirstMatch
+ };
+
+ var serialized = RuleSerializer.Serialize(chain);
+ var restoredChain = RuleSerializer.Deserialize(serialized);
+
+ Assert.True(chain.ID.SequenceEqual(restoredChain.ID));
+
+ Assert.Equal(chain.MatchType, restoredChain.MatchType);
+ CompareRules(chain.Rules, restoredChain.Rules);
+ }
+
+ [Fact]
+ public void NegativeDeserialize1Test()
+ {
+ try
+ {
+ _ = RuleSerializer.Deserialize(null);
+ Assert.Fail("Error is expected");
+ }
+ catch (ArgumentNullException)
+ {
+ }
+ }
+
+ [Fact]
+ public void NegativeDeserialize2Test()
+ {
+ try
+ {
+ _ = RuleSerializer.Deserialize([]);
+ Assert.Fail("Error is expected");
+ }
+ catch (FrostFsException)
+ {
+ }
+ }
+
+ [Fact]
+ public void NegativeDeserialize3Test()
+ {
+ try
+ {
+ _ = RuleSerializer.Deserialize([1, 2, 3]);
+ Assert.Fail("Error is expected");
+ }
+ catch (FrostFsException)
+ {
+ }
+ }
+
+ [Fact]
+ public void NegativeDeserialize4Test()
+ {
+ try
+ {
+ //"\x00\x00:aws:iam::namespace:group/so\x82\x82\x82\x82\x82\x82u\x82"
+ _ = RuleSerializer.Deserialize([0x00, 0x00, 0x3A, 0x77, 0x73, 0x3A, 0x69, 0x61, 0x6D, 0x3A, 0x3A, 0x6E, 0x61, 0x6D, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x3A, 0x67, 0x72, 0x6F, 0x75, 0x70, 0x2F, 0x73, 0x6F, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x75, 0x82]);
+ Assert.Fail("Error is expected");
+ }
+ catch (FrostFsException)
+ {
+ }
+ }
+
+ private static void CompareRules(FrostFsRule[] rules1, FrostFsRule[] rules2)
+ {
+ Assert.NotNull(rules1);
+ Assert.NotNull(rules2);
+
+ Assert.Equal(rules1.Length, rules2.Length);
+
+ for (int ri = 0; ri < rules1.Length; ri++)
+ {
+ var rule1 = rules1[ri];
+ var rule2 = rules2[ri];
+
+ Assert.Equal(rule1.Status, rule2.Status);
+ Assert.Equal(rule1.Any, rule2.Any);
+
+ Assert.Equal(rule1.Actions, rule2.Actions);
+
+ Assert.Equal(rule1.Resources, rule2.Resources);
+
+ bool cond1Empty = rule1.Conditions == null || rule1.Conditions.Length == 0;
+ bool cond2Empty = rule2.Conditions == null || rule2.Conditions.Length == 0;
+
+ if (cond1Empty && cond2Empty)
+ {
+ return;
+ }
+
+ Assert.Equal(cond1Empty, cond2Empty);
+
+ Assert.Equal(rule1.Conditions!.Length, rule2.Conditions!.Length);
+
+ for (int i = 0; i < rule1.Conditions.Length; i++)
+ {
+ Assert.Equal(rule1.Conditions[i], rule2.Conditions[i]);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/FrostFS.SDK.Tests/Unit/ObjectTest.cs b/src/FrostFS.SDK.Tests/Unit/ObjectTest.cs
index 445dd80..564627d 100644
--- a/src/FrostFS.SDK.Tests/Unit/ObjectTest.cs
+++ b/src/FrostFS.SDK.Tests/Unit/ObjectTest.cs
@@ -111,12 +111,12 @@ public class ObjectTest : ObjectTestsBase
Assert.NotNull(header1.Split.SplitId);
Assert.Null(header1.Split.Previous);
- Assert.Equal(bytes[..blockSize], payload1);
+ Assert.Equal(SHA256.HashData(bytes.AsMemory().Slice(0, blockSize).ToArray()), SHA256.HashData(payload1));
Assert.True(header1.Attributes.Count == 0);
Assert.Equal(header1.Split.SplitId, header2.Split.SplitId);
Assert.Equal(objIds.ElementAt(0), header2.Split.Previous.Value);
- Assert.Equal(bytes[blockSize..(blockSize * 2)], payload2);
+ Assert.Equal(SHA256.HashData(bytes.AsMemory().Slice(blockSize, blockSize).ToArray()), SHA256.HashData(payload2));
Assert.True(header2.Attributes.Count == 0);
// last part
@@ -124,7 +124,7 @@ public class ObjectTest : ObjectTestsBase
Assert.NotNull(header3.Split.ParentHeader);
Assert.NotNull(header3.Split.ParentSignature);
Assert.Equal(header2.Split.SplitId, header3.Split.SplitId);
- Assert.Equal(bytes[(fileLength / blockSize * blockSize)..fileLength], payload3);
+ Assert.Equal(SHA256.HashData(bytes.AsMemory().Slice(fileLength - fileLength % blockSize, fileLength % blockSize).ToArray()), SHA256.HashData(payload3));
Assert.True(header3.Attributes.Count == 0);
//link object