using System.Diagnostics.CodeAnalysis; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; using FrostFS.Refs; using FrostFS.SDK.Client; using FrostFS.SDK.Client.Interfaces; using FrostFS.SDK.Cryptography; using FrostFS.SDK.SmokeTests; using FrostFS.Session; using Google.Protobuf; 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")] [SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "No secure purpose")] public class SmokeClientTests : SmokeTestsBase { private static readonly PrmWait lightWait = new(100, 1); [Fact] public async void AccountTest() { var test = lightWait.Timeout; var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); var result = await client.GetBalanceAsync(default); Assert.NotNull(result); Assert.True(result.Value == 0); } [Fact] public async void NodeInfoTest() { var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); var result = await client.GetNodeInfoAsync(default); Assert.Equal(2, result.Version.Major); Assert.Equal(13, result.Version.Minor); Assert.Equal(NodeState.Online, result.State); Assert.Equal(33, result.PublicKey.Length); // Assert.Single(result.Addresses); // Assert.Equal(9, result.Attributes.Count); } [Fact] public async void NodeInfoStatisticsTest() { var options = ClientOptions; var callbackContent = string.Empty; options.Value.Callback = (cs) => callbackContent = $"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds"; var client = FrostFSClient.GetInstance(options, GrpcChannel); var result = await client.GetNodeInfoAsync(default); Assert.NotEmpty(callbackContent); } [Fact] public async void GetSessionTest() { var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); var token = await client.CreateSessionAsync(new(100), default); Assert.NotNull(token); Assert.NotEqual(Guid.Empty, token.Id); Assert.Equal(33, token.SessionKey.Length); } [Fact] public async void CreateObjectWithSessionToken() { var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); await Cleanup(client); var token = await client.CreateSessionAsync(new PrmSessionCreate(int.MaxValue), default); var createContainerParam = new PrmContainerCreate( new FrostFsContainerInfo( new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(3)), [new FrostFsAttributePair("__SYSTEM__DISABLE_HOMOMORPHIC_HASHING", "true")]), PrmWait.DefaultParams, xheaders: ["key1", "value1"]); var containerId = await client.CreateContainerAsync(createContainerParam, default); await AddObjectRules(client, containerId); var bytes = GetRandomBytes(1024); var param = new PrmObjectPut( new FrostFsObjectHeader( containerId: containerId, type: FrostFsObjectType.Regular, [new FrostFsAttributePair("fileName", "test")]), sessionToken: token); var stream = await client.PutObjectAsync(param, default).ConfigureAwait(true); await stream.WriteAsync(bytes.AsMemory()); var objectId = await stream.CompleteAsync(); var @object = await client.GetObjectAsync(new PrmObjectGet(containerId, objectId), default) .ConfigureAwait(true); var downloadedBytes = new byte[@object.Header.PayloadLength]; MemoryStream ms = new(downloadedBytes); ReadOnlyMemory? chunk = null; while ((chunk = await @object.ObjectReader!.ReadChunk().ConfigureAwait(true)) != null) { ms.Write(chunk.Value.Span); } Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); await Cleanup(client).ConfigureAwait(true); } [Fact] public async void FilterTest() { var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); await Cleanup(client); var createContainerParam = new PrmContainerCreate( new FrostFsContainerInfo( new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(3)), [new FrostFsAttributePair("__SYSTEM__DISABLE_HOMOMORPHIC_HASHING", "true")]), lightWait); var containerId = await client.CreateContainerAsync(createContainerParam, default); await AddObjectRules(client, containerId); var bytes = new byte[] { 1, 2, 3 }; var param = new PrmObjectPut( new FrostFsObjectHeader( containerId: containerId, type: FrostFsObjectType.Regular, [new FrostFsAttributePair("fileName", "test")], new FrostFsSplit())); var stream = await client.PutObjectAsync(param, default).ConfigureAwait(true); await stream.WriteAsync(bytes.AsMemory()); var objectId = await stream.CompleteAsync(); var ecdsaKey = keyString.LoadWif(); var networkInfo = await client.GetNetmapSnapshotAsync(default); await CheckFilter(client, containerId, new FilterByContainerId(FrostFsMatchType.Equals, containerId)); await CheckFilter(client, containerId, new FilterByOwnerId(FrostFsMatchType.Equals, FrostFsOwner.FromKey(ecdsaKey))); await CheckFilter(client, containerId, new FilterBySplitId(FrostFsMatchType.Equals, param.Header!.Split!.SplitId)); await CheckFilter(client, containerId, new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test")); await CheckFilter(client, containerId, new FilterByObjectId(FrostFsMatchType.Equals, objectId)); await CheckFilter(client, containerId, new FilterByVersion(FrostFsMatchType.Equals, networkInfo.NodeInfoCollection[0].Version)); await CheckFilter(client, containerId, new FilterByEpoch(FrostFsMatchType.Equals, networkInfo.Epoch)); await CheckFilter(client, containerId, new FilterByPayloadLength(FrostFsMatchType.Equals, 3)); var checkSum = CheckSum.CreateCheckSum(bytes); // await CheckFilter(client, containerId, new FilterByPayloadHash(FrostFsMatchType.Equals, checkSum)); await CheckFilter(client, containerId, new FilterByPhysicallyStored()); } private static async Task CheckFilter(IFrostFSClient client, FrostFsContainerId containerId, IObjectFilter filter) { var resultObjectsCount = 0; PrmObjectSearch searchParam = new(containerId, null, [], filter); await foreach (var objId in client.SearchObjectsAsync(searchParam, default)) { resultObjectsCount++; var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objId), default); } Assert.True(0 < resultObjectsCount, $"Filter for {filter.Key} doesn't work"); } [Theory] [InlineData(1)] [InlineData(3 * 1024 * 1024)] // exactly one chunk size - 3MB [InlineData(6 * 1024 * 1024 + 100)] public async void SimpleScenarioTest(int objectSize) { bool callbackInvoked = false; var options = ClientOptions; options.Value.Callback = new((cs) => { callbackInvoked = true; Assert.True(cs.ElapsedMicroSeconds > 0); }); var client = FrostFSClient.GetInstance(options, GrpcChannel); await Cleanup(client); var ctx = new CallContext(TimeSpan.FromSeconds(20)); var createContainerParam = new PrmContainerCreate( new FrostFsContainerInfo( new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(3)), [new FrostFsAttributePair("__SYSTEM__DISABLE_HOMOMORPHIC_HASHING", "true")]), PrmWait.DefaultParams, xheaders: ["testKey", "testValue"]); var containerId = await client.CreateContainerAsync(createContainerParam, ctx); var container = await client.GetContainerAsync(new PrmContainerGet(containerId), default); Assert.NotNull(container); Assert.True(callbackInvoked); await AddObjectRules(client, containerId); var bytes = GetRandomBytes(objectSize); var param = new PrmObjectPut( new FrostFsObjectHeader( containerId: containerId, type: FrostFsObjectType.Regular, [new FrostFsAttributePair("fileName", "test")])); var stream = await client.PutObjectAsync(param, default).ConfigureAwait(true); await stream.WriteAsync(bytes.AsMemory()); var objectId = await stream.CompleteAsync(); var filter1 = new FilterByOwnerId(FrostFsMatchType.Equals, OwnerId!); await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(containerId, null, [], filter1), default)) { var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId, false), default); } var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); bool hasObject = false; await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(containerId, null, [], filter), default)) { hasObject = true; var res = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId), default); var objHeader = res.HeaderInfo; Assert.NotNull(objHeader); Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); Assert.NotNull(objHeader.Attributes); Assert.Single(objHeader.Attributes); Assert.Equal("fileName", objHeader.Attributes.First().Key); Assert.Equal("test", objHeader.Attributes.First().Value); } Assert.True(hasObject); var @object = await client.GetObjectAsync(new PrmObjectGet(containerId, objectId), default); var downloadedBytes = new byte[@object.Header.PayloadLength]; MemoryStream ms = new(downloadedBytes); ReadOnlyMemory? chunk = null; while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) { ms.Write(chunk.Value.Span); } Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); } [Theory] [InlineData(1)] [InlineData(500 * 1024)] [InlineData(3 * 1024 * 1024)] public async void PutSingleObjectTest(int objectSize) { var options = ClientOptions; var client = FrostFSClient.GetInstance(options, GrpcChannel); await Cleanup(client); var ctx = new CallContext(); var createContainerParam = new PrmContainerCreate( new FrostFsContainerInfo( new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(3)), [new FrostFsAttributePair("__SYSTEM__DISABLE_HOMOMORPHIC_HASHING", "true")]), PrmWait.DefaultParams, xheaders: ["testKey1", "testValue1"]); var containerId = await client.CreateContainerAsync(createContainerParam, ctx); var container = await client.GetContainerAsync(new PrmContainerGet(containerId), default); Assert.NotNull(container); await AddObjectRules(client, containerId); var bytes = GetRandomBytes(objectSize); var header = new FrostFsObjectHeader( containerId: containerId, type: FrostFsObjectType.Regular, [new FrostFsAttributePair("fileName1", "test1")]); var obj = new FrostFsObject(header) { SingleObjectPayload = bytes }; var param = new PrmSingleObjectPut(obj); var objectId = await client.PutSingleObjectAsync(param, default).ConfigureAwait(true); var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName1", "test1"); bool hasObject = false; await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(containerId, null, [], filter), default)) { hasObject = true; var res = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId), default); var objHeader = res.HeaderInfo; Assert.NotNull(objHeader); Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); Assert.NotNull(objHeader.Attributes); Assert.Single(objHeader.Attributes); Assert.Equal("fileName1", objHeader.Attributes.First().Key); Assert.Equal("test1", objHeader.Attributes.First().Value); } Assert.True(hasObject); var @object = await client.GetObjectAsync(new PrmObjectGet(containerId, objectId), default); var downloadedBytes = new byte[@object.Header.PayloadLength]; MemoryStream ms = new(downloadedBytes); ReadOnlyMemory? chunk = null; while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) { ms.Write(chunk.Value.Span); } Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); await Cleanup(client); await foreach (var _ in client.ListContainersAsync(default, default)) { Assert.Fail("Containers exist"); } } [Fact] public async void CleanupTest() { var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); await Cleanup(client); } [Fact] public async void PatchTest() { var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); await Cleanup(client); var createContainerParam = new PrmContainerCreate( new FrostFsContainerInfo( new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1)), [new FrostFsAttributePair("__SYSTEM__DISABLE_HOMOMORPHIC_HASHING", "true")]), PrmWait.DefaultParams, xheaders: ["testKey", "testValue"]); var containerId = await client.CreateContainerAsync(createContainerParam, default); var container = await client.GetContainerAsync(new PrmContainerGet(containerId), default); Assert.NotNull(container); await AddObjectRules(client, containerId); var bytes = new byte[1024]; for (int i = 0; i < 1024; i++) { bytes[i] = 31; } var param = new PrmObjectPut( new FrostFsObjectHeader( containerId: containerId, type: FrostFsObjectType.Regular, [new FrostFsAttributePair("fileName", "test")])); var stream = await client.PutObjectAsync(param, default).ConfigureAwait(true); await stream.WriteAsync(bytes.AsMemory()); var objectId = await stream.CompleteAsync(); var patch = new byte[256]; for (int i = 0; i < patch.Length; i++) { patch[i] = 32; } var range = new FrostFsRange(64, (ulong)patch.Length); var patchParams = new PrmObjectPatch( new FrostFsAddress(containerId, objectId), payload: new MemoryStream(patch), maxChunkLength: 256, range: range); var newIbjId = await client.PatchObjectAsync(patchParams, default); var @object = await client.GetObjectAsync(new PrmObjectGet(containerId, newIbjId), default); var downloadedBytes = new byte[@object.Header.PayloadLength]; MemoryStream ms = new(downloadedBytes); ReadOnlyMemory? chunk = null; while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) { ms.Write(chunk.Value.Span); } for (int i = 0; i < (int)range.Offset; i++) Assert.Equal(downloadedBytes[i], bytes[i]); var rangeEnd = range.Offset + range.Length; for (int i = (int)range.Offset; i < (int)rangeEnd; i++) Assert.Equal(downloadedBytes[i], patch[i - (int)range.Offset]); for (int i = (int)rangeEnd; i < bytes.Length; i++) Assert.Equal(downloadedBytes[i], bytes[i]); await Cleanup(client); await foreach (var _ in client.ListContainersAsync(default, default)) { Assert.Fail("Containers exist"); } } [Fact] public async void RangeTest() { var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); await Cleanup(client); var createContainerParam = new PrmContainerCreate( new FrostFsContainerInfo( new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(3)), [new FrostFsAttributePair("__SYSTEM__DISABLE_HOMOMORPHIC_HASHING", "true")]), PrmWait.DefaultParams, xheaders: ["testKey", "testValue"]); var containerId = await client.CreateContainerAsync(createContainerParam, default); var container = await client.GetContainerAsync(new PrmContainerGet(containerId), default); Assert.NotNull(container); await AddObjectRules(client, containerId); var bytes = new byte[256]; for (int i = 0; i < 256; i++) { bytes[i] = (byte)i; } var param = new PrmObjectPut( new FrostFsObjectHeader( containerId: containerId, type: FrostFsObjectType.Regular)); var stream = await client.PutObjectAsync(param, default).ConfigureAwait(true); await stream.WriteAsync(bytes.AsMemory()); var objectId = await stream.CompleteAsync(); var rangeParam = new PrmRangeGet(containerId, objectId, new FrostFsRange(50, 100)); var rangeReader = await client.GetRangeAsync(rangeParam, default); var downloadedBytes = new byte[rangeParam.Range.Length]; MemoryStream ms = new(downloadedBytes); ReadOnlyMemory? chunk; while ((chunk = await rangeReader!.ReadChunk()) != null) { ms.Write(chunk.Value.Span); } Assert.Equal(SHA256.HashData(bytes.AsSpan().Slice(50, 100)), SHA256.HashData(downloadedBytes)); } [Fact] public async void RangeHashTest() { var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); await Cleanup(client); var createContainerParam = new PrmContainerCreate( new FrostFsContainerInfo( new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(3)), [new FrostFsAttributePair("__SYSTEM__DISABLE_HOMOMORPHIC_HASHING", "true")]), PrmWait.DefaultParams, xheaders: ["testKey", "testValue"]); var containerId = await client.CreateContainerAsync(createContainerParam, default); var container = await client.GetContainerAsync(new PrmContainerGet(containerId), default); Assert.NotNull(container); await AddObjectRules(client, containerId); var bytes = new byte[256]; for (int i = 0; i < 256; i++) { bytes[i] = (byte)i; } var param = new PrmObjectPut( new FrostFsObjectHeader( containerId: containerId, type: FrostFsObjectType.Regular)); var stream = await client.PutObjectAsync(param, default).ConfigureAwait(true); await stream.WriteAsync(bytes.AsMemory()); var objectId = await stream.CompleteAsync(); var rangeParam = new PrmRangeHashGet(containerId, objectId, [new FrostFsRange(100, 64)], bytes); var hashes = await client.GetRangeHashAsync(rangeParam, default); foreach (var hash in hashes) { var x = hash[..32].ToArray(); Assert.NotNull(x); Assert.True(x.Length > 0); } } [Theory] [InlineData(1)] [InlineData(3 * 1024 * 1024)] // exactly one chunk size - 3MB [InlineData(6 * 1024 * 1024 + 100)] public async void SimpleScenarioWithSessionTest(int objectSize) { var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); var token = await client.CreateSessionAsync(new PrmSessionCreate(int.MaxValue), default); await Cleanup(client); var ctx = new CallContext(TimeSpan.FromSeconds(20)); var createContainerParam = new PrmContainerCreate( new FrostFsContainerInfo( new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(3)), [new FrostFsAttributePair("__SYSTEM__DISABLE_HOMOMORPHIC_HASHING", "true")]), PrmWait.DefaultParams); var container = await client.CreateContainerAsync(createContainerParam, ctx); var containerInfo = await client.GetContainerAsync(new PrmContainerGet(container), ctx); Assert.NotNull(containerInfo); await AddObjectRules(client, container); var bytes = GetRandomBytes(objectSize); var param = new PrmObjectPut( new FrostFsObjectHeader( containerId: container, type: FrostFsObjectType.Regular, [new FrostFsAttributePair("fileName", "test")]), sessionToken: token); var stream = await client.PutObjectAsync(param, default).ConfigureAwait(true); await stream.WriteAsync(bytes.AsMemory()); var objectId = await stream.CompleteAsync(); var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); bool hasObject = false; await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(container, token, [], filter), default)) { hasObject = true; var res = await client.GetObjectHeadAsync(new PrmObjectHeadGet(container, objectId, false, token), default); var objHeader = res.HeaderInfo; Assert.NotNull(objHeader); Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); Assert.NotNull(objHeader.Attributes); Assert.Single(objHeader.Attributes); Assert.Equal("fileName", objHeader.Attributes.First().Key); Assert.Equal("test", objHeader.Attributes.First().Value); } Assert.True(hasObject); var @object = await client.GetObjectAsync(new PrmObjectGet(container, objectId, token), default); var downloadedBytes = new byte[@object.Header.PayloadLength]; MemoryStream ms = new(downloadedBytes); ReadOnlyMemory? chunk = null; while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) { ms.Write(chunk.Value.Span); } Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); await Cleanup(client); await foreach (var _ in client.ListContainersAsync(default, default)) { Assert.Fail("Containers exist"); } } [Fact] public async void ClientCutScenarioTest() { var options = ClientOptions; options.Value.Interceptors.Add(new CallbackInterceptor()); var client = FrostFSClient.GetInstance(options, GrpcChannel); await Cleanup(client); int[] replicas = [3]; //1 foreach (var rep in replicas) { var createContainerParam = new PrmContainerCreate( new FrostFsContainerInfo( new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(rep)), [new FrostFsAttributePair("__SYSTEM__DISABLE_HOMOMORPHIC_HASHING", "true")]), lightWait); var containerId = await client.CreateContainerAsync(createContainerParam, default); var ctx = new CallContext(TimeSpan.FromSeconds(10)); var container = await client.GetContainerAsync(new PrmContainerGet(containerId), ctx); Assert.NotNull(container); await AddObjectRules(client, containerId); var mb = 1024 * 1024; int[] objectSizes = [1, 1024, 64 * mb + 1]; //64 * mb, 64 * mb - 1, , 2 * 64 * mb + 256]; foreach (var objectSize in objectSizes) { byte[] bytes = GetRandomBytes(objectSize); var param = new PrmObjectClientCutPut( new FrostFsObjectHeader( containerId: containerId, type: FrostFsObjectType.Regular, [new FrostFsAttributePair("fileName", "test")]), payload: new MemoryStream(bytes)); var objectId = await client.PutClientCutObjectAsync(param, default).ConfigureAwait(true); var @object = await client.GetObjectAsync(new PrmObjectGet(containerId, objectId), default); var downloadedBytes = new byte[@object.Header.PayloadLength]; MemoryStream ms = new(downloadedBytes); ReadOnlyMemory? chunk = null; while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) { ms.Write(chunk.Value.Span); } Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); } } } [Fact] public async void NodeInfoCallbackAndInterceptorTest() { bool callbackInvoked = false; bool interceptorInvoked = false; var options = ClientOptions; options.Value.Callback = (cs) => { callbackInvoked = true; Assert.True(cs.ElapsedMicroSeconds > 0); }; options.Value.Interceptors.Add(new CallbackInterceptor(s => interceptorInvoked = true)); var client = FrostFSClient.GetInstance(options, GrpcChannel); var result = await client.GetNodeInfoAsync(default); Assert.True(callbackInvoked); Assert.True(interceptorInvoked); Assert.Equal(2, result.Version.Major); Assert.Equal(13, result.Version.Minor); Assert.Equal(NodeState.Online, result.State); Assert.Equal(33, result.PublicKey.Length); Assert.NotNull(result.Addresses); Assert.True(result.Attributes.Count > 0); } private static byte[] GetRandomBytes(int size) { Random rnd = new(); var bytes = new byte[size]; rnd.NextBytes(bytes); return bytes; } private static IOptions GetClientOptions(string key, string url) { return Options.Create(new ClientSettings { Key = key, Host = url }); } static async Task Cleanup(IFrostFSClient client) { await foreach (var cid in client.ListContainersAsync(default, default)) { await client.DeleteContainerAsync(new PrmContainerDelete(cid, lightWait), default); } } private static async Task AddContainerRules(IFrostFSClient client, FrostFsContainerId containerId) { var addChainPrm = new PrmApeChainAdd( new FrostFsChainTarget(FrostFsTargetType.Container, containerId.GetValue()), new FrostFsChain { ID = Encoding.ASCII.GetBytes("chain-id-test1"), Rules = [ new FrostFsRule { Status = RuleStatus.Allow, Actions = new Actions(inverted: false, names: ["*"]), Resources = new Resource (inverted: false, names: [$"native:container/*"]), Any = false, Conditions = [] } ], MatchType = RuleMatchType.DenyPriority } ); await client.AddChainAsync(addChainPrm, default); var listChainPrm = new PrmApeChainList(new FrostFsChainTarget(FrostFsTargetType.Container, containerId.GetValue())); while (true) { await Task.Delay(1000); var chains = await client.ListChainAsync(listChainPrm, default); if (chains.Length > 0) break; } await Task.Delay(8000); } private static async Task AddObjectRules(IFrostFSClient client, FrostFsContainerId containerId) { var addChainPrm = new PrmApeChainAdd( new FrostFsChainTarget(FrostFsTargetType.Container, containerId.GetValue()), new FrostFsChain { ID = Encoding.ASCII.GetBytes("chain-id-test"), Rules = [ new FrostFsRule { Status = RuleStatus.Allow, Actions = new Actions(inverted: false, names: ["*"]), Resources = new Resource (inverted: false, names: [$"native:object/*"]), Any = false, Conditions = [] } ], MatchType = RuleMatchType.DenyPriority } ); await client.AddChainAsync(addChainPrm, default); await Task.Delay(8000); } }