From 79764c9b6737f806b28671ab6eda3c4fe71d864a Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Mon, 10 Jun 2024 10:44:48 +0300 Subject: [PATCH 1/3] [#4] Infrastructure for Client Cut Signed-off-by: Pavel Gross --- FrostFS.SDK.sln | 7 +- src/FrostFS.SDK.ClientV2/Client.cs | 10 +- src/FrostFS.SDK.ClientV2/Extensions/Object.cs | 44 +++ .../FrostFS.SDK.ClientV2.csproj | 1 + .../Interfaces/IFrostFSClient.cs | 13 +- .../Mappers/GRPC/ContainerId.cs | 3 +- .../Mappers/GRPC/Object.cs | 92 ++++- .../Mappers/GRPC/ObjectId.cs | 5 + src/FrostFS.SDK.ClientV2/RequestSigner.cs | 2 +- .../Services/Container.cs | 8 +- src/FrostFS.SDK.ClientV2/Services/Object.cs | 317 ++++++++++++------ src/FrostFS.SDK.Cryptography/Base58.cs | 2 - src/FrostFS.SDK.Cryptography/Helper.cs | 1 - src/FrostFS.SDK.ModelsV2/ContainerId.cs | 28 +- src/FrostFS.SDK.ModelsV2/Object/Object.cs | 65 ++++ .../Object/ObjectAttribute.cs | 13 + .../{Object.cs => Object/ObjectFilter.cs} | 39 --- .../Object/ObjectHeader.cs | 37 ++ src/FrostFS.SDK.ModelsV2/Splitter.cs | 88 +++++ .../object/Extension.Message.cs | 56 ++++ .../session/Extension.XHeader.cs | 2 +- 21 files changed, 642 insertions(+), 191 deletions(-) create mode 100644 src/FrostFS.SDK.ClientV2/Extensions/Object.cs create mode 100644 src/FrostFS.SDK.ModelsV2/Object/Object.cs create mode 100644 src/FrostFS.SDK.ModelsV2/Object/ObjectAttribute.cs rename src/FrostFS.SDK.ModelsV2/{Object.cs => Object/ObjectFilter.cs} (56%) create mode 100644 src/FrostFS.SDK.ModelsV2/Object/ObjectHeader.cs create mode 100644 src/FrostFS.SDK.ModelsV2/Splitter.cs diff --git a/FrostFS.SDK.sln b/FrostFS.SDK.sln index d7d789e..67e5eb3 100644 --- a/FrostFS.SDK.sln +++ b/FrostFS.SDK.sln @@ -29,9 +29,8 @@ Global {5012EF96-9C9E-4E77-BC78-B4111EE54107}.Debug|Any CPU.Build.0 = Debug|Any CPU {5012EF96-9C9E-4E77-BC78-B4111EE54107}.Release|Any CPU.ActiveCfg = Release|Any CPU {5012EF96-9C9E-4E77-BC78-B4111EE54107}.Release|Any CPU.Build.0 = Release|Any CPU - {B738F3E1-654D-41A3-B068-58ED122BB688}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B738F3E1-654D-41A3-B068-58ED122BB688}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B738F3E1-654D-41A3-B068-58ED122BB688}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B738F3E1-654D-41A3-B068-58ED122BB688}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE EndGlobalSection EndGlobal diff --git a/src/FrostFS.SDK.ClientV2/Client.cs b/src/FrostFS.SDK.ClientV2/Client.cs index 7cb47d6..7e9e29c 100644 --- a/src/FrostFS.SDK.ClientV2/Client.cs +++ b/src/FrostFS.SDK.ClientV2/Client.cs @@ -1,5 +1,6 @@ using System; using System.Security.Cryptography; +using System.Threading.Tasks; using FrostFS.Container; using FrostFS.Netmap; using FrostFS.Object; @@ -25,7 +26,12 @@ public partial class Client: IFrostFSClient private ObjectService.ObjectServiceClient? _objectServiceClient; private SessionService.SessionServiceClient? _sessionServiceClient; - public Client(string key, string host) + public static IFrostFSClient GetInstance(string key, string host) + { + return new Client(key, host); + } + + private Client(string key, string host) { // TODO: Развязать клиент и реализацию GRPC _key = key.LoadWif(); @@ -37,7 +43,7 @@ public partial class Client: IFrostFSClient InitSessionClient(); CheckFrostFsVersionSupport(); } - + private async void CheckFrostFsVersionSupport() { var localNodeInfo = await GetLocalNodeInfoAsync(); diff --git a/src/FrostFS.SDK.ClientV2/Extensions/Object.cs b/src/FrostFS.SDK.ClientV2/Extensions/Object.cs new file mode 100644 index 0000000..93d8d5f --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Extensions/Object.cs @@ -0,0 +1,44 @@ + +using System.Collections.Generic; +using FrostFS.SDK.ModelsV2; + +namespace FrostFS.SDK.ClientV2.Extensions; + +public static class Extensions +{ + public static ModelsV2.Object SetPayloadLength(this ModelsV2.Object obj, ulong length) + { + obj.Header.PayloadLength = length; + return obj; + } + + public static ModelsV2.Object AddAttribute(this ModelsV2.Object obj, string key, string value) + { + obj.AddAttribute(new ObjectAttribute(key, value)); + return obj; + } + + public static ModelsV2.Object AddAttribute(this ModelsV2.Object obj, ObjectAttribute attribute) + { + obj.Header.Attributes.Add(attribute); + return obj; + } + + public static ModelsV2.Object AddAttributes(this ModelsV2.Object obj, IEnumerable attributes) + { + obj.Header.Attributes.AddRange(attributes); + return obj; + } + + public static ModelsV2.Object SetSplit(this ModelsV2.Object obj, Split split) + { + obj.Header.Split = split; + return obj; + } + + public static LinkObject AddChildren(this LinkObject linkObject, IEnumerable objectIds) + { + linkObject.Header.Split.Children.AddRange(objectIds); + return linkObject; + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/FrostFS.SDK.ClientV2.csproj b/src/FrostFS.SDK.ClientV2/FrostFS.SDK.ClientV2.csproj index 0208178..d446e0e 100644 --- a/src/FrostFS.SDK.ClientV2/FrostFS.SDK.ClientV2.csproj +++ b/src/FrostFS.SDK.ClientV2/FrostFS.SDK.ClientV2.csproj @@ -8,6 +8,7 @@ + diff --git a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs b/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs index 8412c6c..5b82d1b 100644 --- a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs +++ b/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs @@ -9,13 +9,24 @@ namespace FrostFS.SDK.ClientV2.Interfaces; public interface IFrostFSClient { Task GetContainerAsync(ContainerId containerId); + IAsyncEnumerable ListContainersAsync(); + Task CreateContainerAsync(ModelsV2.Container container); + Task DeleteContainerAsync(ContainerId containerId); + Task GetObjectHeadAsync(ContainerId containerId, ObjectId objectId); + Task GetObjectAsync(ContainerId containerId, ObjectId objectId); + Task PutObjectAsync(ObjectHeader header, Stream payload); + Task PutObjectAsync(ObjectHeader header, byte[] payload); + + Task PutSingleObjectAsync(ModelsV2.Object obj); + Task DeleteObjectAsync(ContainerId containerId, ObjectId objectId); - IAsyncEnumerable SearchObjectsAsync(ContainerId cid, params ObjectFilter[] filters); + + IAsyncEnumerable SearchObjectsAsync(ContainerId cid, params ObjectFilter[] filters); } \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/ContainerId.cs b/src/FrostFS.SDK.ClientV2/Mappers/GRPC/ContainerId.cs index 1bfd614..920c49a 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/ContainerId.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/GRPC/ContainerId.cs @@ -1,4 +1,5 @@ using FrostFS.Refs; +using FrostFS.SDK.Cryptography; using FrostFS.SDK.ModelsV2; using Google.Protobuf; @@ -10,7 +11,7 @@ public static class ContainerIdMapper { return new ContainerID { - Value = ByteString.CopyFrom(containerId.ToHash()) + Value = ByteString.CopyFrom(Base58.Decode(containerId.Value)) }; } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/Object.cs b/src/FrostFS.SDK.ClientV2/Mappers/GRPC/Object.cs index b6e68f6..ef21202 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/Object.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/GRPC/Object.cs @@ -1,8 +1,9 @@ using System; using System.Linq; - using FrostFS.Object; +using FrostFS.SDK.Cryptography; using FrostFS.SDK.ModelsV2; +using Google.Protobuf; using MatchType = FrostFS.Object.MatchType; using ObjectType = FrostFS.Object.ObjectType; @@ -29,11 +30,20 @@ public static class ObjectFilterMapper { public static SearchRequest.Types.Body.Types.Filter ToGrpcMessage(this ObjectFilter filter) { - var objMatchTypeName = Enum.GetName(typeof(MatchType), filter.MatchType) - ?? throw new ArgumentException($"Unknown MatchType. Value: '{filter.MatchType}'."); + var objMatchTypeName = filter.MatchType switch + { + ModelsV2.Enums.ObjectMatchType.Unspecified => MatchType.Unspecified, + ModelsV2.Enums.ObjectMatchType.Equals => MatchType.StringEqual, + ModelsV2.Enums.ObjectMatchType.NotEquals => MatchType.StringNotEqual, + ModelsV2.Enums.ObjectMatchType.KeyAbsent => MatchType.NotPresent, + ModelsV2.Enums.ObjectMatchType.StartsWith => MatchType.CommonPrefix, + + _ => throw new ArgumentException($"Unknown MatchType. Value: '{filter.MatchType}'.") + }; + return new SearchRequest.Types.Body.Types.Filter { - MatchType = (MatchType)Enum.Parse(typeof(MatchType), objMatchTypeName), + MatchType = objMatchTypeName, Key = filter.Key, Value = filter.Value }; @@ -44,13 +54,19 @@ public static class ObjectHeaderMapper { public static Header ToGrpcMessage(this ObjectHeader header) { - var objTypeName = Enum.GetName(typeof(ObjectType), header.ObjectType) - ?? throw new ArgumentException($"Unknown ObjectType. Value: '{header.ObjectType}'."); - var head = new Header + var objTypeName = header.ObjectType switch { - Attributes = { }, + ModelsV2.Enums.ObjectType.Regular => ObjectType.Regular, + ModelsV2.Enums.ObjectType.Lock => ObjectType.Lock, + ModelsV2.Enums.ObjectType.Tombstone => ObjectType.Tombstone, + _ => throw new ArgumentException($"Unknown ObjectType. Value: '{header.ObjectType}'.") + }; + + var head = new Header + { ContainerId = header.ContainerId.ToGrpcMessage(), - ObjectType = (ObjectType)Enum.Parse(typeof(ObjectType), objTypeName) + ObjectType = objTypeName, + PayloadLength = header.PayloadLength }; foreach (var attribute in header.Attributes) @@ -58,20 +74,42 @@ public static class ObjectHeaderMapper head.Attributes.Add(attribute.ToGrpcMessage()); } + var split = header.Split; + if (split != null) + { + head.Split = new Header.Types.Split + { + Parent = split.Parent?.ToGrpcMessage(), + ParentSignature = split.ParentSignature?.ToGrpcMessage(), + ParentHeader = split.ParentHeader?.ToGrpcMessage(), + Previous = split.Previous?.ToGrpcMessage(), + SplitId = split.SplitId != null ? ByteString.CopyFrom(split.SplitId.ToBinary()) : null + }; + + if (split.Children != null && split.Children.Any()) + head.Split.Children.AddRange(split.Children.Select(id => id.ToGrpcMessage())); + } + return head; } public static ObjectHeader ToModel(this Header header) { - var objTypeName = Enum.GetName(typeof(ModelsV2.Enums.ObjectType), header.ObjectType) - ?? throw new ArgumentException($"Unknown ObjectType. Value: '{header.ObjectType}'."); + var objTypeName = header.ObjectType switch + { + ObjectType.Regular => ModelsV2.Enums.ObjectType.Regular, + ObjectType.Lock => ModelsV2.Enums.ObjectType.Lock, + ObjectType.Tombstone => ModelsV2.Enums.ObjectType.Tombstone, + _ => throw new ArgumentException($"Unknown ObjectType. Value: '{header.ObjectType}'.") + }; + return new ObjectHeader( - ContainerId.FromHash(header.ContainerId.Value.ToByteArray()), - (ModelsV2.Enums.ObjectType)Enum.Parse(typeof(ModelsV2.Enums.ObjectType), objTypeName), + new ContainerId(Base58.Encode(header.ContainerId.Value.ToByteArray())), + objTypeName, header.Attributes.Select(attribute => attribute.ToModel()).ToArray() ) { - Size = (long)header.PayloadLength, + PayloadLength = header.PayloadLength, Version = header.Version.ToModel() }; } @@ -81,11 +119,33 @@ public static class ObjectMapper { public static ModelsV2.Object ToModel(this Object.Object obj) { - return new ModelsV2.Object + return new ModelsV2.Object() { Header = obj.Header.ToModel(), ObjectId = ObjectId.FromHash(obj.ObjectId.Value.ToByteArray()), Payload = obj.Payload.ToByteArray() }; + } +} + +public static class SignatureMapper +{ + public static Refs.Signature ToGrpcMessage(this Signature signature) + { + var scheme = signature.Scheme switch + { + SignatureScheme.EcdsaRfc6979Sha256 => Refs.SignatureScheme.EcdsaRfc6979Sha256, + SignatureScheme.EcdsaRfc6979Sha256WalletConnect => Refs.SignatureScheme.EcdsaRfc6979Sha256WalletConnect, + SignatureScheme.EcdsaSha512 => Refs.SignatureScheme.EcdsaSha512, + _ => throw new ArgumentException(message: $"Unexpected enum value: {signature.Scheme}", paramName: nameof(signature.Scheme)) + }; + + return new Refs.Signature + { + Key = ByteString.CopyFrom(signature.Key), + Scheme = scheme, + Sign = ByteString.CopyFrom(signature.Sign) + }; } -} \ No newline at end of file +} + diff --git a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/ObjectId.cs b/src/FrostFS.SDK.ClientV2/Mappers/GRPC/ObjectId.cs index 4c28035..bfd568d 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/ObjectId.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/GRPC/ObjectId.cs @@ -13,4 +13,9 @@ public static class ObjectIdMapper Value = ByteString.CopyFrom(objectId.ToHash()) }; } + + public static ObjectId ToModel(this ObjectID objectId) + { + return ObjectId.FromHash(objectId.Value.ToByteArray()); + } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/RequestSigner.cs b/src/FrostFS.SDK.ClientV2/RequestSigner.cs index 3747c92..23f8eb6 100644 --- a/src/FrostFS.SDK.ClientV2/RequestSigner.cs +++ b/src/FrostFS.SDK.ClientV2/RequestSigner.cs @@ -91,7 +91,7 @@ public static class RequestSigner { IRequest => new RequestVerificationHeader(), IResponse => new ResponseVerificationHeader(), - _ => throw new InvalidOperationException("Unsopported message type") + _ => throw new InvalidOperationException("Unsupported message type") }; var verifyOrigin = message.GetVerificationHeader(); diff --git a/src/FrostFS.SDK.ClientV2/Services/Container.cs b/src/FrostFS.SDK.ClientV2/Services/Container.cs index 886768c..7566492 100644 --- a/src/FrostFS.SDK.ClientV2/Services/Container.cs +++ b/src/FrostFS.SDK.ClientV2/Services/Container.cs @@ -1,5 +1,6 @@ using FrostFS.Container; using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Cryptography; using FrostFS.SDK.ModelsV2; using System.Collections.Generic; using System.Threading.Tasks; @@ -17,6 +18,7 @@ public partial class Client ContainerId = cid.ToGrpcMessage() }, }; + request.AddMetaHeader(); request.Sign(_key); var response = await _containerServiceClient.GetAsync(request); @@ -33,13 +35,14 @@ public partial class Client OwnerId = OwnerId.ToGrpcMessage() } }; + request.AddMetaHeader(); request.Sign(_key); var response = await _containerServiceClient.ListAsync(request); Verifier.CheckResponse(response); foreach (var cid in response.Body.ContainerIds) { - yield return ContainerId.FromHash(cid.Value.ToByteArray()); + yield return new ContainerId(Base58.Encode(cid.Value.ToByteArray())); } } @@ -48,6 +51,7 @@ public partial class Client var cntnr = container.ToGrpcMessage(); cntnr.OwnerId = OwnerId.ToGrpcMessage(); cntnr.Version = Version.ToGrpcMessage(); + var request = new PutRequest { Body = new PutRequest.Types.Body @@ -60,7 +64,7 @@ public partial class Client request.Sign(_key); var response = await _containerServiceClient.PutAsync(request); Verifier.CheckResponse(response); - return ContainerId.FromHash(response.Body.ContainerId.Value.ToByteArray()); + return new ContainerId(Base58.Encode(response.Body.ContainerId.Value.ToByteArray())); } public async Task DeleteContainerAsync(ContainerId cid) diff --git a/src/FrostFS.SDK.ClientV2/Services/Object.cs b/src/FrostFS.SDK.ClientV2/Services/Object.cs index 180e351..bc98dc8 100644 --- a/src/FrostFS.SDK.ClientV2/Services/Object.cs +++ b/src/FrostFS.SDK.ClientV2/Services/Object.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Threading.Tasks; using Google.Protobuf; @@ -9,9 +10,10 @@ using FrostFS.Object; using FrostFS.Refs; using FrostFS.SDK.ClientV2.Mappers.GRPC; using FrostFS.SDK.Cryptography; -using FrostFS.SDK.ModelsV2; using FrostFS.Session; +using FrostFS.SDK.ModelsV2; + namespace FrostFS.SDK.ClientV2; public partial class Client @@ -20,7 +22,7 @@ public partial class Client { var request = new HeadRequest { - Body = new HeadRequest.Types.Body + Body = new HeadRequest.Types.Body { Address = new Address { @@ -29,12 +31,12 @@ public partial class Client } } }; - + request.AddMetaHeader(); request.Sign(_key); - var response = await _objectServiceClient.HeadAsync(request); + var response = await _objectServiceClient!.HeadAsync(request); Verifier.CheckResponse(response); - + return response.Body.Header.Header.ToModel(); } @@ -45,7 +47,6 @@ public partial class Client { Body = new GetRequest.Types.Body { - Raw = false, Address = new Address { ContainerId = cid.ToGrpcMessage(), @@ -76,85 +77,8 @@ public partial class Client public async Task PutObjectAsync(ObjectHeader header, byte[] payload) { - return await PutObject(header, new MemoryStream(payload)); - } - - public async Task DeleteObjectAsync(ContainerId cid, ObjectId oid) - { - var request = new DeleteRequest - { - Body = new DeleteRequest.Types.Body - { - Address = new Address - { - ContainerId = cid.ToGrpcMessage(), - ObjectId = oid.ToGrpcMessage() - } - } - }; - - request.AddMetaHeader(); - request.Sign(_key); - var response = await _objectServiceClient.DeleteAsync(request); - Verifier.CheckResponse(response); - } - - public async IAsyncEnumerable SearchObjectsAsync(ContainerId cid, params ObjectFilter[] filters) - { - var request = new SearchRequest - { - Body = new SearchRequest.Types.Body - { - ContainerId = cid.ToGrpcMessage(), - Filters = { }, - Version = 1 - } - }; - - foreach (var filter in filters) - { - request.Body.Filters.Add(filter.ToGrpcMessage()); - } - - request.AddMetaHeader(); - request.Sign(_key); - var objectsIds = SearchObjects(request); - - await foreach (var oid in objectsIds) - { - yield return ObjectId.FromHash(oid.Value.ToByteArray()); - } - } - - private async Task GetObject(GetRequest request) - { - using var stream = GetObjectInit(request); - var obj = await stream.ReadHeader(); - var payload = new byte[obj.Header.PayloadLength]; - var offset = 0; - var chunk = await stream.ReadChunk(); - - while (chunk is not null) - { - chunk.CopyTo(payload, offset); - offset += chunk.Length; - chunk = await stream.ReadChunk(); - } - - obj.Payload = ByteString.CopyFrom(payload); - - return obj; - } - - private ObjectReader GetObjectInit(GetRequest initRequest) - { - if (initRequest is null) - throw new ArgumentNullException(nameof(initRequest)); - - return new ObjectReader - { - Call = _objectServiceClient.Get(initRequest) - }; + using var stream = new MemoryStream(payload); + return await PutObject(header, stream); } private async Task PutObject(ObjectHeader header, Stream payload) @@ -163,12 +87,12 @@ public partial class Client var hdr = header.ToGrpcMessage(); hdr.OwnerId = OwnerId.ToGrpcMessage(); hdr.Version = Version.ToGrpcMessage(); - + var oid = new ObjectID { Value = hdr.Sha256() }; - + var request = new PutRequest { Body = new PutRequest.Types.Body @@ -188,23 +112,27 @@ public partial class Client ObjectSessionContext.Types.Verb.Put, _key ); - + request.Sign(_key); using var stream = await PutObjectInit(request); var buffer = new byte[Constants.ObjectChunkSize]; - var bufferLength = await payload.ReadAsync(buffer, 0, Constants.ObjectChunkSize); - - while (bufferLength > 0) + + while (true) { + var bufferLength = await payload.ReadAsync(buffer, 0, Constants.ObjectChunkSize); + + if (bufferLength == 0) + break; + request.Body = new PutRequest.Types.Body { Chunk = ByteString.CopyFrom(buffer[..bufferLength]), }; + request.VerifyHeader = null; request.Sign(_key); await stream.Write(request); - bufferLength = await payload.ReadAsync(buffer, 0, Constants.ObjectChunkSize); } var response = await stream.Close(); @@ -213,6 +141,192 @@ public partial class Client return ObjectId.FromHash(response.Body.ObjectId.Value.ToByteArray()); } + public async Task PutSingleObjectAsync(ModelsV2.Object @object) + { + var sessionToken = await CreateSessionAsync(uint.MaxValue); + + var obj = CreateObject(@object); + + var request = new PutSingleRequest + { + Body = new () { Object = obj } + }; + + request.AddMetaHeader(); + request.AddObjectSessionToken( + sessionToken, + obj.Header.ContainerId, + obj.ObjectId, + ObjectSessionContext.Types.Verb.Put, + _key + ); + + request.Sign(_key); + + var response = await _objectServiceClient!.PutSingleAsync(request); + Verifier.CheckResponse(response); + + return ObjectId.FromHash(obj.ObjectId.Value.ToByteArray()); + } + + public Object.Object CreateObject(ModelsV2.Object @object) + { + var grpcHeader = @object.Header.ToGrpcMessage(); + + grpcHeader.OwnerId = OwnerId.ToGrpcMessage(); + grpcHeader.Version = Version.ToGrpcMessage(); + + if (@object.Payload != null) + { + grpcHeader.PayloadLength = (ulong)@object.Payload.Length; + grpcHeader.PayloadHash = Sha256Checksum(@object.Payload); + } + + var split = @object.Header.Split; + if (split != null) + { + grpcHeader.Split = new Header.Types.Split + { + SplitId = split.SplitId != null ? ByteString.CopyFrom(split.SplitId.ToBinary()) : null + }; + + if (split.Children != null && split.Children.Any()) + grpcHeader.Split.Children.AddRange(split.Children.Select(id => id.ToGrpcMessage())); + + if (split.ParentHeader is not null) + { + var grpcParentHeader = CreateHeader(split.ParentHeader, []); + + grpcHeader.Split.Parent = new ObjectID { Value = grpcParentHeader.Sha256() }; + grpcHeader.Split.ParentHeader = grpcParentHeader; + grpcHeader.Split.ParentSignature = new Refs.Signature + { + Key = ByteString.CopyFrom(_key.PublicKey()), + Sign = ByteString.CopyFrom(_key.SignData(grpcHeader.Split.Parent.ToByteArray())), + }; + + split.Parent = grpcHeader.Split.Parent.ToModel(); + } + } + + var obj = new Object.Object + { + Header = grpcHeader, + ObjectId = new ObjectID { Value = grpcHeader.Sha256() }, + Payload = ByteString.CopyFrom(@object.Payload) + }; + + obj.Signature = new Refs.Signature + { + Key = ByteString.CopyFrom(_key.PublicKey()), + Sign = ByteString.CopyFrom(_key.SignData(obj.ObjectId.ToByteArray())), + }; + + return obj; + } + + public Header CreateHeader(ObjectHeader header, byte[]? payload) + { + var grpcHeader = header.ToGrpcMessage(); + + grpcHeader.OwnerId = OwnerId.ToGrpcMessage(); + grpcHeader.Version = Version.ToGrpcMessage(); + + if (header.PayloadCheckSum != null) + { + grpcHeader.PayloadHash = new Checksum + { + Type = ChecksumType.Sha256, + Sum = ByteString.CopyFrom(header.PayloadCheckSum) + }; + } + else + { + if (payload != null) + grpcHeader.PayloadHash = Sha256Checksum(payload); + } + + return grpcHeader; + } + + public async Task DeleteObjectAsync(ContainerId cid, ObjectId oid) + { + var request = new DeleteRequest + { + Body = new DeleteRequest.Types.Body + { + Address = new Address + { + ContainerId = cid.ToGrpcMessage(), + ObjectId = oid.ToGrpcMessage() + } + } + }; + + request.AddMetaHeader(); + request.Sign(_key); + var response = await _objectServiceClient!.DeleteAsync(request); + Verifier.CheckResponse(response); + } + + public async IAsyncEnumerable SearchObjectsAsync(ContainerId cid, params ObjectFilter[] filters) + { + var request = new SearchRequest + { + Body = new SearchRequest.Types.Body + { + ContainerId = cid.ToGrpcMessage(), + Filters = { }, + Version = 1 + } + }; + + foreach (var filter in filters) + { + request.Body.Filters.Add(filter.ToGrpcMessage()); + } + + request.AddMetaHeader(); + request.Sign(_key); + var objectsIds = SearchObjects(request); + + await foreach (var oid in objectsIds) + { + yield return ObjectId.FromHash(oid.Value.ToByteArray()); + } + } + + private async Task GetObject(GetRequest request) + { + using var stream = GetObjectInit(request); + var obj = await stream.ReadHeader(); + var payload = new byte[obj.Header.PayloadLength]; + var offset = 0; + var chunk = await stream.ReadChunk(); + + while (chunk is not null) + { + chunk.CopyTo(payload, offset); + offset += chunk.Length; + chunk = await stream.ReadChunk(); + } + + obj.Payload = ByteString.CopyFrom(payload); + + return obj; + } + + private ObjectReader GetObjectInit(GetRequest initRequest) + { + if (initRequest is null) + throw new ArgumentNullException(nameof(initRequest)); + + return new ObjectReader + { + Call = _objectServiceClient!.Get(initRequest) + }; + } + private async Task PutObjectInit(PutRequest initRequest) { if (initRequest is null) @@ -220,9 +334,9 @@ public partial class Client throw new ArgumentNullException(nameof(initRequest)); } - var call = _objectServiceClient.Put(); + var call = _objectServiceClient!.Put(); await call.RequestStream.WriteAsync(initRequest); - + return new ObjectStreamer(call); } @@ -236,7 +350,7 @@ public partial class Client { yield return oid; } - + ids = await stream.Read(); } } @@ -248,6 +362,17 @@ public partial class Client throw new ArgumentNullException(nameof(initRequest)); } - return new SearchReader(_objectServiceClient.Search(initRequest)); + return new SearchReader(_objectServiceClient!.Search(initRequest)); + } + + public Checksum Sha256Checksum(byte[] data) + { + return new Checksum + { + Type = ChecksumType.Sha256, + Sum = ByteString.CopyFrom(data.Sha256()) + }; } } + + diff --git a/src/FrostFS.SDK.Cryptography/Base58.cs b/src/FrostFS.SDK.Cryptography/Base58.cs index 36711c0..d9d91cf 100644 --- a/src/FrostFS.SDK.Cryptography/Base58.cs +++ b/src/FrostFS.SDK.Cryptography/Base58.cs @@ -3,8 +3,6 @@ using System.Linq; using System.Numerics; using System.Text; -using Org.BouncyCastle.Security.Certificates; - namespace FrostFS.SDK.Cryptography; public static class Base58 diff --git a/src/FrostFS.SDK.Cryptography/Helper.cs b/src/FrostFS.SDK.Cryptography/Helper.cs index 93058d9..266a193 100644 --- a/src/FrostFS.SDK.Cryptography/Helper.cs +++ b/src/FrostFS.SDK.Cryptography/Helper.cs @@ -44,7 +44,6 @@ public static class Helper { return ByteString.CopyFrom(data.ToByteArray().Sha256()); } - public static ulong Murmur64(this byte[] value, uint seed) { diff --git a/src/FrostFS.SDK.ModelsV2/ContainerId.cs b/src/FrostFS.SDK.ModelsV2/ContainerId.cs index 8bfbc54..349eb1c 100644 --- a/src/FrostFS.SDK.ModelsV2/ContainerId.cs +++ b/src/FrostFS.SDK.ModelsV2/ContainerId.cs @@ -1,30 +1,8 @@ -using System; +namespace FrostFS.SDK.ModelsV2; -using FrostFS.SDK.Cryptography; - -namespace FrostFS.SDK.ModelsV2; - -public class ContainerId +public class ContainerId(string id) { - public string Value { get; set; } - - public ContainerId(string id) - { - Value = id; - } - - public static ContainerId FromHash(byte[] hash) - { - if (hash.Length != Constants.Sha256HashLength) - throw new FormatException("ContainerID must be a sha256 hash."); - - return new ContainerId(Base58.Encode(hash)); - } - - public byte[] ToHash() - { - return Base58.Decode(Value); - } + public string Value { get; set; } = id; public override string ToString() { diff --git a/src/FrostFS.SDK.ModelsV2/Object/Object.cs b/src/FrostFS.SDK.ModelsV2/Object/Object.cs new file mode 100644 index 0000000..5810699 --- /dev/null +++ b/src/FrostFS.SDK.ModelsV2/Object/Object.cs @@ -0,0 +1,65 @@ +using System.Security.Cryptography; +using FrostFS.SDK.ModelsV2.Enums; + +namespace FrostFS.SDK.ModelsV2; + +public class Object +{ + public Object() + { + } + + public Object(ContainerId container, byte[] payload, ObjectType objectType = ObjectType.Regular) + { + Payload = payload; + Header = new ObjectHeader(containerId: container, type: objectType, attributes: []); + } + + public ObjectHeader Header { get; set; } + public ObjectId ObjectId { get; set; } + public byte[] Payload { get; set; } + public Signature Signature { get; set; } + + public void SetParent(LargeObject largeObject) + { + Header.Split!.ParentHeader = largeObject.Header; + } + + public ObjectId? GetParentId() + { + return Header.Split?.Parent; + } +} + +public class LargeObject(ContainerId container) : Object(container, []) +{ + private SHA256 payloadHash = SHA256.Create(); + + public void AppendBlock(byte[] bytes, int count) + { + Header.PayloadLength += (ulong)count; + this.payloadHash.TransformBlock(bytes, 0, count, bytes, 0); + } + + public void CalculateHash() + { + this.payloadHash.TransformFinalBlock([], 0, 0); + Header.PayloadCheckSum = this.payloadHash.Hash; + } + + public ulong PayloadLength + { + get { return Header.PayloadLength; } + } +} + +public class LinkObject : Object +{ + public LinkObject(ContainerId containerId, SplitId splitId, LargeObject largeObject) : base (containerId, []) + { + Header.Split = new Split(splitId) + { + ParentHeader = largeObject.Header + }; + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Object/ObjectAttribute.cs b/src/FrostFS.SDK.ModelsV2/Object/ObjectAttribute.cs new file mode 100644 index 0000000..b114f2b --- /dev/null +++ b/src/FrostFS.SDK.ModelsV2/Object/ObjectAttribute.cs @@ -0,0 +1,13 @@ +namespace FrostFS.SDK.ModelsV2; + +public class ObjectAttribute +{ + public string Key { get; set; } + public string Value { get; set; } + + public ObjectAttribute(string key, string value) + { + Key = key; + Value = value; + } +} diff --git a/src/FrostFS.SDK.ModelsV2/Object.cs b/src/FrostFS.SDK.ModelsV2/Object/ObjectFilter.cs similarity index 56% rename from src/FrostFS.SDK.ModelsV2/Object.cs rename to src/FrostFS.SDK.ModelsV2/Object/ObjectFilter.cs index 7db6289..5fdb54f 100644 --- a/src/FrostFS.SDK.ModelsV2/Object.cs +++ b/src/FrostFS.SDK.ModelsV2/Object/ObjectFilter.cs @@ -2,18 +2,6 @@ using FrostFS.SDK.ModelsV2.Enums; namespace FrostFS.SDK.ModelsV2; -public class ObjectAttribute -{ - public string Key { get; set; } - public string Value { get; set; } - - public ObjectAttribute(string key, string value) - { - Key = key; - Value = value; - } -} - public class ObjectFilter { private const string HeaderPrefix = "$Object:"; @@ -48,30 +36,3 @@ public class ObjectFilter return new ObjectFilter(matchType, HeaderPrefix + "version", version.ToString()); } } - -public class ObjectHeader -{ - public ObjectAttribute[] Attributes { get; set; } - public ContainerId ContainerId { get; set; } - public long Size { get; set; } - public ObjectType ObjectType { get; set; } - public Version Version { get; set; } - - public ObjectHeader( - ContainerId containerId, - ObjectType type = ObjectType.Regular, - params ObjectAttribute[] attributes - ) - { - Attributes = attributes; - ContainerId = containerId; - ObjectType = type; - } -} - -public class Object -{ - public ObjectHeader Header { get; set; } - public ObjectId ObjectId { get; set; } - public byte[] Payload { get; set; } -} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Object/ObjectHeader.cs b/src/FrostFS.SDK.ModelsV2/Object/ObjectHeader.cs new file mode 100644 index 0000000..8f51160 --- /dev/null +++ b/src/FrostFS.SDK.ModelsV2/Object/ObjectHeader.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; + +using FrostFS.SDK.ModelsV2.Enums; + +namespace FrostFS.SDK.ModelsV2; + +public class ObjectHeader +{ + public OwnerId OwnerId { get; set; } + + public List Attributes { get; set; } + + public ContainerId ContainerId { get; set; } + + public ulong PayloadLength { get; set; } + + public byte[]? PayloadCheckSum { get; set; } + + public ObjectType ObjectType { get; set; } + + public Version Version { get; set; } + + public Split? Split { get; set; } + + public bool ClientCut { get; set; } + + public ObjectHeader( + ContainerId containerId, + ObjectType type = ObjectType.Regular, + params ObjectAttribute[] attributes + ) + { + Attributes = [.. attributes]; + ContainerId = containerId; + ObjectType = type; + } +} diff --git a/src/FrostFS.SDK.ModelsV2/Splitter.cs b/src/FrostFS.SDK.ModelsV2/Splitter.cs new file mode 100644 index 0000000..f48f903 --- /dev/null +++ b/src/FrostFS.SDK.ModelsV2/Splitter.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; + +namespace FrostFS.SDK.ModelsV2; + +public class Split +{ + public Split() : this(new SplitId()) + { + } + + public Split(SplitId splitId) + { + SplitId = splitId; + } + + public SplitId SplitId { get; private set; } + + public ObjectId? Parent { get; set; } + + public ObjectId? Previous { get; set; } + + public Signature? ParentSignature { get; set; } + + public ObjectHeader? ParentHeader { get; set; } + + public List Children { get; } = []; +} + + public enum SignatureScheme { + EcdsaSha512, + EcdsaRfc6979Sha256, + EcdsaRfc6979Sha256WalletConnect + } + +public class Signature +{ + public byte[] Key { get; set; } + public byte[] Sign { get; set; } + public SignatureScheme Scheme { get; set; } +} + +public class SplitId +{ + private Guid id; + + public SplitId() + { + this.id = Guid.NewGuid(); + } + public SplitId(Guid guid) + { + this.id = guid; + } + + private SplitId(byte[] binary) + { + this.id = new Guid(binary); + } + + private SplitId(string str) + { + this.id = new Guid(str); + } + + public static SplitId CrateFromBinary(byte[] binaryData) + { + return new SplitId(binaryData); + } + + public static SplitId CrateFromString(string stringData) + { + return new SplitId(stringData); + } + + public override string ToString() + { + return this.id.ToString(); + } + + public byte[]? ToBinary() + { + if (this.id == Guid.Empty) + return null; + + return this.id.ToByteArray(); + } +} diff --git a/src/FrostFS.SDK.ProtosV2/object/Extension.Message.cs b/src/FrostFS.SDK.ProtosV2/object/Extension.Message.cs index 243ab00..6334c71 100644 --- a/src/FrostFS.SDK.ProtosV2/object/Extension.Message.cs +++ b/src/FrostFS.SDK.ProtosV2/object/Extension.Message.cs @@ -115,6 +115,62 @@ namespace FrostFS.Object } } + public partial class PutSingleRequest : IRequest + { + IMetaHeader IVerificableMessage.GetMetaHeader() + { + return MetaHeader; + } + + IVerificationHeader IVerificableMessage.GetVerificationHeader() + { + return VerifyHeader; + } + + void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + { + MetaHeader = (RequestMetaHeader)metaHeader; + } + + void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + { + VerifyHeader = (RequestVerificationHeader)verificationHeader; + } + + public IMessage GetBody() + { + return Body; + } + } + + public partial class PutSingleResponse : IResponse + { + IMetaHeader IVerificableMessage.GetMetaHeader() + { + return MetaHeader; + } + + IVerificationHeader IVerificableMessage.GetVerificationHeader() + { + return VerifyHeader; + } + + void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + { + MetaHeader = (ResponseMetaHeader)metaHeader; + } + + void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + { + VerifyHeader = (ResponseVerificationHeader)verificationHeader; + } + + public IMessage GetBody() + { + return Body; + } + } + public partial class DeleteRequest : IRequest { IMetaHeader IVerificableMessage.GetMetaHeader() diff --git a/src/FrostFS.SDK.ProtosV2/session/Extension.XHeader.cs b/src/FrostFS.SDK.ProtosV2/session/Extension.XHeader.cs index f8f2695..c0c5b25 100644 --- a/src/FrostFS.SDK.ProtosV2/session/Extension.XHeader.cs +++ b/src/FrostFS.SDK.ProtosV2/session/Extension.XHeader.cs @@ -2,7 +2,7 @@ public partial class XHeader { - public const string ReservedXHeaderPrefix = "__NEOFS__"; + public const string ReservedXHeaderPrefix = "__SYSTEM__"; public const string XHeaderNetmapEpoch = ReservedXHeaderPrefix + "NETMAP_EPOCH"; public const string XHeaderNetmapLookupDepth = ReservedXHeaderPrefix + "NETMAP_LOOKUP_DEPTH"; } -- 2.45.2 From edf10b970be7fc65257d987537386bc962ee397f Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Mon, 10 Jun 2024 11:11:09 +0300 Subject: [PATCH 2/3] [#4] sample for custom Client Cut Signed-off-by: Pavel Gross --- README.md | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 75 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 832b42f..542a544 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ using FrostFS.SDK.ModelsV2; using FrostFS.SDK.ModelsV2.Enums; using FrostFS.SDK.ModelsV2.Netmap; -var fsClient = new Client(, ); +var fsClient = Client.GetInstance(, ); // List containers var containersIds = await fsClient.ListContainersAsync(); @@ -55,7 +55,7 @@ using FrostFS.SDK.ModelsV2; using FrostFS.SDK.ModelsV2.Enums; using FrostFS.SDK.ModelsV2.Netmap; -var fsClient = new Client(, ); +var fsClient = Client.GetInstance(, ); // Search regular objects var objectsIds = await fsClient.SearchObjectAsync( @@ -77,4 +77,77 @@ var objHeader = await fsClient.GetObjectHeadAsync(cId, oId); // Get object var obj = await fsClient.GetObjectAsync(cId, oId); +``` + +### Custom client cut +```csharp + +using FrostFS.SDK.ClientV2; +using FrostFS.SDK.ModelsV2; +using FrostFS.SDK.ModelsV2.Enums; +using FrostFS.SDK.ModelsV2.Netmap; +using FrostFS.SDK.ClientV2.Extensions; +using FrostFS.SDK.ClientV2.Interfaces; + +var fsClient = Client.GetInstance(, ); + +ContainerId containerId = ; +string fileName = ; + +await PutObjectClientCut(fsClient, containerId, fileName); + +static async Task PutObjectClientCut(IFrostFSClient fsClient, ContainerId containerId, string fileName) +{ + List sentObjectIds = []; + FrostFS.SDK.ModelsV2.Object? currentObject; + + var partSize = 1024 * 1024; + var buffer = new byte[partSize]; + + var largeObject = new LargeObject(containerId); + + var split = new Split(); + + var fileInfo = new FileInfo(fileName); + var fullLength = (ulong)fileInfo.Length; + var fileNameAttribute = new ObjectAttribute("fileName", fileInfo.Name); + + using var stream = File.OpenRead(fileName); + while (true) + { + var bytesCount = await stream.ReadAsync(buffer.AsMemory(0, partSize)); + + split.Previous = sentObjectIds.LastOrDefault(); + + largeObject.AppendBlock(buffer, bytesCount); + + currentObject = new FrostFS.SDK.ModelsV2.Object(containerId, buffer) + .AddAttribute(fileNameAttribute) + .SetSplit(split); + + if (largeObject.PayloadLength == fullLength) + break; + + var objectId = await fsClient.PutSingleObjectAsync(currentObject); + sentObjectIds.Add(objectId); + } + + if (sentObjectIds.Any()) + { + largeObject.CalculateHash(); + + var linkObject = new LinkObject(containerId, split.SplitId, largeObject) + .AddChildren(sentObjectIds); + + _ = await fsClient.PutSingleObjectAsync(linkObject); + + currentObject.SetParent(largeObject); + _ = await fsClient.PutSingleObjectAsync(currentObject); + + return currentObject.GetParentId(); + } + + return await fsClient.PutSingleObjectAsync(currentObject); +} + ``` \ No newline at end of file -- 2.45.2 From 85506a997eeb5ac278d29a42b0100680eb838197 Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Sat, 22 Jun 2024 23:03:30 +0300 Subject: [PATCH 3/3] [#10] Netmap metrics and options --- .gitignore | 1 + FrostFS.SDK.sln | 11 +- README.md | 147 ++---- src/FrostFS.SDK.ClientV2/Client.cs | 270 +++++++--- .../FrostFS.SDK.ClientV2.csproj | 7 +- .../Interfaces/IFrostFSClient.cs | 31 +- .../Mappers/{GRPC => }/Container.cs | 14 +- .../Mappers/{GRPC => }/ContainerId.cs | 0 .../Mappers/GRPC/Netmap/NodeInfo.cs | 24 - .../Mappers/{GRPC => }/MetaHeader.cs | 0 .../Mappers/Netmap/Netmap.cs | 15 + .../Mappers/Netmap/NodeInfo.cs | 35 ++ .../{GRPC => }/Netmap/PlacementPolicy.cs | 0 .../Mappers/{GRPC => }/Netmap/Replica.cs | 0 .../Mappers/Object/Object.cs | 14 + .../Mappers/Object/ObjectAttributeMapper.cs | 21 + .../Mappers/Object/ObjectFilterMapper.cs | 30 ++ .../ObjectHeaderMapper.cs} | 76 --- .../Mappers/{GRPC => Object}/ObjectId.cs | 0 .../Mappers/{GRPC => }/OwnerId.cs | 0 .../Mappers/Session/Session.cs | 25 + .../Mappers/SignatureMapper.cs | 26 + .../Mappers/{GRPC => }/Status.cs | 0 .../Mappers/{GRPC => }/Version.cs | 0 .../Services/Container.cs | 85 ---- .../Services/ContainerServiceProvider.cs | 117 +++++ src/FrostFS.SDK.ClientV2/Services/Netmap.cs | 38 -- .../Services/NetmapServiceProvider.cs | 134 +++++ .../Services/ObjectReader.cs | 10 +- .../{Object.cs => ObjectServiceProvider.cs} | 477 ++++++++++-------- .../Services/SearchReader.cs | 10 +- .../{Session.cs => SessionServiceProvider.cs} | 28 +- .../Tools/ClientEnvironment.cs | 116 +++++ .../Tools/ContextAccessor.cs | 8 + .../Tools/NetworkSettings.cs | 21 + .../{Extensions => Tools}/Object.cs | 14 +- src/FrostFS.SDK.ClientV2/{ => Tools}/Range.cs | 32 +- .../{ => Tools}/RequestConstructor.cs | 11 +- .../{ => Tools}/RequestSigner.cs | 9 +- .../{ => Tools}/Verifier.cs | 16 +- src/FrostFS.SDK.Cryptography/AssemblyInfo.cs | 2 +- src/FrostFS.SDK.Cryptography/Base58.cs | 2 + .../{Helper.cs => Extentions.cs} | 2 +- .../FrostFS.SDK.Cryptography.csproj | 4 + src/FrostFS.SDK.Cryptography/Key.cs | 6 +- src/FrostFS.SDK.Cryptography/Range.cs | 32 +- src/FrostFS.SDK.ModelsV2/ClientSettings.cs | 28 + src/FrostFS.SDK.ModelsV2/Container.cs | 17 +- src/FrostFS.SDK.ModelsV2/Context.cs | 13 + src/FrostFS.SDK.ModelsV2/Enums/BasicAcl.cs | 5 +- .../FrostFS.SDK.ModelsV2.csproj | 4 + src/FrostFS.SDK.ModelsV2/GrpcCallInfo.cs | 8 + src/FrostFS.SDK.ModelsV2/MetaHeader.cs | 15 +- src/FrostFS.SDK.ModelsV2/Netmap/NetmapInfo.cs | 10 + src/FrostFS.SDK.ModelsV2/Netmap/NodeInfo.cs | 23 +- .../Netmap/PlacementPolicy.cs | 27 +- src/FrostFS.SDK.ModelsV2/Object/Object.cs | 36 +- .../Object/ObjectHeader.cs | 2 - .../{ => Object}/ObjectId.cs | 0 src/FrostFS.SDK.ModelsV2/OwnerId.cs | 9 +- .../PutObjectParameters.cs | 12 + src/FrostFS.SDK.ModelsV2/SessionToken.cs | 8 + src/FrostFS.SDK.ModelsV2/Signature.cs | 8 + src/FrostFS.SDK.ModelsV2/SignatureScheme.cs | 7 + src/FrostFS.SDK.ModelsV2/SplitId.cs | 50 ++ src/FrostFS.SDK.ModelsV2/Splitter.cs | 72 +-- src/FrostFS.SDK.ModelsV2/Status.cs | 17 +- .../FrostFS.SDK.ProtosV2.csproj | 4 + .../Interfaces/IRequest.cs | 2 +- .../Interfaces/IResponse.cs | 2 +- .../Interfaces/IVerifiableMessage.cs | 2 +- .../container/Extension.Message.cs | 112 ++-- .../netmap/Extension.Message.cs | 89 +++- .../object/Extension.Message.cs | 128 ++--- .../session/Extension.Message.cs | 16 +- src/FrostFS.SDK.Tests/ClientTest.cs | 85 ++++ src/FrostFS.SDK.Tests/ClientTestLive.cs | 208 ++++++++ .../ContainerServiceBase.cs | 28 + .../DeleteContainerMock.cs | 69 +++ .../ContainerServiceMocks/GetContainerMock.cs | 163 ++++++ .../ContainerServiceMocks/PutContainerMock.cs | 88 ++++ src/FrostFS.SDK.Tests/NetmapMock.cs | 14 + src/FrostFS.SDK.Tests/ObjectMock.cs | 14 + src/FrostFS.SDK.Tests/SessionMock.cs | 14 + 84 files changed, 2314 insertions(+), 1016 deletions(-) rename src/FrostFS.SDK.ClientV2/Mappers/{GRPC => }/Container.cs (75%) rename src/FrostFS.SDK.ClientV2/Mappers/{GRPC => }/ContainerId.cs (100%) delete mode 100644 src/FrostFS.SDK.ClientV2/Mappers/GRPC/Netmap/NodeInfo.cs rename src/FrostFS.SDK.ClientV2/Mappers/{GRPC => }/MetaHeader.cs (100%) create mode 100644 src/FrostFS.SDK.ClientV2/Mappers/Netmap/Netmap.cs create mode 100644 src/FrostFS.SDK.ClientV2/Mappers/Netmap/NodeInfo.cs rename src/FrostFS.SDK.ClientV2/Mappers/{GRPC => }/Netmap/PlacementPolicy.cs (100%) rename src/FrostFS.SDK.ClientV2/Mappers/{GRPC => }/Netmap/Replica.cs (100%) create mode 100644 src/FrostFS.SDK.ClientV2/Mappers/Object/Object.cs create mode 100644 src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectAttributeMapper.cs create mode 100644 src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectFilterMapper.cs rename src/FrostFS.SDK.ClientV2/Mappers/{GRPC/Object.cs => Object/ObjectHeaderMapper.cs} (55%) rename src/FrostFS.SDK.ClientV2/Mappers/{GRPC => Object}/ObjectId.cs (100%) rename src/FrostFS.SDK.ClientV2/Mappers/{GRPC => }/OwnerId.cs (100%) create mode 100644 src/FrostFS.SDK.ClientV2/Mappers/Session/Session.cs create mode 100644 src/FrostFS.SDK.ClientV2/Mappers/SignatureMapper.cs rename src/FrostFS.SDK.ClientV2/Mappers/{GRPC => }/Status.cs (100%) rename src/FrostFS.SDK.ClientV2/Mappers/{GRPC => }/Version.cs (100%) delete mode 100644 src/FrostFS.SDK.ClientV2/Services/Container.cs create mode 100644 src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs delete mode 100644 src/FrostFS.SDK.ClientV2/Services/Netmap.cs create mode 100644 src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs rename src/FrostFS.SDK.ClientV2/Services/{Object.cs => ObjectServiceProvider.cs} (56%) rename src/FrostFS.SDK.ClientV2/Services/{Session.cs => SessionServiceProvider.cs} (50%) create mode 100644 src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs create mode 100644 src/FrostFS.SDK.ClientV2/Tools/ContextAccessor.cs create mode 100644 src/FrostFS.SDK.ClientV2/Tools/NetworkSettings.cs rename src/FrostFS.SDK.ClientV2/{Extensions => Tools}/Object.cs (76%) rename src/FrostFS.SDK.ClientV2/{ => Tools}/Range.cs (91%) rename src/FrostFS.SDK.ClientV2/{ => Tools}/RequestConstructor.cs (84%) rename src/FrostFS.SDK.ClientV2/{ => Tools}/RequestSigner.cs (95%) rename src/FrostFS.SDK.ClientV2/{ => Tools}/Verifier.cs (86%) rename src/FrostFS.SDK.Cryptography/{Helper.cs => Extentions.cs} (97%) create mode 100644 src/FrostFS.SDK.ModelsV2/ClientSettings.cs create mode 100644 src/FrostFS.SDK.ModelsV2/Context.cs create mode 100644 src/FrostFS.SDK.ModelsV2/GrpcCallInfo.cs create mode 100644 src/FrostFS.SDK.ModelsV2/Netmap/NetmapInfo.cs rename src/FrostFS.SDK.ModelsV2/{ => Object}/ObjectId.cs (100%) create mode 100644 src/FrostFS.SDK.ModelsV2/PutObjectParameters.cs create mode 100644 src/FrostFS.SDK.ModelsV2/SessionToken.cs create mode 100644 src/FrostFS.SDK.ModelsV2/Signature.cs create mode 100644 src/FrostFS.SDK.ModelsV2/SignatureScheme.cs create mode 100644 src/FrostFS.SDK.ModelsV2/SplitId.cs create mode 100644 src/FrostFS.SDK.Tests/ClientTest.cs create mode 100644 src/FrostFS.SDK.Tests/ClientTestLive.cs create mode 100644 src/FrostFS.SDK.Tests/ContainerServiceMocks/ContainerServiceBase.cs create mode 100644 src/FrostFS.SDK.Tests/ContainerServiceMocks/DeleteContainerMock.cs create mode 100644 src/FrostFS.SDK.Tests/ContainerServiceMocks/GetContainerMock.cs create mode 100644 src/FrostFS.SDK.Tests/ContainerServiceMocks/PutContainerMock.cs create mode 100644 src/FrostFS.SDK.Tests/NetmapMock.cs create mode 100644 src/FrostFS.SDK.Tests/ObjectMock.cs create mode 100644 src/FrostFS.SDK.Tests/SessionMock.cs diff --git a/.gitignore b/.gitignore index cf6e05a..ae0b81f 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ vendor/ # IDE .idea .vscode +.vs # coverage coverage.txt diff --git a/FrostFS.SDK.sln b/FrostFS.SDK.sln index fcfe732..c192375 100644 --- a/FrostFS.SDK.sln +++ b/FrostFS.SDK.sln @@ -1,7 +1,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # VisualStudioVersion = 17.5.002.0 -MinimumVisualStudioVersion = + Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrostFS.SDK.ClientV2", "src\FrostFS.SDK.ClientV2\FrostFS.SDK.ClientV2.csproj", "{50D8F61F-C302-4AC9-8D8A-AB0B8C0988C3}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrostFS.SDK.Cryptography", "src\FrostFS.SDK.Cryptography\FrostFS.SDK.Cryptography.csproj", "{3D804F4A-B0B2-47A5-B006-BE447BE64B50}" @@ -10,6 +10,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrostFS.SDK.ModelsV2", "src EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrostFS.SDK.ProtosV2", "src\FrostFS.SDK.ProtosV2\FrostFS.SDK.ProtosV2.csproj", "{5012EF96-9C9E-4E77-BC78-B4111EE54107}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FrostFS.SDK.Tests", "src\FrostFS.SDK.Tests\FrostFS.SDK.Tests.csproj", "{8FDA7E0D-9C75-4874-988E-6592CD28F76C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -32,8 +34,9 @@ Global {5012EF96-9C9E-4E77-BC78-B4111EE54107}.Debug|Any CPU.Build.0 = Debug|Any CPU {5012EF96-9C9E-4E77-BC78-B4111EE54107}.Release|Any CPU.ActiveCfg = Release|Any CPU {5012EF96-9C9E-4E77-BC78-B4111EE54107}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE + {8FDA7E0D-9C75-4874-988E-6592CD28F76C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8FDA7E0D-9C75-4874-988E-6592CD28F76C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8FDA7E0D-9C75-4874-988E-6592CD28F76C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8FDA7E0D-9C75-4874-988E-6592CD28F76C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/README.md b/README.md index 89ab89a..43f4040 100644 --- a/README.md +++ b/README.md @@ -25,133 +25,50 @@ using FrostFS.SDK.ClientV2; using FrostFS.SDK.ModelsV2; using FrostFS.SDK.ModelsV2.Enums; using FrostFS.SDK.ModelsV2.Netmap; +using Microsoft.Extensions.Options; -var fsClient = Client.GetInstance(, ); -// List containers -var containersIds = await fsClient.ListContainersAsync(); +var options = Options.Create(new ClientSettings + { + Key = , + Host = + }); + +var fsClient = Client.GetInstance(options); + +// Mertics +fsClient.GrpcInvoked += (sender, info) => +{ + Console.WriteLine($"Invoked: {info.MethodName}. Elapsed time: {info.ElapsedTimeMicroSec}"); +}; -// Create container var placementPolicy = new PlacementPolicy(true, new Replica(1)); -var containerId = await fsClient.CreateContainerAsync( - new Container( - BasicAcl.PublicRW, - placementPolicy - ) -); -// Get container -var container = await fsClient.GetContainerAsync(cId); - -// Delete container -await fsClient.DeleteContainerAsync(containerId); -``` - -### Object - -```csharp -using FrostFS.SDK.ClientV2; -using FrostFS.SDK.ModelsV2; -using FrostFS.SDK.ModelsV2.Enums; -using FrostFS.SDK.ModelsV2.Netmap; - -var fsClient = Client.GetInstance(, ); - -// Search regular objects -var objectsIds = await fsClient.SearchObjectAsync( - cId, - ObjectFilter.RootFilter() -); +var containerId = await fsClient.CreateContainerAsync(new Container(BasicAcl.PublicRW, placementPolicy)); // Put object -var f = File.OpenRead("cat.jpg"); -var cat = new ObjectHeader( - containerId: cId, +var fileStream = File.OpenRead("cat.jpg"); +var header = new ObjectHeader( + containerId: containerId, type: ObjectType.Regular, new ObjectAttribute("Filename", "cat.jpg") ); -var oId = await fsClient.PutObjectAsync(cat, f); -// Get object header -var objHeader = await fsClient.GetObjectHeadAsync(cId, oId); - -// Get object -var obj = await fsClient.GetObjectAsync(cId, oId); -``` - -### Custom client cut -```csharp - -using FrostFS.SDK.ClientV2; -using FrostFS.SDK.ModelsV2; -using FrostFS.SDK.ModelsV2.Enums; -using FrostFS.SDK.ModelsV2.Netmap; -using FrostFS.SDK.ClientV2.Extensions; -using FrostFS.SDK.ClientV2.Interfaces; - -var fsClient = Client.GetInstance(, ); - -ContainerId containerId = ; -string fileName = ; - -await PutObjectClientCut(fsClient, containerId, fileName); - -static async Task PutObjectClientCut(IFrostFSClient fsClient, ContainerId containerId, string fileName) +var param = new PutObjectParameters { - List sentObjectIds = []; - FrostFS.SDK.ModelsV2.Object? currentObject; + Header = header, + Payload = fileStream +}; - var partSize = 1024 * 1024; - var buffer = new byte[partSize]; +var oId = await fsClient.PutObjectAsync(param); - var largeObject = new LargeObject(containerId); - - var split = new Split(); - - var fileInfo = new FileInfo(fileName); - var fullLength = (ulong)fileInfo.Length; - var fileNameAttribute = new ObjectAttribute("fileName", fileInfo.Name); +// Search regular objects +await foreach (var objId in fsClient.SearchObjectsAsync(containerId, [ObjectFilter.RootFilter()])) +{ + // Get object header + var info = await fsClient.GetObjectHeadAsync(containerId, objId); - using var stream = File.OpenRead(fileName); - while (true) - { - var bytesCount = await stream.ReadAsync(buffer.AsMemory(0, partSize)); - - split.Previous = sentObjectIds.LastOrDefault(); - - largeObject.AppendBlock(buffer, bytesCount); - - currentObject = new FrostFS.SDK.ModelsV2.Object(containerId, bytesCount < partSize ? buffer.Take(bytesCount).ToArray() : buffer) - .AddAttribute(fileNameAttribute) - .SetSplit(split); - - if (largeObject.PayloadLength == fullLength) - break; - - var objectId = await fsClient.PutSingleObjectAsync(currentObject); - sentObjectIds.Add(objectId); - } - - if (sentObjectIds.Any()) - { - largeObject.CalculateHash() - .AddAttribute(fileNameAttribute); - - currentObject.SetParent(largeObject); - - var objectId = await fsClient.PutSingleObjectAsync(currentObject); - sentObjectIds.Add(objectId); - - var linkObject = new LinkObject(containerId, split.SplitId, largeObject) - .AddChildren(sentObjectIds) - .AddAttribute(fileNameAttribute); - - _ = await fsClient.PutSingleObjectAsync(linkObject); - - return currentObject.GetParentId(); - } - - return await fsClient.PutSingleObjectAsync(currentObject); -} - -``` \ No newline at end of file + // Get object + var obj = await fsClient.GetObjectAsync(containerId, oId); +} +``` diff --git a/src/FrostFS.SDK.ClientV2/Client.cs b/src/FrostFS.SDK.ClientV2/Client.cs index 96fb227..093951f 100644 --- a/src/FrostFS.SDK.ClientV2/Client.cs +++ b/src/FrostFS.SDK.ClientV2/Client.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; -using System.Security.Cryptography; -using System.Text; +using System.Data.Common; using System.Threading.Tasks; using FrostFS.Container; using FrostFS.Netmap; @@ -9,85 +8,222 @@ using FrostFS.Object; using FrostFS.SDK.ClientV2.Interfaces; using FrostFS.SDK.Cryptography; using FrostFS.SDK.ModelsV2; +using FrostFS.SDK.ModelsV2.Netmap; using FrostFS.Session; using Grpc.Core; using Grpc.Net.Client; -using static FrostFS.Netmap.NetworkConfig.Types; +using Microsoft.Extensions.Options; using Version = FrostFS.SDK.ModelsV2.Version; namespace FrostFS.SDK.ClientV2; -public partial class Client: IFrostFSClient +public class Client : IFrostFSClient, IDisposable { - private GrpcChannel? _channel; - private readonly ECDsa _key; - public readonly OwnerId OwnerId; - public readonly Version Version = new(2, 13); + private bool isDisposed; - private readonly Dictionary NetworkSettings = []; + internal ClientEnvironment ClientCtx { get; set; } - private ContainerService.ContainerServiceClient? _containerServiceClient; - private NetmapService.NetmapServiceClient? _netmapServiceClient; - private ObjectService.ObjectServiceClient? _objectServiceClient; - private SessionService.SessionServiceClient? _sessionServiceClient; - - public static IFrostFSClient GetInstance(string key, string host) + public static IFrostFSClient GetInstance(IOptions options) { - return new Client(key, host); + return new Client(options); } - private Client(string key, string host) + /// + /// For test only. Provide custom implementation or mock object to inject required logic instead of internal gRPC client. + /// + /// User's key + /// ContainerService.ContainerServiceClient implementation + /// Netmap.NetmapService.NetmapServiceClient implementation + /// Session.SessionService.SessionServiceClient implementation + /// Object.ObjectService.ObjectServiceClient implementation + /// + public static IFrostFSClient GetTestInstance( + IOptions settings, + NetmapService.NetmapServiceClient netmapService, + SessionService.SessionServiceClient sessionService, + ContainerService.ContainerServiceClient containerService, + ObjectService.ObjectServiceClient objectService) { - // TODO: Развязать клиент и реализацию GRPC - _key = key.LoadWif(); - OwnerId = OwnerId.FromKey(_key); - InitGrpcChannel(host); - InitContainerClient(); - InitNetmapClient(); - InitObjectClient(); - InitSessionClient(); + return new Client(settings, containerService, netmapService, sessionService, objectService); + } + + private Client( + IOptions settings, + ContainerService.ContainerServiceClient containerService, + NetmapService.NetmapServiceClient netmapService, + SessionService.SessionServiceClient sessionService, + ObjectService.ObjectServiceClient objectService) + { + var ecdsaKey = settings.Value.Key.LoadWif(); + OwnerId.FromKey(ecdsaKey); + + ClientCtx = new ClientEnvironment( + key: ecdsaKey, + owner: OwnerId.FromKey(ecdsaKey), + channel: InitGrpcChannel(settings.Value.Host), + version: new Version(2, 13)); + + ClientCtx.ContainerService = new ContainerServiceProvider(containerService, ClientCtx); + ClientCtx.NetmapService = new NetmapServiceProvider(netmapService, ClientCtx); + ClientCtx.SessionService = new SessionServiceProvider(sessionService, ClientCtx); + ClientCtx.ObjectService = new ObjectServiceProvider(objectService, ClientCtx); + } + + private Client(IOptions options) + { + var clientSettings = (options?.Value) ?? throw new ArgumentException("Options must be initialized"); + + clientSettings.Validate(); + + var ecdsaKey = clientSettings.Key.LoadWif(); + var channel = InitGrpcChannel(clientSettings.Host); + + ClientCtx = new ClientEnvironment( + key: ecdsaKey, + owner: OwnerId.FromKey(ecdsaKey), + channel: InitGrpcChannel(clientSettings.Host), + version: new Version(2, 13)); + + ClientCtx.ContainerService = new ContainerServiceProvider(new ContainerService.ContainerServiceClient(channel), ClientCtx); + ClientCtx.NetmapService = new NetmapServiceProvider(new NetmapService.NetmapServiceClient(channel), ClientCtx); + ClientCtx.SessionService = new SessionServiceProvider(new SessionService.SessionServiceClient(channel), ClientCtx); + ClientCtx.ObjectService = new ObjectServiceProvider(new ObjectService.ObjectServiceClient(channel), ClientCtx); + CheckFrostFsVersionSupport(); - - InitNetworkInfoAsync(); } - private async void InitNetworkInfoAsync() + event EventHandler IFrostFSClient.GrpcInvoked { - var info = await GetNetworkInfoAsync(); - - foreach (var param in info.Body.NetworkInfo.NetworkConfig.Parameters) + add { - SetNetworksParam(param); + ClientCtx.GrpcInvokedEvent += value; + } + remove + { + ClientCtx.GrpcInvokedEvent -= value; } } - private void SetNetworksParam(Parameter param) - { - var key = Encoding.UTF8.GetString(param.Key.ToByteArray()); - - var encodedValue = param.Value.ToByteArray(); - - ulong val = 0; - for (var i = encodedValue.Length - 1; i >= 0; i--) - { - val = (val << 8) + encodedValue[i]; - } - - NetworkSettings.Add(key, val); - } - - private async void CheckFrostFsVersionSupport() + public void Dispose() { - var localNodeInfo = await GetLocalNodeInfoAsync(); - if (!localNodeInfo.Version.IsSupported(Version)) + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing && !isDisposed) + { + ClientCtx.Dispose(); + isDisposed = true; + } + } + + public Task GetContainerAsync(ContainerId containerId, Context? ctx = null) + { + ValidateEnvironment(ref ctx); + return ClientCtx.ContainerService!.GetContainerAsync(containerId, ctx!); + } + + public IAsyncEnumerable ListContainersAsync(Context? ctx = default) + { + ValidateEnvironment(ref ctx); + return ClientCtx.ContainerService!.ListContainersAsync(ctx!); + } + + public Task CreateContainerAsync(ModelsV2.Container container, Context? ctx = null) + { + ValidateEnvironment(ref ctx); + return ClientCtx.ContainerService!.CreateContainerAsync(container, ctx!); + } + + public Task DeleteContainerAsync(ContainerId containerId, Context? ctx = default) + { + ValidateEnvironment(ref ctx); + return ClientCtx.ContainerService!.DeleteContainerAsync(containerId, ctx!); + } + + public Task GetNetmapSnapshotAsync(Context? ctx = default) + { + ValidateEnvironment(ref ctx); + return ClientCtx.NetmapService!.GetNetmapSnapshotAsync(ctx!); + } + + public Task GetNodeInfoAsync(Context? ctx = default) + { + ValidateEnvironment(ref ctx); + return ClientCtx.NetmapService!.GetLocalNodeInfoAsync(ctx!); + } + + public Task GetObjectHeadAsync(ContainerId containerId, ObjectId objectId, Context? ctx = default) + { + ValidateEnvironment(ref ctx); + return ClientCtx.ObjectService!.GetObjectHeadAsync(containerId, objectId, ctx!); + } + + public Task GetObjectAsync(ContainerId containerId, ObjectId objectId, Context? ctx = default) + { + ValidateEnvironment(ref ctx); + return ClientCtx.ObjectService!.GetObjectAsync(containerId, objectId, ctx!); + } + + public Task PutObjectAsync(PutObjectParameters putObjectParameters, Context? ctx = default) + { + ValidateEnvironment(ref ctx); + return ClientCtx.ObjectService!.PutObjectAsync(putObjectParameters, ctx!); + } + + public Task PutSingleObjectAsync(ModelsV2.Object obj, Context? ctx = default) + { + ValidateEnvironment(ref ctx); + return ClientCtx.ObjectService!.PutSingleObjectAsync(obj, ctx!); + } + + public Task DeleteObjectAsync(ContainerId containerId, ObjectId objectId, Context? ctx = default) + { + ValidateEnvironment(ref ctx); + return ClientCtx.ObjectService!.DeleteObjectAsync(containerId, objectId, ctx!); + } + + public IAsyncEnumerable SearchObjectsAsync( + ContainerId containerId, + IEnumerable filters, + Context? ctx = default) + { + ValidateEnvironment(ref ctx); + return ClientCtx.ObjectService!.SearchObjectsAsync(containerId, filters, ctx!); + } + + public ObjectId CalculateObjectId(ObjectHeader header) + { + return ClientCtx.ObjectService!.CalculateObjectId(header); + } + + private async void CheckFrostFsVersionSupport(Context? ctx = default) + { + ValidateEnvironment(ref ctx); + var localNodeInfo = await ClientCtx.NetmapService!.GetLocalNodeInfoAsync(ctx!); + + if (!localNodeInfo.Version.IsSupported(ClientCtx.Version)) { var msg = $"FrostFS {localNodeInfo.Version} is not supported."; Console.WriteLine(msg); throw new ApplicationException(msg); } } - - private void InitGrpcChannel(string host) + + private void ValidateEnvironment(ref Context? ctx) + { + if (isDisposed) + throw new Exception("Client is disposed."); + + if (ClientCtx == null || !ClientCtx.Initialized) + throw new Exception("Client is not initialized."); + + ctx ??= new Context(); + } + + private static GrpcChannel InitGrpcChannel(string host) { Uri uri; try @@ -116,30 +252,10 @@ public partial class Client: IFrostFSClient throw new ArgumentException(msg); } - _channel = GrpcChannel.ForAddress(uri, new GrpcChannelOptions - { - Credentials = grpcCredentials, - HttpHandler = new System.Net.Http.HttpClientHandler() - }); - } - - private void InitContainerClient() - { - _containerServiceClient = new ContainerService.ContainerServiceClient(_channel); - } - - private void InitNetmapClient() - { - _netmapServiceClient = new NetmapService.NetmapServiceClient(_channel); - } - - private void InitObjectClient() - { - _objectServiceClient = new ObjectService.ObjectServiceClient(_channel); - } - - private void InitSessionClient() - { - _sessionServiceClient = new SessionService.SessionServiceClient(_channel); + return GrpcChannel.ForAddress(uri, new GrpcChannelOptions + { + Credentials = grpcCredentials, + HttpHandler = new System.Net.Http.HttpClientHandler() + }); } } diff --git a/src/FrostFS.SDK.ClientV2/FrostFS.SDK.ClientV2.csproj b/src/FrostFS.SDK.ClientV2/FrostFS.SDK.ClientV2.csproj index d446e0e..818bce2 100644 --- a/src/FrostFS.SDK.ClientV2/FrostFS.SDK.ClientV2.csproj +++ b/src/FrostFS.SDK.ClientV2/FrostFS.SDK.ClientV2.csproj @@ -6,9 +6,14 @@ enable + + true + + - + + diff --git a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs b/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs index 9ae6833..f74a084 100644 --- a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs +++ b/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs @@ -1,34 +1,39 @@ +using System; using System.Collections.Generic; -using System.IO; -using System.Threading; using System.Threading.Tasks; using FrostFS.SDK.ModelsV2; +using FrostFS.SDK.ModelsV2.Netmap; namespace FrostFS.SDK.ClientV2.Interfaces; public interface IFrostFSClient { - Task GetContainerAsync(ContainerId containerId); + Task GetContainerAsync(ContainerId containerId, Context? context = default); - IAsyncEnumerable ListContainersAsync(); + IAsyncEnumerable ListContainersAsync(Context? context = default); - Task CreateContainerAsync(ModelsV2.Container container); + Task CreateContainerAsync(ModelsV2.Container container, Context? context = default); - Task DeleteContainerAsync(ContainerId containerId); + Task DeleteContainerAsync(ContainerId containerId, Context? context = default); - Task GetObjectHeadAsync(ContainerId containerId, ObjectId objectId); + Task GetObjectHeadAsync(ContainerId containerId, ObjectId objectId, Context? context = default); + Task GetObjectAsync(ContainerId containerId, ObjectId objectId, Context? context = default); - Task GetObjectAsync(ContainerId containerId, ObjectId objectId); + Task PutObjectAsync(PutObjectParameters putObjectParameters, Context? context = default); + + Task PutSingleObjectAsync(ModelsV2.Object obj, Context? context = default); - Task PutObjectAsync(ObjectHeader header, Stream payload, CancellationToken cancellationToken = default); + Task DeleteObjectAsync(ContainerId containerId, ObjectId objectId, Context? context = default); - Task PutObjectAsync(ObjectHeader header, byte[] payload, CancellationToken cancellationToken = default); + IAsyncEnumerable SearchObjectsAsync(ContainerId cid, IEnumerable filters, Context? context = default); - Task PutSingleObjectAsync(ModelsV2.Object obj, CancellationToken cancellationToken = default); + Task GetNetmapSnapshotAsync(Context? context = default); - Task DeleteObjectAsync(ContainerId containerId, ObjectId objectId); + Task GetNodeInfoAsync(Context? context = default); - IAsyncEnumerable SearchObjectsAsync(ContainerId cid, params ObjectFilter[] filters); + ObjectId CalculateObjectId(ObjectHeader header); + + event EventHandler GrpcInvoked; } \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/Container.cs b/src/FrostFS.SDK.ClientV2/Mappers/Container.cs similarity index 75% rename from src/FrostFS.SDK.ClientV2/Mappers/GRPC/Container.cs rename to src/FrostFS.SDK.ClientV2/Mappers/Container.cs index 93eac3f..3d1207f 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/Container.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Container.cs @@ -22,19 +22,15 @@ public static class ContainerMapper public static ModelsV2.Container ToModel(this Container.Container container) { - var basicAclName = Enum.GetName(typeof(BasicAcl), container.BasicAcl); - if (basicAclName is null) - { + if (!Enum.IsDefined(typeof(BasicAcl),(int)container.BasicAcl)) throw new ArgumentException($"Unknown BasicACL rule. Value: '{container.BasicAcl}'."); - } + + BasicAcl acl = (BasicAcl)container.BasicAcl; - return new ModelsV2.Container( - (BasicAcl)Enum.Parse(typeof(BasicAcl), basicAclName), - container.PlacementPolicy.ToModel() - ) + return new ModelsV2.Container(acl, container.PlacementPolicy.ToModel()) { Nonce = container.Nonce.ToUuid(), Version = container.Version.ToModel() }; } -} \ No newline at end of file +} diff --git a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/ContainerId.cs b/src/FrostFS.SDK.ClientV2/Mappers/ContainerId.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Mappers/GRPC/ContainerId.cs rename to src/FrostFS.SDK.ClientV2/Mappers/ContainerId.cs diff --git a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/Netmap/NodeInfo.cs b/src/FrostFS.SDK.ClientV2/Mappers/GRPC/Netmap/NodeInfo.cs deleted file mode 100644 index f8f2d56..0000000 --- a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/Netmap/NodeInfo.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; - -using FrostFS.Netmap; -using FrostFS.SDK.ModelsV2.Enums; -using NodeInfo = FrostFS.SDK.ModelsV2.Netmap.NodeInfo; - -namespace FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; - -public static class NodeInfoMapper -{ - public static NodeInfo ToModel(this LocalNodeInfoResponse.Types.Body nodeInfo) - { - var nodeStateName = Enum.GetName(typeof(NodeState), nodeInfo.NodeInfo.State); - if (nodeStateName is null) - { - throw new ArgumentException($"Unknown NodeState. Value: '{nodeInfo.NodeInfo.State}'."); - } - return new NodeInfo - { - State = (NodeState)Enum.Parse(typeof(NodeState), nodeStateName), - Version = nodeInfo.Version.ToModel() - }; - } -} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/MetaHeader.cs b/src/FrostFS.SDK.ClientV2/Mappers/MetaHeader.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Mappers/GRPC/MetaHeader.cs rename to src/FrostFS.SDK.ClientV2/Mappers/MetaHeader.cs diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Netmap/Netmap.cs b/src/FrostFS.SDK.ClientV2/Mappers/Netmap/Netmap.cs new file mode 100644 index 0000000..359041c --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Mappers/Netmap/Netmap.cs @@ -0,0 +1,15 @@ +using System.Linq; +using FrostFS.Netmap; +using FrostFS.SDK.ModelsV2.Netmap; + +namespace FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; + +public static class NetmapMapper +{ + public static NetmapSnapshot ToModel(this NetmapSnapshotResponse netmap) + { + return new NetmapSnapshot( + netmap.Body.Netmap.Epoch, + netmap.Body.Netmap.Nodes.Select(n => n.ToModel(netmap.MetaHeader.Version)).ToArray()); + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Netmap/NodeInfo.cs b/src/FrostFS.SDK.ClientV2/Mappers/Netmap/NodeInfo.cs new file mode 100644 index 0000000..da87dfd --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Mappers/Netmap/NodeInfo.cs @@ -0,0 +1,35 @@ +using System; +using System.Linq; +using FrostFS.Netmap; +using FrostFS.SDK.ModelsV2.Enums; +using NodeInfo = FrostFS.SDK.ModelsV2.Netmap.NodeInfo; + +namespace FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; + +public static class NodeInfoMapper +{ + public static NodeInfo ToModel(this LocalNodeInfoResponse.Types.Body node) + { + return node.NodeInfo.ToModel(node.Version); + } + + public static NodeInfo ToModel(this FrostFS.Netmap.NodeInfo nodeInfo, Refs.Version version) + { + NodeState state = nodeInfo.State switch + { + FrostFS.Netmap.NodeInfo.Types.State.Unspecified => NodeState.Unspecified, + FrostFS.Netmap.NodeInfo.Types.State.Online => NodeState.Online, + FrostFS.Netmap.NodeInfo.Types.State.Offline => NodeState.Offline, + FrostFS.Netmap.NodeInfo.Types.State.Maintenance => NodeState.Maintenance, + _ => throw new ArgumentException($"Unknown NodeState. Value: '{nodeInfo.State}'.") + }; + + return new NodeInfo( + version: version.ToModel(), + state: state, + addresses: [.. nodeInfo.Addresses], + attributes: nodeInfo.Attributes.ToDictionary(n => n.Key, n => n.Value), + publicKey: nodeInfo.PublicKey.ToByteArray() + ); + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/Netmap/PlacementPolicy.cs b/src/FrostFS.SDK.ClientV2/Mappers/Netmap/PlacementPolicy.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Mappers/GRPC/Netmap/PlacementPolicy.cs rename to src/FrostFS.SDK.ClientV2/Mappers/Netmap/PlacementPolicy.cs diff --git a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/Netmap/Replica.cs b/src/FrostFS.SDK.ClientV2/Mappers/Netmap/Replica.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Mappers/GRPC/Netmap/Replica.cs rename to src/FrostFS.SDK.ClientV2/Mappers/Netmap/Replica.cs diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Object/Object.cs b/src/FrostFS.SDK.ClientV2/Mappers/Object/Object.cs new file mode 100644 index 0000000..be61b13 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Mappers/Object/Object.cs @@ -0,0 +1,14 @@ +using FrostFS.SDK.ModelsV2; + +namespace FrostFS.SDK.ClientV2.Mappers.GRPC; + +public static class ObjectMapper +{ + public static ModelsV2.Object ToModel(this Object.Object obj) + { + return new ModelsV2.Object( + ObjectId.FromHash(obj.ObjectId.Value.ToByteArray()), + obj.Header.ToModel(), + obj.Payload.ToByteArray()); + } +} diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectAttributeMapper.cs b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectAttributeMapper.cs new file mode 100644 index 0000000..75c4bb8 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectAttributeMapper.cs @@ -0,0 +1,21 @@ +using FrostFS.Object; +using FrostFS.SDK.ModelsV2; + +namespace FrostFS.SDK.ClientV2.Mappers.GRPC; + +public static class ObjectAttributeMapper +{ + public static Header.Types.Attribute ToGrpcMessage(this ObjectAttribute attribute) + { + return new Header.Types.Attribute + { + Key = attribute.Key, + Value = attribute.Value + }; + } + + public static ObjectAttribute ToModel(this Header.Types.Attribute attribute) + { + return new ObjectAttribute(attribute.Key, attribute.Value); + } +} diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectFilterMapper.cs b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectFilterMapper.cs new file mode 100644 index 0000000..ee819c6 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectFilterMapper.cs @@ -0,0 +1,30 @@ +using System; +using FrostFS.Object; +using FrostFS.SDK.ModelsV2; +using MatchType = FrostFS.Object.MatchType; + +namespace FrostFS.SDK.ClientV2.Mappers.GRPC; + +public static class ObjectFilterMapper +{ + public static SearchRequest.Types.Body.Types.Filter ToGrpcMessage(this ObjectFilter filter) + { + var objMatchTypeName = filter.MatchType switch + { + ModelsV2.Enums.ObjectMatchType.Unspecified => MatchType.Unspecified, + ModelsV2.Enums.ObjectMatchType.Equals => MatchType.StringEqual, + ModelsV2.Enums.ObjectMatchType.NotEquals => MatchType.StringNotEqual, + ModelsV2.Enums.ObjectMatchType.KeyAbsent => MatchType.NotPresent, + ModelsV2.Enums.ObjectMatchType.StartsWith => MatchType.CommonPrefix, + + _ => throw new ArgumentException($"Unknown MatchType. Value: '{filter.MatchType}'.") + }; + + return new SearchRequest.Types.Body.Types.Filter + { + MatchType = objMatchTypeName, + Key = filter.Key, + Value = filter.Value + }; + } +} diff --git a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/Object.cs b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectHeaderMapper.cs similarity index 55% rename from src/FrostFS.SDK.ClientV2/Mappers/GRPC/Object.cs rename to src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectHeaderMapper.cs index 15a7b1c..7e82dd3 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/Object.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectHeaderMapper.cs @@ -4,52 +4,10 @@ using FrostFS.Object; using FrostFS.SDK.Cryptography; using FrostFS.SDK.ModelsV2; using Google.Protobuf; -using MatchType = FrostFS.Object.MatchType; using ObjectType = FrostFS.Object.ObjectType; namespace FrostFS.SDK.ClientV2.Mappers.GRPC; -public static class ObjectAttributeMapper -{ - public static Header.Types.Attribute ToGrpcMessage(this ObjectAttribute attribute) - { - return new Header.Types.Attribute - { - Key = attribute.Key, - Value = attribute.Value - }; - } - - public static ObjectAttribute ToModel(this Header.Types.Attribute attribute) - { - return new ObjectAttribute(attribute.Key, attribute.Value); - } -} - -public static class ObjectFilterMapper -{ - public static SearchRequest.Types.Body.Types.Filter ToGrpcMessage(this ObjectFilter filter) - { - var objMatchTypeName = filter.MatchType switch - { - ModelsV2.Enums.ObjectMatchType.Unspecified => MatchType.Unspecified, - ModelsV2.Enums.ObjectMatchType.Equals => MatchType.StringEqual, - ModelsV2.Enums.ObjectMatchType.NotEquals => MatchType.StringNotEqual, - ModelsV2.Enums.ObjectMatchType.KeyAbsent => MatchType.NotPresent, - ModelsV2.Enums.ObjectMatchType.StartsWith => MatchType.CommonPrefix, - - _ => throw new ArgumentException($"Unknown MatchType. Value: '{filter.MatchType}'.") - }; - - return new SearchRequest.Types.Body.Types.Filter - { - MatchType = objMatchTypeName, - Key = filter.Key, - Value = filter.Value - }; - } -} - public static class ObjectHeaderMapper { public static Header ToGrpcMessage(this ObjectHeader header) @@ -130,37 +88,3 @@ public static class ObjectHeaderMapper return model; } } - -public static class ObjectMapper -{ - public static ModelsV2.Object ToModel(this Object.Object obj) - { - return new ModelsV2.Object() - { - Header = obj.Header.ToModel(), - ObjectId = ObjectId.FromHash(obj.ObjectId.Value.ToByteArray()), - Payload = obj.Payload.ToByteArray() - }; - } -} - -public static class SignatureMapper -{ - public static Refs.Signature ToGrpcMessage(this Signature signature) - { - var scheme = signature.Scheme switch - { - SignatureScheme.EcdsaRfc6979Sha256 => Refs.SignatureScheme.EcdsaRfc6979Sha256, - SignatureScheme.EcdsaRfc6979Sha256WalletConnect => Refs.SignatureScheme.EcdsaRfc6979Sha256WalletConnect, - SignatureScheme.EcdsaSha512 => Refs.SignatureScheme.EcdsaSha512, - _ => throw new ArgumentException(message: $"Unexpected enum value: {signature.Scheme}", paramName: nameof(signature.Scheme)) - }; - - return new Refs.Signature - { - Key = ByteString.CopyFrom(signature.Key), - Scheme = scheme, - Sign = ByteString.CopyFrom(signature.Sign) - }; - } -} diff --git a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/ObjectId.cs b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectId.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Mappers/GRPC/ObjectId.cs rename to src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectId.cs diff --git a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/OwnerId.cs b/src/FrostFS.SDK.ClientV2/Mappers/OwnerId.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Mappers/GRPC/OwnerId.cs rename to src/FrostFS.SDK.ClientV2/Mappers/OwnerId.cs diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Session/Session.cs b/src/FrostFS.SDK.ClientV2/Mappers/Session/Session.cs new file mode 100644 index 0000000..402f7e1 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Mappers/Session/Session.cs @@ -0,0 +1,25 @@ + +using System; +using Google.Protobuf; + +namespace FrostFS.SDK.ClientV2.Mappers.GRPC; + +public static class SessionMapper +{ + internal static string SerializeSessionToken(this Session.SessionToken token) + { + byte[] bytes = new byte[token.CalculateSize()]; + CodedOutputStream stream = new(bytes); + token.WriteTo(stream); + + return Convert.ToBase64String(bytes); + } + + internal static Session.SessionToken DeserializeSessionToken(this byte[] bytes) + { + Session.SessionToken token = new(); + token.MergeFrom(bytes); + + return token; + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Mappers/SignatureMapper.cs b/src/FrostFS.SDK.ClientV2/Mappers/SignatureMapper.cs new file mode 100644 index 0000000..2da358a --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Mappers/SignatureMapper.cs @@ -0,0 +1,26 @@ +using System; +using FrostFS.SDK.ModelsV2; +using Google.Protobuf; + +namespace FrostFS.SDK.ClientV2.Mappers.GRPC; + +public static class SignatureMapper +{ + public static Refs.Signature ToGrpcMessage(this Signature signature) + { + var scheme = signature.Scheme switch + { + SignatureScheme.EcdsaRfc6979Sha256 => Refs.SignatureScheme.EcdsaRfc6979Sha256, + SignatureScheme.EcdsaRfc6979Sha256WalletConnect => Refs.SignatureScheme.EcdsaRfc6979Sha256WalletConnect, + SignatureScheme.EcdsaSha512 => Refs.SignatureScheme.EcdsaSha512, + _ => throw new ArgumentException(message: $"Unexpected enum value: {signature.Scheme}", paramName: nameof(signature.Scheme)) + }; + + return new Refs.Signature + { + Key = ByteString.CopyFrom(signature.Key), + Scheme = scheme, + Sign = ByteString.CopyFrom(signature.Sign) + }; + } +} diff --git a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/Status.cs b/src/FrostFS.SDK.ClientV2/Mappers/Status.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Mappers/GRPC/Status.cs rename to src/FrostFS.SDK.ClientV2/Mappers/Status.cs diff --git a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/Version.cs b/src/FrostFS.SDK.ClientV2/Mappers/Version.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Mappers/GRPC/Version.cs rename to src/FrostFS.SDK.ClientV2/Mappers/Version.cs diff --git a/src/FrostFS.SDK.ClientV2/Services/Container.cs b/src/FrostFS.SDK.ClientV2/Services/Container.cs deleted file mode 100644 index 7566492..0000000 --- a/src/FrostFS.SDK.ClientV2/Services/Container.cs +++ /dev/null @@ -1,85 +0,0 @@ -using FrostFS.Container; -using FrostFS.SDK.ClientV2.Mappers.GRPC; -using FrostFS.SDK.Cryptography; -using FrostFS.SDK.ModelsV2; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace FrostFS.SDK.ClientV2; - -public partial class Client -{ - public async Task GetContainerAsync(ContainerId cid) - { - var request = new GetRequest - { - Body = new GetRequest.Types.Body - { - ContainerId = cid.ToGrpcMessage() - }, - }; - - request.AddMetaHeader(); - request.Sign(_key); - var response = await _containerServiceClient.GetAsync(request); - Verifier.CheckResponse(response); - return response.Body.Container.ToModel(); - } - - public async IAsyncEnumerable ListContainersAsync() - { - var request = new ListRequest - { - Body = new ListRequest.Types.Body - { - OwnerId = OwnerId.ToGrpcMessage() - } - }; - - request.AddMetaHeader(); - request.Sign(_key); - var response = await _containerServiceClient.ListAsync(request); - Verifier.CheckResponse(response); - foreach (var cid in response.Body.ContainerIds) - { - yield return new ContainerId(Base58.Encode(cid.Value.ToByteArray())); - } - } - - public async Task CreateContainerAsync(ModelsV2.Container container) - { - var cntnr = container.ToGrpcMessage(); - cntnr.OwnerId = OwnerId.ToGrpcMessage(); - cntnr.Version = Version.ToGrpcMessage(); - - var request = new PutRequest - { - Body = new PutRequest.Types.Body - { - Container = cntnr, - Signature = _key.SignRFC6979(cntnr), - } - }; - request.AddMetaHeader(); - request.Sign(_key); - var response = await _containerServiceClient.PutAsync(request); - Verifier.CheckResponse(response); - return new ContainerId(Base58.Encode(response.Body.ContainerId.Value.ToByteArray())); - } - - public async Task DeleteContainerAsync(ContainerId cid) - { - var request = new DeleteRequest - { - Body = new DeleteRequest.Types.Body - { - ContainerId = cid.ToGrpcMessage(), - Signature = _key.SignRFC6979(cid.ToGrpcMessage().Value) - } - }; - request.AddMetaHeader(); - request.Sign(_key); - var response = await _containerServiceClient.DeleteAsync(request); - Verifier.CheckResponse(response); - } -} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs new file mode 100644 index 0000000..61f60c1 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs @@ -0,0 +1,117 @@ +using System.Threading.Tasks; +using FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; +using FrostFS.SDK.ModelsV2; +using FrostFS.Container; + +using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Cryptography; +using System.Collections.Generic; + +namespace FrostFS.SDK.ClientV2; + +internal class ContainerServiceProvider : ContextAccessor +{ + private readonly ContainerService.ContainerServiceClient containerServiceClient; + + internal ContainerServiceProvider(ContainerService.ContainerServiceClient service, ClientEnvironment context) + : base(context) + { + containerServiceClient = service; + } + + internal async Task GetContainerAsync(ContainerId cid, Context context) + { + var request = new GetRequest + { + Body = new GetRequest.Types.Body + { + ContainerId = cid.ToGrpcMessage() + }, + }; + + request.AddMetaHeader(); + request.Sign(Context.Key); + + var response = await Context.InvokeAsyncUnaryWithMetrics(() => + containerServiceClient.GetAsync(request, null, context.Deadline, context.CancellationToken), + nameof(containerServiceClient.GetAsync)); + + Verifier.CheckResponse(response); + + return response.Body.Container.ToModel(); + } + + internal async IAsyncEnumerable ListContainersAsync(Context ctx) + { + var request = new ListRequest + { + Body = new ListRequest.Types.Body + { + OwnerId = Context.Owner.ToGrpcMessage() + } + }; + + request.AddMetaHeader(); + request.Sign(Context.Key); + + var response = await Context.InvokeAsyncUnaryWithMetrics(() => + containerServiceClient.ListAsync(request, null, ctx.Deadline, ctx.CancellationToken), + nameof(containerServiceClient.ListAsync)); + + Verifier.CheckResponse(response); + + foreach (var cid in response.Body.ContainerIds) + { + yield return new ContainerId(Base58.Encode(cid.Value.ToByteArray())); + } + } + + internal async Task CreateContainerAsync(ModelsV2.Container container, Context ctx) + { + var grpcContainer = container.ToGrpcMessage(); + grpcContainer.OwnerId = Context.Owner.ToGrpcMessage(); + grpcContainer.Version = Context.Version.ToGrpcMessage(); + + var request = new PutRequest + { + Body = new PutRequest.Types.Body + { + Container = grpcContainer, + Signature = Context.Key.SignRFC6979(grpcContainer), + } + }; + request.AddMetaHeader(); + request.Sign(Context.Key); + + var response = await Context.InvokeAsyncUnaryWithMetrics(() => + containerServiceClient.PutAsync(request, null, ctx.Deadline, ctx.CancellationToken), + nameof(containerServiceClient.PutAsync)); + + Verifier.CheckResponse(response); + + return new ContainerId(Base58.Encode(response.Body.ContainerId.Value.ToByteArray())); + } + + internal async Task DeleteContainerAsync(ContainerId cid, Context ctx) + { + var request = new DeleteRequest + { + Body = new DeleteRequest.Types.Body + { + ContainerId = cid.ToGrpcMessage(), + Signature = Context.Key.SignRFC6979(cid.ToGrpcMessage().Value) + } + }; + + request.AddMetaHeader(); + request.Sign(Context.Key); + + var response = await Context.InvokeAsyncUnaryWithMetrics(() => + containerServiceClient.DeleteAsync(request, null, ctx.Deadline, ctx.CancellationToken), + nameof(containerServiceClient.DeleteAsync)); + + Verifier.CheckResponse(response); + } +} + + diff --git a/src/FrostFS.SDK.ClientV2/Services/Netmap.cs b/src/FrostFS.SDK.ClientV2/Services/Netmap.cs deleted file mode 100644 index ce439cc..0000000 --- a/src/FrostFS.SDK.ClientV2/Services/Netmap.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Threading.Tasks; - -using FrostFS.Netmap; -using FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; - -using NodeInfo = FrostFS.SDK.ModelsV2.Netmap.NodeInfo; - -namespace FrostFS.SDK.ClientV2; - -public partial class Client -{ - public async Task GetLocalNodeInfoAsync() - { - var request = new LocalNodeInfoRequest - { - Body = new LocalNodeInfoRequest.Types.Body { } - }; - - request.AddMetaHeader(); - request.Sign(_key); - var response = await _netmapServiceClient.LocalNodeInfoAsync(request); - - return response.Body.ToModel(); - } - - public async Task GetNetworkInfoAsync() - { - var request = new NetworkInfoRequest - { - Body = new NetworkInfoRequest.Types.Body { } - }; - - request.AddMetaHeader(); - request.Sign(_key); - - return await _netmapServiceClient.NetworkInfoAsync(request); - } -} diff --git a/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs new file mode 100644 index 0000000..da3282e --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs @@ -0,0 +1,134 @@ +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using FrostFS.Netmap; +using FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; +using NodeInfo = FrostFS.SDK.ModelsV2.Netmap.NodeInfo; + +using FrostFS.SDK.ModelsV2.Netmap; +using static FrostFS.Netmap.NetworkConfig.Types; + +namespace FrostFS.SDK.ClientV2; + +internal class NetmapServiceProvider : ContextAccessor +{ + private readonly NetmapService.NetmapServiceClient netmapServiceClient; + + internal NetmapServiceProvider(NetmapService.NetmapServiceClient netmapServiceClient, ClientEnvironment context) + : base(context) + { + this.netmapServiceClient = netmapServiceClient; + } + + internal async Task GetNetworkSettingsAsync(Context ctx) + { + if (Context.NetworkSettings != null) + return Context.NetworkSettings; + + var info = await GetNetworkInfoAsync(ctx); + + var settings = new NetworkSettings(); + + foreach (var param in info.Body.NetworkInfo.NetworkConfig.Parameters) + { + SetNetworksParam(param, settings); + } + + Context.NetworkSettings = settings; + + return settings; + } + + internal async Task GetLocalNodeInfoAsync(Context ctx) + { + var request = new LocalNodeInfoRequest + { + Body = new LocalNodeInfoRequest.Types.Body { } + }; + + request.AddMetaHeader(); + request.Sign(Context.Key); + + var response = await Context.InvokeAsyncUnaryWithMetrics(() => + netmapServiceClient.LocalNodeInfoAsync(request, null, ctx.Deadline, ctx.CancellationToken), + nameof(netmapServiceClient.LocalNodeInfoAsync)); + + Verifier.CheckResponse(response); + + return response.Body.ToModel(); + } + + internal async Task GetNetworkInfoAsync(Context ctx) + { + var request = new NetworkInfoRequest + { + Body = new NetworkInfoRequest.Types.Body { } + }; + + request.AddMetaHeader(); + request.Sign(Context.Key); + + var response = await Context.InvokeAsyncUnaryWithMetrics(() => + netmapServiceClient.NetworkInfoAsync(request, null, ctx.Deadline, ctx.CancellationToken), + nameof(netmapServiceClient.NetworkInfoAsync)); + + Verifier.CheckResponse(response); + + return response; + } + + internal async Task GetNetmapSnapshotAsync(Context ctx) + { + var request = new NetmapSnapshotRequest + { + Body = new NetmapSnapshotRequest.Types.Body { } + }; + + request.AddMetaHeader(); + request.Sign(Context.Key); + + var response = await Context.InvokeAsyncUnaryWithMetrics(() => + netmapServiceClient.NetmapSnapshotAsync(request, null, ctx.Deadline, ctx.CancellationToken), + nameof(netmapServiceClient.NetmapSnapshotAsync)); + + Verifier.CheckResponse(response); + + return response.ToModel(); + } + + private bool GetBoolValue(byte[] bytes) + { + return bytes.Any(b => b != 0); + } + + private ulong GetLongValue(byte[] bytes) + { + ulong val = 0; + for (var i = bytes.Length - 1; i >= 0; i--) + val = (val << 8) + bytes[i]; + + return val; + } + + private void SetNetworksParam(Parameter param, NetworkSettings settings) + { + var key = Encoding.UTF8.GetString(param.Key.ToByteArray()); + + var valueBytes = param.Value.ToByteArray(); + switch (key) + { + case "ContainerFee": settings.ContainerFee = GetLongValue(valueBytes); break; + case "EpochDuration": settings.EpochDuration = GetLongValue(valueBytes); break; + case "IRCandidateFee": settings.IRCandidateFee = GetLongValue(valueBytes); break; + case "MaxECDataCount": settings.MaxECDataCount = GetLongValue(valueBytes); break; + case "MaxECParityCount": settings.MaxECParityCount = GetLongValue(valueBytes); break; + case "MaxObjectSize": settings.MaxObjectSize = GetLongValue(valueBytes); break; + case "WithdrawalFee": settings.WithdrawalFee = GetLongValue(valueBytes); break; + case "HomomorphicHashingDisabled": settings.HomomorphicHashingDisabled = GetBoolValue(valueBytes); break; + case "MaintenanceModeAllowed": settings.MaintenanceModeAllowed = GetBoolValue(valueBytes); break; + default: settings.UnnamedSettings.Add(key, valueBytes); break; + } + } +} + + diff --git a/src/FrostFS.SDK.ClientV2/Services/ObjectReader.cs b/src/FrostFS.SDK.ClientV2/Services/ObjectReader.cs index 415c001..73e982a 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ObjectReader.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ObjectReader.cs @@ -7,20 +7,20 @@ using FrostFS.Object; namespace FrostFS.SDK.ClientV2; -internal class ObjectReader : IDisposable +internal class ObjectReader(AsyncServerStreamingCall call) : IDisposable { - public AsyncServerStreamingCall Call { get; set; } + public AsyncServerStreamingCall Call { get; private set; } = call; public async Task ReadHeader() { if (!await Call.ResponseStream.MoveNext()) - throw new InvalidOperationException("unexpect end of stream"); + throw new InvalidOperationException("unexpected end of stream"); var response = Call.ResponseStream.Current; Verifier.CheckResponse(response); if (response.Body.ObjectPartCase != GetResponse.Types.Body.ObjectPartOneofCase.Init) - throw new InvalidOperationException("unexpect message type"); + throw new InvalidOperationException("unexpected message type"); return new Object.Object { @@ -38,7 +38,7 @@ internal class ObjectReader : IDisposable Verifier.CheckResponse(response); if (response.Body.ObjectPartCase != GetResponse.Types.Body.ObjectPartOneofCase.Chunk) - throw new InvalidOperationException("unexpect message type"); + throw new InvalidOperationException("unexpected message type"); return response.Body.Chunk.ToByteArray(); } diff --git a/src/FrostFS.SDK.ClientV2/Services/Object.cs b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs similarity index 56% rename from src/FrostFS.SDK.ClientV2/Services/Object.cs rename to src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs index 004df1d..dcb15f7 100644 --- a/src/FrostFS.SDK.ClientV2/Services/Object.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Threading.Tasks; @@ -11,16 +10,22 @@ using FrostFS.Refs; using FrostFS.SDK.ClientV2.Mappers.GRPC; using FrostFS.SDK.Cryptography; using FrostFS.Session; - using FrostFS.SDK.ModelsV2; using FrostFS.SDK.ClientV2.Extensions; -using System.Threading; namespace FrostFS.SDK.ClientV2; -public partial class Client +internal class ObjectServiceProvider : ContextAccessor { - public async Task GetObjectHeadAsync(ContainerId cid, ObjectId oid) + private readonly ObjectService.ObjectServiceClient objectServiceClient; + + internal ObjectServiceProvider(ObjectService.ObjectServiceClient objectServiceClient, ClientEnvironment context) + : base (context) + { + this.objectServiceClient = objectServiceClient; + } + + internal async Task GetObjectHeadAsync(ContainerId cid, ObjectId oid, Context ctx) { var request = new HeadRequest { @@ -34,18 +39,22 @@ public partial class Client } }; - request.AddMetaHeader(); - request.Sign(_key); - var response = await _objectServiceClient!.HeadAsync(request); + request.Sign(Context.Key); + + var response = await Context.InvokeAsyncUnaryWithMetrics(() => + objectServiceClient!.HeadAsync(request, null, ctx.Deadline, ctx.CancellationToken), + nameof(objectServiceClient.HeadAsync)); + Verifier.CheckResponse(response); return response.Body.Header.Header.ToModel(); } - public async Task GetObjectAsync(ContainerId cid, ObjectId oid) + internal async Task GetObjectAsync(ContainerId cid, ObjectId oid, Context ctx) { - var sessionToken = await CreateSessionAsync(uint.MaxValue); + var sessionToken = await GetOrCreateSession(ctx); + var request = new GetRequest { Body = new GetRequest.Types.Body @@ -64,41 +73,137 @@ public partial class Client cid.ToGrpcMessage(), oid.ToGrpcMessage(), ObjectSessionContext.Types.Verb.Get, - _key + Context.Key ); - request.Sign(_key); - var obj = await GetObject(request); + request.Sign(Context.Key); + + var obj = await GetObject(request, ctx); return obj.ToModel(); } - public async Task PutObjectAsync(ObjectHeader header, Stream payload, CancellationToken cancellationToken = default) + internal Task PutObjectAsync(PutObjectParameters parameters, Context ctx) { - return await PutObject(header, payload, cancellationToken); - } - - public async Task PutObjectAsync(ObjectHeader header, byte[] payload, CancellationToken cancellationToken = default) - { - using var stream = new MemoryStream(payload); - return await PutObject(header, stream, cancellationToken); - } - - private Task PutObject(ObjectHeader header, Stream payload, CancellationToken cancellationToken) - { - if (header.ClientCut) - return PutClientCutObject(header, payload, cancellationToken); + if (parameters.Header == null) + throw new ArgumentException("Value cannot be null", nameof(parameters.Header)); + + if (parameters.Payload == null) + throw new ArgumentException("Value cannot be null", nameof(parameters.Payload)); + + if (parameters.ClientCut) + return PutClientCutObject(parameters, ctx); else - return PutStreamObject(header, payload, cancellationToken); + return PutStreamObject(parameters, ctx); + } + + internal async Task PutSingleObjectAsync(ModelsV2.Object @object, Context ctx) + { + var sessionToken = await GetOrCreateSession(ctx); + + var obj = CreateObject(@object); + + var request = new PutSingleRequest + { + Body = new PutSingleRequest.Types.Body() + { + Object = obj + } + }; + + request.AddMetaHeader(); + request.AddObjectSessionToken( + sessionToken, + obj.Header.ContainerId, + obj.ObjectId, + ObjectSessionContext.Types.Verb.Put, + Context.Key + ); + + request.Sign(Context.Key); + + var response = await Context.InvokeAsyncUnaryWithMetrics(() => + objectServiceClient!.PutSingleAsync(request, null, ctx.Deadline, ctx.CancellationToken), + nameof(objectServiceClient.PutSingleAsync)); + + Verifier.CheckResponse(response); + + return ObjectId.FromHash(obj.ObjectId.Value.ToByteArray()); } - private async Task PutClientCutObject(ObjectHeader header, Stream payloadStream, CancellationToken cancellationToken) + internal ObjectId CalculateObjectId(ObjectHeader header) { - ObjectId? objectId = null; + var grpcHeader = CreateHeader(header, []); + + return new ObjectID { Value = grpcHeader.Sha256() }.ToModel(); + } + + internal async Task DeleteObjectAsync(ContainerId cid, ObjectId oid, Context ctx) + { + var request = new DeleteRequest + { + Body = new DeleteRequest.Types.Body + { + Address = new Address + { + ContainerId = cid.ToGrpcMessage(), + ObjectId = oid.ToGrpcMessage() + } + } + }; + + request.AddMetaHeader(); + request.Sign(Context.Key); + + var response = await Context.InvokeAsyncUnaryWithMetrics(() => + objectServiceClient!.DeleteAsync(request, null, ctx.Deadline, ctx.CancellationToken), + nameof(objectServiceClient.DeleteAsync)); + + Verifier.CheckResponse(response); + } + + internal async IAsyncEnumerable SearchObjectsAsync( + ContainerId cid, + IEnumerable filters, + Context ctx) + { + var request = new SearchRequest + { + Body = new SearchRequest.Types.Body + { + ContainerId = cid.ToGrpcMessage(), + Filters = { }, + Version = 1 // TODO: clarify this param + } + }; + + request.Body.Filters.AddRange(filters.Select(f => f.ToGrpcMessage())); + + request.AddMetaHeader(); + request.Sign(Context.Key); + + var objectsIds = SearchObjects(request, ctx); + + await foreach (var oid in objectsIds) + { + yield return ObjectId.FromHash(oid.Value.ToByteArray()); + } + } + + private async Task PutClientCutObject(PutObjectParameters parameters, Context ctx) + { + var payloadStream = parameters.Payload!; + var header = parameters.Header!; + + ObjectId? objectId; List sentObjectIds = []; ModelsV2.Object? currentObject; - var partSize = (int)NetworkSettings["MaxObjectSize"]; + var networkSettings = await Context.InvokeAsyncWithMetrics(() => + Context.NetmapService!.GetNetworkSettingsAsync(ctx), + nameof(Context.NetmapService.GetNetworkSettingsAsync)); + + var partSize = (int)networkSettings.MaxObjectSize; var buffer = new byte[partSize]; var largeObject = new LargeObject(header.ContainerId); @@ -109,8 +214,6 @@ public partial class Client while (true) { - cancellationToken.ThrowIfCancellationRequested(); - var bytesCount = await payloadStream.ReadAsync(buffer, 0, partSize); split.Previous = sentObjectIds.LastOrDefault(); @@ -124,7 +227,7 @@ public partial class Client if (largeObject.PayloadLength == fullLength) break; - objectId = await PutSingleObjectAsync(currentObject, cancellationToken); + objectId = await PutSingleObjectAsync(currentObject, ctx); sentObjectIds.Add(objectId!); } @@ -135,26 +238,30 @@ public partial class Client currentObject.SetParent(largeObject); - objectId = await PutSingleObjectAsync(currentObject, cancellationToken); + objectId = await PutSingleObjectAsync(currentObject, ctx); sentObjectIds.Add(objectId); var linkObject = new LinkObject(header.ContainerId, split.SplitId, largeObject) .AddChildren(sentObjectIds); - _ = await PutSingleObjectAsync(linkObject, cancellationToken); + _ = await PutSingleObjectAsync(linkObject, ctx); - return currentObject.GetParentId(); + return CalculateObjectId(largeObject.Header); } - return await PutSingleObjectAsync(currentObject, cancellationToken); + return await PutSingleObjectAsync(currentObject, ctx); } - private async Task PutStreamObject(ObjectHeader header, Stream payload, CancellationToken cancellationToken) + private async Task PutStreamObject(PutObjectParameters parameters, Context ctx) { - var sessionToken = await CreateSessionAsync(uint.MaxValue); + var payload = parameters.Payload!; + var header = parameters.Header!; + + var sessionToken = await GetOrCreateSession(ctx); + var hdr = header.ToGrpcMessage(); - hdr.OwnerId = OwnerId.ToGrpcMessage(); - hdr.Version = Version.ToGrpcMessage(); + hdr.OwnerId = Context.Owner.ToGrpcMessage(); + hdr.Version = Context.Version.ToGrpcMessage(); var oid = new ObjectID { @@ -178,19 +285,17 @@ public partial class Client hdr.ContainerId, oid, ObjectSessionContext.Types.Verb.Put, - _key + Context.Key ); - request.Sign(_key); + request.Sign(Context.Key); - using var stream = await PutObjectInit(request); + using var stream = await PutObjectInit(request, ctx); var buffer = new byte[Constants.ObjectChunkSize]; while (true) { - cancellationToken.ThrowIfCancellationRequested(); - - var bufferLength = await payload.ReadAsync(buffer, 0, Constants.ObjectChunkSize); + var bufferLength = await payload.ReadAsync(buffer, 0, Constants.ObjectChunkSize, ctx.CancellationToken); if (bufferLength == 0) break; @@ -201,7 +306,7 @@ public partial class Client }; request.VerifyHeader = null; - request.Sign(_key); + request.Sign(Context.Key); await stream.Write(request); } @@ -211,40 +316,98 @@ public partial class Client return ObjectId.FromHash(response.Body.ObjectId.Value.ToByteArray()); } - public async Task PutSingleObjectAsync(ModelsV2.Object @object, CancellationToken cancellationToken = default) + // TODO: add implementation with stream writer! + private async Task GetObject(GetRequest request, Context ctx) { - var sessionToken = await CreateSessionAsync(uint.MaxValue); - - var obj = CreateObject(@object); + using var stream = GetObjectInit(request, ctx); + + var obj = await stream.ReadHeader(); + var payload = new byte[obj.Header.PayloadLength]; + var offset = 0L; + var chunk = await stream.ReadChunk(); - var request = new PutSingleRequest + while (chunk is not null && (ulong)offset < obj.Header.PayloadLength) { - Body = new () { Object = obj } - }; + var length = Math.Min((long)obj.Header.PayloadLength - offset, chunk.Length); - request.AddMetaHeader(); - request.AddObjectSessionToken( - sessionToken, - obj.Header.ContainerId, - obj.ObjectId, - ObjectSessionContext.Types.Verb.Put, - _key - ); + Array.Copy(chunk, 0, payload, offset, length); + offset += chunk.Length; + chunk = await stream.ReadChunk(); + } - request.Sign(_key); + obj.Payload = ByteString.CopyFrom(payload); - var response = await _objectServiceClient!.PutSingleAsync(request, null, null, cancellationToken); - Verifier.CheckResponse(response); - - return ObjectId.FromHash(obj.ObjectId.Value.ToByteArray()); + return obj; + } + + private ObjectReader GetObjectInit(GetRequest initRequest, Context ctx) + { + if (initRequest is null) + throw new ArgumentNullException(nameof(initRequest)); + + var call = Context.InvokeWithMetrics(() => + objectServiceClient!.Get(initRequest, null, ctx.Deadline, ctx.CancellationToken), + nameof(objectServiceClient.Get)); + + return new ObjectReader(call); + } + + private async Task PutObjectInit(PutRequest initRequest, Context ctx) + { + if (initRequest is null) + { + throw new ArgumentNullException(nameof(initRequest)); + } + + var call = Context.InvokeWithMetrics(() => + objectServiceClient!.Put(null, ctx.Deadline, ctx.CancellationToken), + nameof(objectServiceClient.Put)); + + await call.RequestStream.WriteAsync(initRequest); + + return new ObjectStreamer(call); + } + + private async IAsyncEnumerable SearchObjects(SearchRequest request, Context ctx) + { + using var stream = GetSearchReader(request, ctx); + + while (true) + { + var ids = await Context.InvokeAsyncWithMetrics(() => + stream.Read(ctx.CancellationToken), + "SearchObject.ReadStream"); + + if (ids == null) + break; + + foreach (var oid in ids) + { + yield return oid; + } + } + } + + private SearchReader GetSearchReader(SearchRequest initRequest, Context ctx) + { + if (initRequest is null) + { + throw new ArgumentNullException(nameof(initRequest)); + } + + var call = Context.InvokeWithMetrics(() => + objectServiceClient!.Search(initRequest, null, ctx.Deadline, ctx.CancellationToken), + nameof(objectServiceClient.Search)); + + return new SearchReader(call); } - public Object.Object CreateObject(ModelsV2.Object @object) + private Object.Object CreateObject(ModelsV2.Object @object) { var grpcHeader = @object.Header.ToGrpcMessage(); - grpcHeader.OwnerId = OwnerId.ToGrpcMessage(); - grpcHeader.Version = Version.ToGrpcMessage(); + grpcHeader.OwnerId = Context.Owner.ToGrpcMessage(); + grpcHeader.Version = Context.Version.ToGrpcMessage(); if (@object.Payload != null) { @@ -271,8 +434,8 @@ public partial class Client grpcHeader.Split.ParentHeader = grpcParentHeader; grpcHeader.Split.ParentSignature = new Refs.Signature { - Key = ByteString.CopyFrom(_key.PublicKey()), - Sign = ByteString.CopyFrom(_key.SignData(grpcHeader.Split.Parent.ToByteArray())), + Key = ByteString.CopyFrom(Context.Key.PublicKey()), + Sign = ByteString.CopyFrom(Context.Key.SignData(grpcHeader.Split.Parent.ToByteArray())), }; split.Parent = grpcHeader.Split.Parent.ToModel(); @@ -290,159 +453,29 @@ public partial class Client obj.Signature = new Refs.Signature { - Key = ByteString.CopyFrom(_key.PublicKey()), - Sign = ByteString.CopyFrom(_key.SignData(obj.ObjectId.ToByteArray())), + Key = ByteString.CopyFrom(Context.Key.PublicKey()), + Sign = ByteString.CopyFrom(Context.Key.SignData(obj.ObjectId.ToByteArray())), }; return obj; } - public Header CreateHeader(ObjectHeader header, byte[]? payload) + private Header CreateHeader(ObjectHeader header, byte[]? payload) { var grpcHeader = header.ToGrpcMessage(); - grpcHeader.OwnerId = OwnerId.ToGrpcMessage(); - grpcHeader.Version = Version.ToGrpcMessage(); + grpcHeader.OwnerId = Context.Owner.ToGrpcMessage(); + grpcHeader.Version = Context.Version.ToGrpcMessage(); if (header.PayloadCheckSum != null) - { - grpcHeader.PayloadHash = new Checksum - { - Type = ChecksumType.Sha256, - Sum = ByteString.CopyFrom(header.PayloadCheckSum) - }; - } - else - { - if (payload != null) - grpcHeader.PayloadHash = Sha256Checksum(payload); - } - + grpcHeader.PayloadHash = Sha256Checksum(header.PayloadCheckSum); + else if (payload != null) + grpcHeader.PayloadHash = Sha256Checksum(payload); + return grpcHeader; } - public async Task DeleteObjectAsync(ContainerId cid, ObjectId oid) - { - var request = new DeleteRequest - { - Body = new DeleteRequest.Types.Body - { - Address = new Address - { - ContainerId = cid.ToGrpcMessage(), - ObjectId = oid.ToGrpcMessage() - } - } - }; - - request.AddMetaHeader(); - request.Sign(_key); - var response = await _objectServiceClient!.DeleteAsync(request); - Verifier.CheckResponse(response); - } - - public async IAsyncEnumerable SearchObjectsAsync(ContainerId cid, params ObjectFilter[] filters) - { - var request = new SearchRequest - { - Body = new SearchRequest.Types.Body - { - ContainerId = cid.ToGrpcMessage(), - Filters = { }, - Version = 1 - } - }; - - foreach (var filter in filters) - { - request.Body.Filters.Add(filter.ToGrpcMessage()); - } - - - request.AddMetaHeader(); - request.Sign(_key); - var objectsIds = SearchObjects(request); - - - await foreach (var oid in objectsIds) - { - yield return ObjectId.FromHash(oid.Value.ToByteArray()); - } - } - - private async Task GetObject(GetRequest request) - { - using var stream = GetObjectInit(request); - var obj = await stream.ReadHeader(); - var payload = new byte[obj.Header.PayloadLength]; - var offset = 0L; - var chunk = await stream.ReadChunk(); - - while (chunk is not null && (ulong)offset < obj.Header.PayloadLength) - { - var length = Math.Min((long)obj.Header.PayloadLength - offset, chunk.Length); - - Array.Copy(chunk, 0, payload, offset, length); - offset += chunk.Length; - chunk = await stream.ReadChunk(); - } - - obj.Payload = ByteString.CopyFrom(payload); - - return obj; - } - - private ObjectReader GetObjectInit(GetRequest initRequest) - { - if (initRequest is null) - throw new ArgumentNullException(nameof(initRequest)); - - - return new ObjectReader - { - Call = _objectServiceClient!.Get(initRequest) - }; - } - - private async Task PutObjectInit(PutRequest initRequest) - { - if (initRequest is null) - { - throw new ArgumentNullException(nameof(initRequest)); - } - - var call = _objectServiceClient!.Put(); - await call.RequestStream.WriteAsync(initRequest); - - return new ObjectStreamer(call); - } - - private async IAsyncEnumerable SearchObjects(SearchRequest request) - { - using var stream = GetSearchReader(request); - var ids = await stream.Read(); - while (ids is not null) - { - foreach (var oid in ids) - { - yield return oid; - } - - ids = await stream.Read(); - } - } - - private SearchReader GetSearchReader(SearchRequest initRequest) - { - if (initRequest is null) - { - throw new ArgumentNullException(nameof(initRequest)); - } - - return new SearchReader(_objectServiceClient!.Search(initRequest)); - } - - public Checksum Sha256Checksum(byte[] data) + private Checksum Sha256Checksum(byte[] data) { return new Checksum { @@ -450,8 +483,16 @@ public partial class Client Sum = ByteString.CopyFrom(data.Sha256()) }; } + + private async Task GetOrCreateSession(Context ctx) + { + if (string.IsNullOrEmpty(ctx.SessionToken)) + { + return await Context.InvokeAsyncWithMetrics(() => + Context.SessionService!.CreateSessionAsync(uint.MaxValue, ctx), + nameof(Context.SessionService.CreateSessionAsync)); + } + + return Convert.FromBase64String(ctx.SessionToken).DeserializeSessionToken(); + } } - - - - diff --git a/src/FrostFS.SDK.ClientV2/Services/SearchReader.cs b/src/FrostFS.SDK.ClientV2/Services/SearchReader.cs index c3d425a..6bc72df 100644 --- a/src/FrostFS.SDK.ClientV2/Services/SearchReader.cs +++ b/src/FrostFS.SDK.ClientV2/Services/SearchReader.cs @@ -7,6 +7,7 @@ using Grpc.Core; using FrostFS.Object; using FrostFS.Refs; +using System.Threading; namespace FrostFS.SDK.ClientV2; @@ -14,14 +15,13 @@ internal class SearchReader(AsyncServerStreamingCall call) : IDi { public AsyncServerStreamingCall Call { get; private set; } = call; - public async Task?> Read() + public async Task?> Read(CancellationToken cancellationToken) { - if (!await Call.ResponseStream.MoveNext()) - { + if (!await Call.ResponseStream.MoveNext(cancellationToken)) return null; - } - + var response = Call.ResponseStream.Current; + Verifier.CheckResponse(response); return response.Body?.IdList.ToList(); diff --git a/src/FrostFS.SDK.ClientV2/Services/Session.cs b/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs similarity index 50% rename from src/FrostFS.SDK.ClientV2/Services/Session.cs rename to src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs index 3dc52a1..c8c3c47 100644 --- a/src/FrostFS.SDK.ClientV2/Services/Session.cs +++ b/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs @@ -5,28 +5,38 @@ using FrostFS.Session; namespace FrostFS.SDK.ClientV2; -public partial class Client -{ - private async Task CreateSessionAsync(ulong expiration) +internal class SessionServiceProvider : ContextAccessor +{ + private readonly SessionService.SessionServiceClient? _sessionServiceClient; + + internal SessionServiceProvider(SessionService.SessionServiceClient? sessionServiceClient, ClientEnvironment context) + : base (context) + { + _sessionServiceClient = sessionServiceClient; + } + + internal async Task CreateSessionAsync(ulong expiration, Context ctx) { var request = new CreateRequest { Body = new CreateRequest.Types.Body { - OwnerId = OwnerId.ToGrpcMessage(), - Expiration = expiration, + OwnerId = Context.Owner.ToGrpcMessage(), + Expiration = expiration } }; request.AddMetaHeader(); - request.Sign(_key); + request.Sign(Context.Key); - return await CreateSession(request); + var token = await CreateSession(request, ctx); + + return token; } - private async Task CreateSession(CreateRequest request) + internal async Task CreateSession(CreateRequest request, Context ctx) { - var resp = await _sessionServiceClient.CreateAsync(request); + var resp = await _sessionServiceClient!.CreateAsync(request, null, ctx.Deadline, ctx.CancellationToken); return new SessionToken { diff --git a/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs b/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs new file mode 100644 index 0000000..54aeb51 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs @@ -0,0 +1,116 @@ +using System.Security.Cryptography; +using Grpc.Net.Client; +using FrostFS.SDK.ModelsV2; +using System; +using System.Threading.Tasks; +using Grpc.Core; +using System.Diagnostics; + +namespace FrostFS.SDK.ClientV2; + +public class ClientEnvironment(ECDsa key, OwnerId owner, GrpcChannel channel, ModelsV2.Version version) : IDisposable +{ + internal OwnerId Owner { get; } = owner; + internal GrpcChannel Channel { get; private set; } = channel; + internal ECDsa Key { get; } = key; + internal ModelsV2.Version Version { get; } = version; + internal NetworkSettings? NetworkSettings { get; set; } + + internal ContainerServiceProvider? ContainerService { get; set; } + internal NetmapServiceProvider? NetmapService { get; set; } + internal SessionServiceProvider? SessionService { get; set; } + internal ObjectServiceProvider? ObjectService { get; set; } + + internal event EventHandler? GrpcInvokedEvent; + + internal bool IsEventRequested => GrpcInvokedEvent != null; + + internal async Task InvokeAsyncWithMetrics(Func> func, string methodName) + { + if (!IsEventRequested) + return await func(); + + var watch = Stopwatch.StartNew(); + bool exitWithException = false; + try + { + return await func(); + } + catch + { + exitWithException = true; + throw; + } + finally + { + watch.Stop(); + GrpcInvokedEvent?.Invoke(this, new GrpcCallInfo(methodName, watch.ElapsedTicks * 1_000_000 / Stopwatch.Frequency, exitWithException)); + } + } + + internal T InvokeWithMetrics(Func func, string methodName) + { + if (!IsEventRequested) + return func(); + + var watch = Stopwatch.StartNew(); + bool exitWithException = false; + try + { + return func(); + } + catch + { + exitWithException = true; + throw; + } + finally + { + watch.Stop(); + GrpcInvokedEvent?.Invoke(this, new GrpcCallInfo(methodName, watch.ElapsedTicks * 1_000_000 / Stopwatch.Frequency, exitWithException)); + } + } + + internal async Task InvokeAsyncUnaryWithMetrics(Func> func, string methodName) + { + if (!IsEventRequested) + return await func(); + + var watch = Stopwatch.StartNew(); + bool exitWithException = false; + try + { + return await func(); + } + catch + { + exitWithException = true; + throw; + } + finally + { + watch.Stop(); + GrpcInvokedEvent?.Invoke(this, new GrpcCallInfo(methodName, watch.ElapsedTicks * 1_000_000 / Stopwatch.Frequency, exitWithException)); + } + } + + internal bool Initialized => + ContainerService != null + && NetmapService != null + && SessionService != null + && ObjectService != null; + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + Channel.Dispose(); + } + } +} diff --git a/src/FrostFS.SDK.ClientV2/Tools/ContextAccessor.cs b/src/FrostFS.SDK.ClientV2/Tools/ContextAccessor.cs new file mode 100644 index 0000000..75edcc6 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Tools/ContextAccessor.cs @@ -0,0 +1,8 @@ +namespace FrostFS.SDK.ClientV2; + +internal class ContextAccessor(ClientEnvironment context) +{ + protected ClientEnvironment Context { get; set; } = context; +} + + diff --git a/src/FrostFS.SDK.ClientV2/Tools/NetworkSettings.cs b/src/FrostFS.SDK.ClientV2/Tools/NetworkSettings.cs new file mode 100644 index 0000000..03067c6 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Tools/NetworkSettings.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; + +namespace FrostFS.SDK.ClientV2; + +public class NetworkSettings + { + public ulong ContainerFee { get; internal set; } + public ulong ContainerAliasFee { get; internal set; } + public ulong InnerRingCandidateFee { get; internal set; } + public ulong WithdrawFee { get; internal set; } + public ulong EpochDuration { get; internal set; } + public ulong IRCandidateFee { get; internal set; } + public ulong MaxObjectSize { get; internal set; } + public ulong MaxECDataCount { get; internal set; } + public ulong MaxECParityCount { get; internal set; } + public ulong WithdrawalFee { get; internal set; } + public bool HomomorphicHashingDisabled { get; internal set; } + public bool MaintenanceModeAllowed { get; internal set; } + + public Dictionary UnnamedSettings { get; } = []; + } \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Extensions/Object.cs b/src/FrostFS.SDK.ClientV2/Tools/Object.cs similarity index 76% rename from src/FrostFS.SDK.ClientV2/Extensions/Object.cs rename to src/FrostFS.SDK.ClientV2/Tools/Object.cs index 3765e71..e7e59f1 100644 --- a/src/FrostFS.SDK.ClientV2/Extensions/Object.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/Object.cs @@ -1,10 +1,11 @@ +using System; using System.Collections.Generic; using FrostFS.SDK.ModelsV2; namespace FrostFS.SDK.ClientV2.Extensions; -public static class Extensions +public static class ObjectExtensions { public static ModelsV2.Object SetPayloadLength(this ModelsV2.Object obj, ulong length) { @@ -41,4 +42,15 @@ public static class Extensions linkObject.Header.Split!.Children.AddRange(objectIds); return linkObject; } + + public static ModelsV2.Object CalculateObjectId(this ModelsV2.Object obj) + { + if (obj.Payload == null) + throw new MissingFieldException("Payload cannot be null"); + + if (obj.Header == null) + throw new MissingFieldException("Header cannot be null"); + + return obj; + } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Range.cs b/src/FrostFS.SDK.ClientV2/Tools/Range.cs similarity index 91% rename from src/FrostFS.SDK.ClientV2/Range.cs rename to src/FrostFS.SDK.ClientV2/Tools/Range.cs index 8158314..9c989c1 100644 --- a/src/FrostFS.SDK.ClientV2/Range.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/Range.cs @@ -41,10 +41,10 @@ namespace System } /// Create an Index pointing at first element. - public static Index Start => new Index(0); + public static Index Start => new(0); /// Create an Index pointing at beyond last element. - public static Index End => new Index(~0); + public static Index End => new(~0); /// Create an Index from the start at the position indicated by the value. /// The index value from the start. @@ -116,7 +116,7 @@ namespace System /// Indicates whether the current Index object is equal to another object of the same type. /// An object to compare with this object - public override bool Equals(object? value) => value is Index && _value == ((Index)value)._value; + public override bool Equals(object? value) => value is Index index && _value == index._value; /// Indicates whether the current Index object is equal to another Index object. /// An object to compare with this object @@ -147,22 +147,16 @@ namespace System /// int[] subArray2 = someArray[1..^0]; // { 2, 3, 4, 5 } /// /// - internal readonly struct Range : IEquatable + /// Construct a Range object using the start and end indexes. + /// Represent the inclusive start index of the range. + /// Represent the exclusive end index of the range. + internal readonly struct Range(Index start, Index end) : IEquatable { /// Represent the inclusive start index of the Range. - public Index Start { get; } + public Index Start { get; } = start; /// Represent the exclusive end index of the Range. - public Index End { get; } - - /// Construct a Range object using the start and end indexes. - /// Represent the inclusive start index of the range. - /// Represent the exclusive end index of the range. - public Range(Index start, Index end) - { - Start = start; - End = end; - } + public Index End { get; } = end; /// Indicates whether the current Range object is equal to another object of the same type. /// An object to compare with this object @@ -188,13 +182,13 @@ namespace System } /// Create a Range object starting from start index to the end of the collection. - public static Range StartAt(Index start) => new Range(start, Index.End); + public static Range StartAt(Index start) => new(start, Index.End); /// Create a Range object starting from first element in the collection to the end Index. - public static Range EndAt(Index end) => new Range(Index.Start, end); + public static Range EndAt(Index end) => new(Index.Start, end); /// Create a Range object starting from first element to the end. - public static Range All => new Range(Index.Start, Index.End); + public static Range All => new(Index.Start, Index.End); /// Calculate the start offset and length of range object using a collection length. /// The length of the collection that the range will be used with. length has to be a positive value. @@ -252,7 +246,7 @@ namespace System.Runtime.CompilerServices if (length == 0) { - return Array.Empty(); + return []; } var dest = new T[length]; diff --git a/src/FrostFS.SDK.ClientV2/RequestConstructor.cs b/src/FrostFS.SDK.ClientV2/Tools/RequestConstructor.cs similarity index 84% rename from src/FrostFS.SDK.ClientV2/RequestConstructor.cs rename to src/FrostFS.SDK.ClientV2/Tools/RequestConstructor.cs index fb632ee..17865e2 100644 --- a/src/FrostFS.SDK.ClientV2/RequestConstructor.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/RequestConstructor.cs @@ -10,20 +10,24 @@ public static class RequestConstructor { public static void AddMetaHeader(this IRequest request, RequestMetaHeader? metaHeader = null) { - if (request.MetaHeader is not null) return; + if (request.MetaHeader is not null) + return; + metaHeader ??= MetaHeader.Default().ToGrpcMessage(); request.MetaHeader = metaHeader; } public static void AddObjectSessionToken( this IRequest request, - SessionToken sessionToken, + Session.SessionToken sessionToken, ContainerID cid, ObjectID oid, ObjectSessionContext.Types.Verb verb, ECDsa key) { - if (request.MetaHeader.SessionToken is not null) return; + if (request.MetaHeader.SessionToken is not null) + return; + request.MetaHeader.SessionToken = sessionToken; var ctx = new ObjectSessionContext { @@ -34,6 +38,7 @@ public static class RequestConstructor }, Verb = verb }; + request.MetaHeader.SessionToken.Body.Object = ctx; request.MetaHeader.SessionToken.Signature = key.SignMessagePart(request.MetaHeader.SessionToken.Body); } diff --git a/src/FrostFS.SDK.ClientV2/RequestSigner.cs b/src/FrostFS.SDK.ClientV2/Tools/RequestSigner.cs similarity index 95% rename from src/FrostFS.SDK.ClientV2/RequestSigner.cs rename to src/FrostFS.SDK.ClientV2/Tools/RequestSigner.cs index 23f8eb6..6016ddf 100644 --- a/src/FrostFS.SDK.ClientV2/RequestSigner.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/RequestSigner.cs @@ -84,7 +84,7 @@ public static class RequestSigner return sig; } - public static void Sign(this IVerificableMessage message, ECDsa key) + public static void Sign(this IVerifiableMessage message, ECDsa key) { var meta = message.GetMetaHeader(); IVerificationHeader verify = message switch @@ -95,12 +95,15 @@ public static class RequestSigner }; var verifyOrigin = message.GetVerificationHeader(); + if (verifyOrigin is null) verify.BodySignature = key.SignMessagePart(message.GetBody()); - + else + verify.SetOrigin(verifyOrigin); + verify.MetaSignature = key.SignMessagePart(meta); verify.OriginSignature = key.SignMessagePart(verifyOrigin); - verify.SetOrigin(verifyOrigin); + message.SetVerificationHeader(verify); } } diff --git a/src/FrostFS.SDK.ClientV2/Verifier.cs b/src/FrostFS.SDK.ClientV2/Tools/Verifier.cs similarity index 86% rename from src/FrostFS.SDK.ClientV2/Verifier.cs rename to src/FrostFS.SDK.ClientV2/Tools/Verifier.cs index ad6b3e6..5811ff1 100644 --- a/src/FrostFS.SDK.ClientV2/Verifier.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/Verifier.cs @@ -68,7 +68,7 @@ public static class Verifier return false; using var key = sig.Key.ToByteArray().LoadPublicKey(); - var data2Verify = data is null ? Array.Empty() : data.ToByteArray(); + var data2Verify = data is null ? [] : data.ToByteArray(); return key.VerifyData(data2Verify, sig.Sign.ToByteArray()); } @@ -89,7 +89,7 @@ public static class Verifier return verification.BodySignature is null && VerifyMatryoskaLevel(body, meta.GetOrigin(), origin); } - public static bool Verify(this IVerificableMessage message) + public static bool Verify(this IVerifiableMessage message) { return VerifyMatryoskaLevel(message.GetBody(), message.GetMetaHeader(), message.GetVerificationHeader()); } @@ -100,7 +100,17 @@ public static class Verifier throw new FormatException($"invalid response, type={resp.GetType()}"); var status = resp.MetaHeader.Status.ToModel(); - if (!status.IsSuccess()) + if (!status.IsSuccess) throw new ApplicationException(status.ToString()); } + + /// + /// This method is intended for unit tests for request verification. + /// + /// Created by SDK request to gRpc proxy + public static void CheckRequest(IRequest request) + { + if (!request.Verify()) + throw new FormatException($"invalid response, type={request.GetType()}"); + } } \ No newline at end of file diff --git a/src/FrostFS.SDK.Cryptography/AssemblyInfo.cs b/src/FrostFS.SDK.Cryptography/AssemblyInfo.cs index a593c3a..a75808b 100644 --- a/src/FrostFS.SDK.Cryptography/AssemblyInfo.cs +++ b/src/FrostFS.SDK.Cryptography/AssemblyInfo.cs @@ -17,4 +17,4 @@ using System.Runtime.InteropServices; [assembly: Guid("08a8487e-39ce-41fb-9c24-13f73ff2bde0")] -[assembly: InternalsVisibleToAttribute("FrostFS.SDK.Cryptography.Test")] \ No newline at end of file +[assembly: InternalsVisibleTo("FrostFS.SDK.Cryptography.Test")] \ No newline at end of file diff --git a/src/FrostFS.SDK.Cryptography/Base58.cs b/src/FrostFS.SDK.Cryptography/Base58.cs index d9d91cf..f33794a 100644 --- a/src/FrostFS.SDK.Cryptography/Base58.cs +++ b/src/FrostFS.SDK.Cryptography/Base58.cs @@ -34,9 +34,11 @@ public static class Base58 byte[] checksum = data.ToArray().Sha256().Sha256(); Span buffer = stackalloc byte[data.Length + 4]; data.CopyTo(buffer); + checksum[..4].AsSpan().CopyTo(buffer[data.Length..]); var ret = Encode(buffer); buffer.Clear(); + return ret; } diff --git a/src/FrostFS.SDK.Cryptography/Helper.cs b/src/FrostFS.SDK.Cryptography/Extentions.cs similarity index 97% rename from src/FrostFS.SDK.Cryptography/Helper.cs rename to src/FrostFS.SDK.Cryptography/Extentions.cs index 198dcb5..c27f5ef 100644 --- a/src/FrostFS.SDK.Cryptography/Helper.cs +++ b/src/FrostFS.SDK.Cryptography/Extentions.cs @@ -6,7 +6,7 @@ using System.Security.Cryptography; namespace FrostFS.SDK.Cryptography; -public static class Helper +public static class Extentions { internal static byte[] RIPEMD160(this byte[] value) { diff --git a/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj b/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj index aa2afc9..e276848 100644 --- a/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj +++ b/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj @@ -6,6 +6,10 @@ enable + + true + + diff --git a/src/FrostFS.SDK.Cryptography/Key.cs b/src/FrostFS.SDK.Cryptography/Key.cs index 56f71cb..de96791 100644 --- a/src/FrostFS.SDK.Cryptography/Key.cs +++ b/src/FrostFS.SDK.Cryptography/Key.cs @@ -56,7 +56,7 @@ public static class KeyExtension var script = new byte[] { 0x0c, CompressedPublicKeyLength }; //PUSHDATA1 33 script = ArrayHelper.Concat(script, publicKey); - script = ArrayHelper.Concat(script, new byte[] { 0x41 }); //SYSCALL + script = ArrayHelper.Concat(script, [0x41]); //SYSCALL script = ArrayHelper.Concat(script, BitConverter.GetBytes(CheckSigDescriptor)); //Neo_Crypto_CheckSig return script; @@ -80,7 +80,7 @@ public static class KeyExtension private static byte[] GetPrivateKeyFromWIF(string wif) { if (wif == null) - throw new ArgumentNullException(); + throw new ArgumentNullException(nameof(wif)); var data = wif.Base58CheckDecode(); @@ -117,7 +117,7 @@ public static class KeyExtension var pos = 33 - param.Q.X.Length; param.Q.X.CopyTo(pubkey, pos); - if (new BigInteger(param.Q.Y.Reverse().Concat(new byte[] { 0x00 }).ToArray()).IsEven) + if (new BigInteger(param.Q.Y.Reverse().Concat(new byte[] { 0x0 }).ToArray()).IsEven) pubkey[0] = 0x2; else pubkey[0] = 0x3; diff --git a/src/FrostFS.SDK.Cryptography/Range.cs b/src/FrostFS.SDK.Cryptography/Range.cs index 8158314..7313a64 100644 --- a/src/FrostFS.SDK.Cryptography/Range.cs +++ b/src/FrostFS.SDK.Cryptography/Range.cs @@ -41,10 +41,10 @@ namespace System } /// Create an Index pointing at first element. - public static Index Start => new Index(0); + public static Index Start => new(0); /// Create an Index pointing at beyond last element. - public static Index End => new Index(~0); + public static Index End => new(~0); /// Create an Index from the start at the position indicated by the value. /// The index value from the start. @@ -116,7 +116,7 @@ namespace System /// Indicates whether the current Index object is equal to another object of the same type. /// An object to compare with this object - public override bool Equals(object? value) => value is Index && _value == ((Index)value)._value; + public override bool Equals(object? value) => value is Index index && _value == index._value; /// Indicates whether the current Index object is equal to another Index object. /// An object to compare with this object @@ -147,22 +147,16 @@ namespace System /// int[] subArray2 = someArray[1..^0]; // { 2, 3, 4, 5 } /// /// - internal readonly struct Range : IEquatable + /// Construct a Range object using the start and end indexes. + /// Represent the inclusive start index of the range. + /// Represent the exclusive end index of the range. + internal readonly struct Range (Index start, Index end) : IEquatable { /// Represent the inclusive start index of the Range. - public Index Start { get; } + public Index Start { get; } = start; /// Represent the exclusive end index of the Range. - public Index End { get; } - - /// Construct a Range object using the start and end indexes. - /// Represent the inclusive start index of the range. - /// Represent the exclusive end index of the range. - public Range(Index start, Index end) - { - Start = start; - End = end; - } + public Index End { get; } = end; /// Indicates whether the current Range object is equal to another object of the same type. /// An object to compare with this object @@ -188,13 +182,13 @@ namespace System } /// Create a Range object starting from start index to the end of the collection. - public static Range StartAt(Index start) => new Range(start, Index.End); + public static Range StartAt(Index start) => new(start, Index.End); /// Create a Range object starting from first element in the collection to the end Index. - public static Range EndAt(Index end) => new Range(Index.Start, end); + public static Range EndAt(Index end) => new(Index.Start, end); /// Create a Range object starting from first element to the end. - public static Range All => new Range(Index.Start, Index.End); + public static Range All => new(Index.Start, Index.End); /// Calculate the start offset and length of range object using a collection length. /// The length of the collection that the range will be used with. length has to be a positive value. @@ -252,7 +246,7 @@ namespace System.Runtime.CompilerServices if (length == 0) { - return Array.Empty(); + return []; } var dest = new T[length]; diff --git a/src/FrostFS.SDK.ModelsV2/ClientSettings.cs b/src/FrostFS.SDK.ModelsV2/ClientSettings.cs new file mode 100644 index 0000000..4268d53 --- /dev/null +++ b/src/FrostFS.SDK.ModelsV2/ClientSettings.cs @@ -0,0 +1,28 @@ +using System; +using System.Text; + +namespace FrostFS.SDK.ModelsV2; + +public class ClientSettings +{ + private static readonly string errorTemplate = "{0} is required parameter"; + + public string Key { get; set; } = string.Empty; + + public string Host { get; set; } = string.Empty; + + public void Validate() + { + StringBuilder? error = null; + + if (string.IsNullOrWhiteSpace(Key)) + (error ??= new StringBuilder()).AppendLine(string.Format(errorTemplate, nameof(Key))); + + if (string.IsNullOrWhiteSpace(Host)) + (error ??= new StringBuilder()).AppendLine(string.Format(errorTemplate, nameof(Host))); + + if (error != null) + throw new ArgumentException(error.ToString()); + } + +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Container.cs b/src/FrostFS.SDK.ModelsV2/Container.cs index d32ff8f..4533325 100644 --- a/src/FrostFS.SDK.ModelsV2/Container.cs +++ b/src/FrostFS.SDK.ModelsV2/Container.cs @@ -5,17 +5,10 @@ using FrostFS.SDK.ModelsV2.Netmap; namespace FrostFS.SDK.ModelsV2; -public class Container +public class Container(BasicAcl basicAcl, PlacementPolicy placementPolicy) { - public Guid Nonce { get; set; } - public BasicAcl BasicAcl { get; set; } - public PlacementPolicy PlacementPolicy { get; set; } - public Version Version { get; set; } - - public Container(BasicAcl basicAcl, PlacementPolicy placementPolicy) - { - Nonce = Guid.NewGuid(); - BasicAcl = basicAcl; - PlacementPolicy = placementPolicy; - } + public Guid Nonce { get; set; } = Guid.NewGuid(); + public BasicAcl BasicAcl { get; set; } = basicAcl; + public PlacementPolicy PlacementPolicy { get; set; } = placementPolicy; + public Version? Version { get; set; } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Context.cs b/src/FrostFS.SDK.ModelsV2/Context.cs new file mode 100644 index 0000000..302dafc --- /dev/null +++ b/src/FrostFS.SDK.ModelsV2/Context.cs @@ -0,0 +1,13 @@ +using System; +using System.Threading; + +namespace FrostFS.SDK.ClientV2; + +public class Context() +{ + public CancellationToken CancellationToken { get; set; } = default; + public TimeSpan Timeout { get; set; } = default; + public string SessionToken { get; set; } = string.Empty; + + public DateTime? Deadline => Timeout.Ticks > 0 ? DateTime.UtcNow.Add(Timeout) : null; +} diff --git a/src/FrostFS.SDK.ModelsV2/Enums/BasicAcl.cs b/src/FrostFS.SDK.ModelsV2/Enums/BasicAcl.cs index 5c222fc..9418039 100644 --- a/src/FrostFS.SDK.ModelsV2/Enums/BasicAcl.cs +++ b/src/FrostFS.SDK.ModelsV2/Enums/BasicAcl.cs @@ -3,7 +3,10 @@ using System.ComponentModel; namespace FrostFS.SDK.ModelsV2.Enums; public enum BasicAcl -{ +{ + [Description("Not defined ACL")] + NotDefined = 0x00000000, + [Description("Basic ACL for private container")] Private = 0x1C8C8CCC, diff --git a/src/FrostFS.SDK.ModelsV2/FrostFS.SDK.ModelsV2.csproj b/src/FrostFS.SDK.ModelsV2/FrostFS.SDK.ModelsV2.csproj index 96a477c..0f47676 100644 --- a/src/FrostFS.SDK.ModelsV2/FrostFS.SDK.ModelsV2.csproj +++ b/src/FrostFS.SDK.ModelsV2/FrostFS.SDK.ModelsV2.csproj @@ -6,6 +6,10 @@ enable + + true + + diff --git a/src/FrostFS.SDK.ModelsV2/GrpcCallInfo.cs b/src/FrostFS.SDK.ModelsV2/GrpcCallInfo.cs new file mode 100644 index 0000000..71ac276 --- /dev/null +++ b/src/FrostFS.SDK.ModelsV2/GrpcCallInfo.cs @@ -0,0 +1,8 @@ +namespace FrostFS.SDK.ModelsV2; + +public class GrpcCallInfo(string methodName, long elapsedMicroSec, bool hasError) +{ + public string MethodName { get; set; } = methodName; + public long ElapsedTimeMicroSec { get; set; } = elapsedMicroSec; + public bool HasError { get; } = hasError; +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/MetaHeader.cs b/src/FrostFS.SDK.ModelsV2/MetaHeader.cs index 68abbb3..d2b84a2 100644 --- a/src/FrostFS.SDK.ModelsV2/MetaHeader.cs +++ b/src/FrostFS.SDK.ModelsV2/MetaHeader.cs @@ -1,17 +1,10 @@ namespace FrostFS.SDK.ModelsV2; -public class MetaHeader +public class MetaHeader(Version version, int epoch, int ttl) { - public Version Version { get; set; } - public int Epoch { get; set; } - public int Ttl { get; set; } - - public MetaHeader(Version version, int epoch, int ttl) - { - Version = version; - Epoch = epoch; - Ttl = ttl; - } + public Version Version { get; set; } = version; + public int Epoch { get; set; } = epoch; + public int Ttl { get; set; } = ttl; public static MetaHeader Default() { diff --git a/src/FrostFS.SDK.ModelsV2/Netmap/NetmapInfo.cs b/src/FrostFS.SDK.ModelsV2/Netmap/NetmapInfo.cs new file mode 100644 index 0000000..fb8dba1 --- /dev/null +++ b/src/FrostFS.SDK.ModelsV2/Netmap/NetmapInfo.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace FrostFS.SDK.ModelsV2.Netmap; + +public class NetmapSnapshot(ulong epoch, IReadOnlyList nodeInfoCollection) +{ + public ulong Epoch { get; private set; } = epoch; + + public IReadOnlyList NodeInfoCollection { get; private set; } = nodeInfoCollection; +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Netmap/NodeInfo.cs b/src/FrostFS.SDK.ModelsV2/Netmap/NodeInfo.cs index d67d987..9433c04 100644 --- a/src/FrostFS.SDK.ModelsV2/Netmap/NodeInfo.cs +++ b/src/FrostFS.SDK.ModelsV2/Netmap/NodeInfo.cs @@ -1,9 +1,28 @@ +using System; +using System.Collections.Generic; using FrostFS.SDK.ModelsV2.Enums; namespace FrostFS.SDK.ModelsV2.Netmap; public class NodeInfo { - public NodeState State { get; set; } - public Version? Version { get; set; } + public NodeInfo( + Version version, + NodeState state, + IReadOnlyCollection addresses, + IReadOnlyDictionary attributes, + ReadOnlyMemory publicKey) + { + Version = version; + State = state; + Addresses = addresses; + Attributes = attributes; + PublicKey = publicKey; + } + + public NodeState State { get; private set; } + public Version Version { get; private set; } + public IReadOnlyCollection Addresses { get; private set; } + public IReadOnlyDictionary Attributes { get; private set; } + public ReadOnlyMemory PublicKey { get; private set; } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Netmap/PlacementPolicy.cs b/src/FrostFS.SDK.ModelsV2/Netmap/PlacementPolicy.cs index b638bb6..bb7b1a3 100644 --- a/src/FrostFS.SDK.ModelsV2/Netmap/PlacementPolicy.cs +++ b/src/FrostFS.SDK.ModelsV2/Netmap/PlacementPolicy.cs @@ -1,13 +1,28 @@ +using System; +using System.Linq; + namespace FrostFS.SDK.ModelsV2.Netmap; -public class PlacementPolicy +public class PlacementPolicy(bool unique, params Replica[] replicas) : IComparable { - public Replica[] Replicas { get; private set; } - public bool Unique { get; private set; } + public Replica[] Replicas { get; private set; } = replicas; + public bool Unique { get; private set; } = unique; - public PlacementPolicy(bool unique, params Replica[] replicas) + public int CompareTo(PlacementPolicy other) { - Replicas = replicas; - Unique = unique; + var notEqual = other == null + || Unique != other.Unique + || Replicas.Length != other.Replicas.Length; + + if (notEqual) + return 1; + + foreach (var replica in Replicas) + { + if (!other!.Replicas.Any(r => r.Count == replica.Count && r.Selector == replica.Selector)) + return 1; + } + + return 0; } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Object/Object.cs b/src/FrostFS.SDK.ModelsV2/Object/Object.cs index b4236b1..51c4630 100644 --- a/src/FrostFS.SDK.ModelsV2/Object/Object.cs +++ b/src/FrostFS.SDK.ModelsV2/Object/Object.cs @@ -1,3 +1,4 @@ +using System; using System.Security.Cryptography; using FrostFS.SDK.ModelsV2.Enums; @@ -5,8 +6,11 @@ namespace FrostFS.SDK.ModelsV2; public class Object { - public Object() + public Object(ObjectId objectId, ObjectHeader header, byte[] payload) { + ObjectId = objectId; + Payload = payload; + Header = header; } public Object(ContainerId container, byte[] payload, ObjectType objectType = ObjectType.Regular) @@ -14,43 +18,47 @@ public class Object Payload = payload; Header = new ObjectHeader(containerId: container, type: objectType, attributes: []); } - + public ObjectHeader Header { get; set; } - public ObjectId ObjectId { get; set; } + + public ObjectId? ObjectId + { + get; internal set; + } + public byte[] Payload { get; set; } - public Signature Signature { get; set; } + + public Signature? Signature { get; set; } public void SetParent(LargeObject largeObject) { - Header.Split.ParentHeader = largeObject.Header; - } + if (Header?.Split == null) + throw new Exception("The object is not initialized properly"); - public ObjectId? GetParentId() - { - return Header.Split?.Parent; + Header.Split.ParentHeader = largeObject.Header; } } public class LargeObject(ContainerId container) : Object(container, []) { - private SHA256 payloadHash = SHA256.Create(); + private readonly SHA256 payloadHash = SHA256.Create(); public void AppendBlock(byte[] bytes, int count) { - Header.PayloadLength += (ulong)count; + Header!.PayloadLength += (ulong)count; this.payloadHash.TransformBlock(bytes, 0, count, bytes, 0); } public LargeObject CalculateHash() { this.payloadHash.TransformFinalBlock([], 0, 0); - Header.PayloadCheckSum = this.payloadHash.Hash; + Header!.PayloadCheckSum = this.payloadHash.Hash; return this; } public ulong PayloadLength { - get { return Header.PayloadLength; } + get { return Header!.PayloadLength; } } } @@ -58,7 +66,7 @@ public class LinkObject : Object { public LinkObject(ContainerId containerId, SplitId splitId, LargeObject largeObject) : base (containerId, []) { - Header.Split = new Split(splitId) + Header!.Split = new Split(splitId) { ParentHeader = largeObject.Header }; diff --git a/src/FrostFS.SDK.ModelsV2/Object/ObjectHeader.cs b/src/FrostFS.SDK.ModelsV2/Object/ObjectHeader.cs index db9b867..83bee87 100644 --- a/src/FrostFS.SDK.ModelsV2/Object/ObjectHeader.cs +++ b/src/FrostFS.SDK.ModelsV2/Object/ObjectHeader.cs @@ -22,8 +22,6 @@ public class ObjectHeader public Split? Split { get; set; } - public bool ClientCut { get; set; } - public ObjectHeader( ContainerId containerId, ObjectType type = ObjectType.Regular, diff --git a/src/FrostFS.SDK.ModelsV2/ObjectId.cs b/src/FrostFS.SDK.ModelsV2/Object/ObjectId.cs similarity index 100% rename from src/FrostFS.SDK.ModelsV2/ObjectId.cs rename to src/FrostFS.SDK.ModelsV2/Object/ObjectId.cs diff --git a/src/FrostFS.SDK.ModelsV2/OwnerId.cs b/src/FrostFS.SDK.ModelsV2/OwnerId.cs index 85cf239..8746572 100644 --- a/src/FrostFS.SDK.ModelsV2/OwnerId.cs +++ b/src/FrostFS.SDK.ModelsV2/OwnerId.cs @@ -4,14 +4,9 @@ using FrostFS.SDK.Cryptography; namespace FrostFS.SDK.ModelsV2; -public class OwnerId +public class OwnerId(string id) { - public string Value { get; } - - public OwnerId(string id) - { - Value = id; - } + public string Value { get; } = id; public static OwnerId FromKey(ECDsa key) { diff --git a/src/FrostFS.SDK.ModelsV2/PutObjectParameters.cs b/src/FrostFS.SDK.ModelsV2/PutObjectParameters.cs new file mode 100644 index 0000000..23cbc38 --- /dev/null +++ b/src/FrostFS.SDK.ModelsV2/PutObjectParameters.cs @@ -0,0 +1,12 @@ +using System.IO; + +namespace FrostFS.SDK.ModelsV2; + +public class PutObjectParameters +{ + public ObjectHeader? Header { get; set; } + + public Stream? Payload { get; set; } + + public bool ClientCut { get; set; } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/SessionToken.cs b/src/FrostFS.SDK.ModelsV2/SessionToken.cs new file mode 100644 index 0000000..6fde99e --- /dev/null +++ b/src/FrostFS.SDK.ModelsV2/SessionToken.cs @@ -0,0 +1,8 @@ +namespace FrostFS.SDK.ModelsV2; + +public class SessionToken(byte[] sessionKey, byte[] id) +{ + public byte[] Id { get; } = id; + + public byte[] SessionKey { get; } = sessionKey; +} diff --git a/src/FrostFS.SDK.ModelsV2/Signature.cs b/src/FrostFS.SDK.ModelsV2/Signature.cs new file mode 100644 index 0000000..1d36e1c --- /dev/null +++ b/src/FrostFS.SDK.ModelsV2/Signature.cs @@ -0,0 +1,8 @@ +namespace FrostFS.SDK.ModelsV2; + +public class Signature +{ + public byte[]? Key { get; set; } + public byte[]? Sign { get; set; } + public SignatureScheme Scheme { get; set; } +} diff --git a/src/FrostFS.SDK.ModelsV2/SignatureScheme.cs b/src/FrostFS.SDK.ModelsV2/SignatureScheme.cs new file mode 100644 index 0000000..bd6c41d --- /dev/null +++ b/src/FrostFS.SDK.ModelsV2/SignatureScheme.cs @@ -0,0 +1,7 @@ +namespace FrostFS.SDK.ModelsV2; + +public enum SignatureScheme { + EcdsaSha512, + EcdsaRfc6979Sha256, + EcdsaRfc6979Sha256WalletConnect + } diff --git a/src/FrostFS.SDK.ModelsV2/SplitId.cs b/src/FrostFS.SDK.ModelsV2/SplitId.cs new file mode 100644 index 0000000..908c143 --- /dev/null +++ b/src/FrostFS.SDK.ModelsV2/SplitId.cs @@ -0,0 +1,50 @@ +using System; + +namespace FrostFS.SDK.ModelsV2; + +public class SplitId +{ + private Guid id; + + public SplitId() + { + this.id = Guid.NewGuid(); + } + public SplitId(Guid guid) + { + this.id = guid; + } + + private SplitId(byte[] binary) + { + this.id = new Guid(binary); + } + + private SplitId(string str) + { + this.id = new Guid(str); + } + + public static SplitId CrateFromBinary(byte[] binaryData) + { + return new SplitId(binaryData); + } + + public static SplitId CrateFromString(string stringData) + { + return new SplitId(stringData); + } + + public override string ToString() + { + return this.id.ToString(); + } + + public byte[]? ToBinary() + { + if (this.id == Guid.Empty) + return null; + + return this.id.ToByteArray(); + } +} diff --git a/src/FrostFS.SDK.ModelsV2/Splitter.cs b/src/FrostFS.SDK.ModelsV2/Splitter.cs index f48f903..d4622df 100644 --- a/src/FrostFS.SDK.ModelsV2/Splitter.cs +++ b/src/FrostFS.SDK.ModelsV2/Splitter.cs @@ -1,20 +1,14 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; namespace FrostFS.SDK.ModelsV2; -public class Split +public class Split(SplitId splitId) { public Split() : this(new SplitId()) { } - public Split(SplitId splitId) - { - SplitId = splitId; - } - - public SplitId SplitId { get; private set; } + public SplitId SplitId { get; private set; } = splitId; public ObjectId? Parent { get; set; } @@ -26,63 +20,3 @@ public class Split public List Children { get; } = []; } - - public enum SignatureScheme { - EcdsaSha512, - EcdsaRfc6979Sha256, - EcdsaRfc6979Sha256WalletConnect - } - -public class Signature -{ - public byte[] Key { get; set; } - public byte[] Sign { get; set; } - public SignatureScheme Scheme { get; set; } -} - -public class SplitId -{ - private Guid id; - - public SplitId() - { - this.id = Guid.NewGuid(); - } - public SplitId(Guid guid) - { - this.id = guid; - } - - private SplitId(byte[] binary) - { - this.id = new Guid(binary); - } - - private SplitId(string str) - { - this.id = new Guid(str); - } - - public static SplitId CrateFromBinary(byte[] binaryData) - { - return new SplitId(binaryData); - } - - public static SplitId CrateFromString(string stringData) - { - return new SplitId(stringData); - } - - public override string ToString() - { - return this.id.ToString(); - } - - public byte[]? ToBinary() - { - if (this.id == Guid.Empty) - return null; - - return this.id.ToByteArray(); - } -} diff --git a/src/FrostFS.SDK.ModelsV2/Status.cs b/src/FrostFS.SDK.ModelsV2/Status.cs index 2d56441..d52d189 100644 --- a/src/FrostFS.SDK.ModelsV2/Status.cs +++ b/src/FrostFS.SDK.ModelsV2/Status.cs @@ -2,21 +2,12 @@ using FrostFS.SDK.ModelsV2.Enums; namespace FrostFS.SDK.ModelsV2; -public class Status +public class Status(StatusCode code, string? message = null) { - public StatusCode Code { get; set; } - public string Message { get; set; } + public StatusCode Code { get; set; } = code; + public string Message { get; set; } = message ?? string.Empty; - public Status(StatusCode code, string? message = null) - { - Code = code; - Message = message ?? string.Empty; - } - - public bool IsSuccess() - { - return Code == StatusCode.Success; - } + public bool IsSuccess => Code == StatusCode.Success; public override string ToString() { diff --git a/src/FrostFS.SDK.ProtosV2/FrostFS.SDK.ProtosV2.csproj b/src/FrostFS.SDK.ProtosV2/FrostFS.SDK.ProtosV2.csproj index 746a8b4..0a559ed 100644 --- a/src/FrostFS.SDK.ProtosV2/FrostFS.SDK.ProtosV2.csproj +++ b/src/FrostFS.SDK.ProtosV2/FrostFS.SDK.ProtosV2.csproj @@ -6,6 +6,10 @@ enable + + true + + diff --git a/src/FrostFS.SDK.ProtosV2/Interfaces/IRequest.cs b/src/FrostFS.SDK.ProtosV2/Interfaces/IRequest.cs index f75db43..7efad42 100644 --- a/src/FrostFS.SDK.ProtosV2/Interfaces/IRequest.cs +++ b/src/FrostFS.SDK.ProtosV2/Interfaces/IRequest.cs @@ -1,6 +1,6 @@ namespace FrostFS.Session; -public interface IRequest : IVerificableMessage +public interface IRequest : IVerifiableMessage { RequestMetaHeader MetaHeader { get; set; } RequestVerificationHeader VerifyHeader { get; set; } diff --git a/src/FrostFS.SDK.ProtosV2/Interfaces/IResponse.cs b/src/FrostFS.SDK.ProtosV2/Interfaces/IResponse.cs index 609be8e..ac6382a 100644 --- a/src/FrostFS.SDK.ProtosV2/Interfaces/IResponse.cs +++ b/src/FrostFS.SDK.ProtosV2/Interfaces/IResponse.cs @@ -1,6 +1,6 @@ namespace FrostFS.Session; -public interface IResponse : IVerificableMessage +public interface IResponse : IVerifiableMessage { ResponseMetaHeader MetaHeader { get; set; } ResponseVerificationHeader VerifyHeader { get; set; } diff --git a/src/FrostFS.SDK.ProtosV2/Interfaces/IVerifiableMessage.cs b/src/FrostFS.SDK.ProtosV2/Interfaces/IVerifiableMessage.cs index 3903757..57fce64 100644 --- a/src/FrostFS.SDK.ProtosV2/Interfaces/IVerifiableMessage.cs +++ b/src/FrostFS.SDK.ProtosV2/Interfaces/IVerifiableMessage.cs @@ -2,7 +2,7 @@ using Google.Protobuf; namespace FrostFS.Session; -public interface IVerificableMessage : IMessage +public interface IVerifiableMessage : IMessage { IMetaHeader GetMetaHeader(); void SetMetaHeader(IMetaHeader metaHeader); diff --git a/src/FrostFS.SDK.ProtosV2/container/Extension.Message.cs b/src/FrostFS.SDK.ProtosV2/container/Extension.Message.cs index a46ddc2..d8bd601 100644 --- a/src/FrostFS.SDK.ProtosV2/container/Extension.Message.cs +++ b/src/FrostFS.SDK.ProtosV2/container/Extension.Message.cs @@ -6,22 +6,22 @@ namespace FrostFS.Container; public partial class AnnounceUsedSpaceRequest : IRequest { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (RequestMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (RequestVerificationHeader)verificationHeader; } @@ -34,22 +34,22 @@ public partial class AnnounceUsedSpaceRequest : IRequest public partial class AnnounceUsedSpaceResponse : IResponse { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (ResponseMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (ResponseVerificationHeader)verificationHeader; } @@ -62,22 +62,22 @@ public partial class AnnounceUsedSpaceResponse : IResponse public partial class GetRequest : IRequest { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (RequestMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (RequestVerificationHeader)verificationHeader; } @@ -90,22 +90,22 @@ public partial class GetRequest : IRequest public partial class GetResponse : IResponse { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (ResponseMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (ResponseVerificationHeader)verificationHeader; } @@ -118,22 +118,22 @@ public partial class GetResponse : IResponse public partial class PutRequest : IRequest { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (RequestMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (RequestVerificationHeader)verificationHeader; } @@ -146,22 +146,22 @@ public partial class PutRequest : IRequest public partial class PutResponse : IResponse { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (ResponseMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (ResponseVerificationHeader)verificationHeader; } @@ -174,22 +174,22 @@ public partial class PutResponse : IResponse public partial class DeleteRequest : IRequest { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (RequestMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (RequestVerificationHeader)verificationHeader; } @@ -202,22 +202,22 @@ public partial class DeleteRequest : IRequest public partial class DeleteResponse : IResponse { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (ResponseMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (ResponseVerificationHeader)verificationHeader; } @@ -230,22 +230,22 @@ public partial class DeleteResponse : IResponse public partial class ListRequest : IRequest { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (RequestMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (RequestVerificationHeader)verificationHeader; } @@ -258,22 +258,22 @@ public partial class ListRequest : IRequest public partial class ListResponse : IResponse { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (ResponseMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (ResponseVerificationHeader)verificationHeader; } @@ -286,22 +286,22 @@ public partial class ListResponse : IResponse public partial class SetExtendedACLRequest : IRequest { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (RequestMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (RequestVerificationHeader)verificationHeader; } @@ -314,22 +314,22 @@ public partial class SetExtendedACLRequest : IRequest public partial class SetExtendedACLResponse : IResponse { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (ResponseMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (ResponseVerificationHeader)verificationHeader; } @@ -342,22 +342,22 @@ public partial class SetExtendedACLResponse : IResponse public partial class GetExtendedACLRequest : IRequest { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (RequestMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (RequestVerificationHeader)verificationHeader; } @@ -370,22 +370,22 @@ public partial class GetExtendedACLRequest : IRequest public partial class GetExtendedACLResponse : IResponse { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (ResponseMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (ResponseVerificationHeader)verificationHeader; } diff --git a/src/FrostFS.SDK.ProtosV2/netmap/Extension.Message.cs b/src/FrostFS.SDK.ProtosV2/netmap/Extension.Message.cs index 207568d..ba2d11e 100644 --- a/src/FrostFS.SDK.ProtosV2/netmap/Extension.Message.cs +++ b/src/FrostFS.SDK.ProtosV2/netmap/Extension.Message.cs @@ -5,22 +5,22 @@ namespace FrostFS.Netmap; public partial class LocalNodeInfoRequest : IRequest { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (RequestMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (RequestVerificationHeader)verificationHeader; } @@ -33,22 +33,22 @@ public partial class LocalNodeInfoRequest : IRequest public partial class LocalNodeInfoResponse : IResponse { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (ResponseMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (ResponseVerificationHeader)verificationHeader; } @@ -61,22 +61,22 @@ public partial class LocalNodeInfoResponse : IResponse public partial class NetworkInfoRequest : IRequest { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (RequestMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (RequestVerificationHeader)verificationHeader; } @@ -89,22 +89,22 @@ public partial class NetworkInfoRequest : IRequest public partial class NetworkInfoResponse : IResponse { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (ResponseMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (ResponseVerificationHeader)verificationHeader; } @@ -114,3 +114,60 @@ public partial class NetworkInfoResponse : IResponse return Body; } } + + +public partial class NetmapSnapshotRequest : IRequest +{ + IMetaHeader IVerifiableMessage.GetMetaHeader() + { + return MetaHeader; + } + + IVerificationHeader IVerifiableMessage.GetVerificationHeader() + { + return VerifyHeader; + } + + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) + { + MetaHeader = (RequestMetaHeader)metaHeader; + } + + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + { + VerifyHeader = (RequestVerificationHeader)verificationHeader; + } + + public IMessage GetBody() + { + return Body; + } +} + +public partial class NetmapSnapshotResponse : IResponse +{ + IMetaHeader IVerifiableMessage.GetMetaHeader() + { + return MetaHeader; + } + + IVerificationHeader IVerifiableMessage.GetVerificationHeader() + { + return VerifyHeader; + } + + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) + { + MetaHeader = (ResponseMetaHeader)metaHeader; + } + + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + { + VerifyHeader = (ResponseVerificationHeader)verificationHeader; + } + + public IMessage GetBody() + { + return Body; + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ProtosV2/object/Extension.Message.cs b/src/FrostFS.SDK.ProtosV2/object/Extension.Message.cs index a7cd446..de933fb 100644 --- a/src/FrostFS.SDK.ProtosV2/object/Extension.Message.cs +++ b/src/FrostFS.SDK.ProtosV2/object/Extension.Message.cs @@ -6,22 +6,22 @@ namespace FrostFS.Object { public partial class GetRequest : IRequest { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (RequestMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (RequestVerificationHeader)verificationHeader; } @@ -34,22 +34,22 @@ namespace FrostFS.Object public partial class GetResponse : IResponse { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (ResponseMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (ResponseVerificationHeader)verificationHeader; } @@ -62,22 +62,22 @@ namespace FrostFS.Object public partial class PutRequest : IRequest { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (RequestMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (RequestVerificationHeader)verificationHeader; } @@ -90,22 +90,22 @@ namespace FrostFS.Object public partial class PutResponse : IResponse { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (ResponseMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (ResponseVerificationHeader)verificationHeader; } @@ -118,22 +118,22 @@ namespace FrostFS.Object public partial class PutSingleRequest : IRequest { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (RequestMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (RequestVerificationHeader)verificationHeader; } @@ -146,22 +146,22 @@ namespace FrostFS.Object public partial class PutSingleResponse : IResponse { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (ResponseMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (ResponseVerificationHeader)verificationHeader; } @@ -174,22 +174,22 @@ namespace FrostFS.Object public partial class DeleteRequest : IRequest { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (RequestMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (RequestVerificationHeader)verificationHeader; } @@ -203,25 +203,25 @@ namespace FrostFS.Object public partial class DeleteResponse : IResponse { [DebuggerStepThrough] - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } [DebuggerStepThrough] - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } [DebuggerStepThrough] - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (ResponseMetaHeader)metaHeader; } [DebuggerStepThrough] - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (ResponseVerificationHeader)verificationHeader; } @@ -235,22 +235,22 @@ namespace FrostFS.Object public partial class HeadRequest : IRequest { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (RequestMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (RequestVerificationHeader)verificationHeader; } @@ -263,22 +263,22 @@ namespace FrostFS.Object public partial class HeadResponse : IResponse { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (ResponseMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (ResponseVerificationHeader)verificationHeader; } @@ -290,22 +290,22 @@ namespace FrostFS.Object } public partial class SearchRequest : IRequest { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (RequestMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (RequestVerificationHeader)verificationHeader; } @@ -318,22 +318,22 @@ namespace FrostFS.Object public partial class SearchResponse : IResponse { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (ResponseMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (ResponseVerificationHeader)verificationHeader; } @@ -346,22 +346,22 @@ namespace FrostFS.Object public partial class GetRangeRequest : IRequest { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (RequestMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (RequestVerificationHeader)verificationHeader; } @@ -374,22 +374,22 @@ namespace FrostFS.Object public partial class GetRangeResponse : IResponse { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (ResponseMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (ResponseVerificationHeader)verificationHeader; } @@ -402,22 +402,22 @@ namespace FrostFS.Object public partial class GetRangeHashRequest : IRequest { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (RequestMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (RequestVerificationHeader)verificationHeader; } @@ -430,22 +430,22 @@ namespace FrostFS.Object public partial class GetRangeHashResponse : IResponse { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (ResponseMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (ResponseVerificationHeader)verificationHeader; } diff --git a/src/FrostFS.SDK.ProtosV2/session/Extension.Message.cs b/src/FrostFS.SDK.ProtosV2/session/Extension.Message.cs index fd78adc..937af24 100644 --- a/src/FrostFS.SDK.ProtosV2/session/Extension.Message.cs +++ b/src/FrostFS.SDK.ProtosV2/session/Extension.Message.cs @@ -4,22 +4,22 @@ namespace FrostFS.Session; public partial class CreateResponse : IResponse { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (ResponseMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (ResponseVerificationHeader)verificationHeader; } @@ -32,22 +32,22 @@ public partial class CreateResponse : IResponse public partial class CreateRequest : IRequest { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (RequestMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (RequestVerificationHeader)verificationHeader; } diff --git a/src/FrostFS.SDK.Tests/ClientTest.cs b/src/FrostFS.SDK.Tests/ClientTest.cs new file mode 100644 index 0000000..a95f1d9 --- /dev/null +++ b/src/FrostFS.SDK.Tests/ClientTest.cs @@ -0,0 +1,85 @@ +using FrostFS.SDK.ClientV2; +using FrostFS.SDK.Cryptography; +using FrostFS.SDK.ModelsV2; +using FrostFS.SDK.ModelsV2.Enums; +using FrostFS.SDK.ModelsV2.Netmap; +using Microsoft.Extensions.Options; + +namespace FrostFS.SDK.Tests; + +public class ClientTest +{ + private readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; + + [Fact] + public async void CreateContainerTest() + { + var factory = new PutContainerMock(this.key) + { + PlacementPolicy = new PlacementPolicy(true, new Replica(1)), + Version = new ModelsV2.Version(2, 13), + ContainerGuid = Guid.NewGuid() + }; + + var settings = Options.Create(new ClientSettings + { + Key = key, + Host = "http://localhost:8080" + }); + + var fsClient = Client.GetTestInstance( + settings, + new NetmapMock(this.key).GetMock().Object, + new SessionMock(this.key).GetMock().Object, + factory.GetMock().Object, + new ObjectMock(this.key).GetMock().Object); + + var result = await fsClient.CreateContainerAsync(new ModelsV2.Container(BasicAcl.PublicRW, factory.PlacementPolicy)); + + Assert.NotNull(result); + Assert.NotNull(result.Value); + Assert.True(Base58.Encode(factory.ContainerGuid.ToBytes()) == result.Value); + } + + [Fact] + public async void GetContainerTest() + { + var factory = new GetContainerMock(this.key) + { + PlacementPolicy = new PlacementPolicy(true, new Replica(1)), + Version = new ModelsV2.Version(2, 13), + Acl = BasicAcl.PublicRO, + ContainerGuid = Guid.NewGuid(), + }; + + var settings = Options.Create(new ClientSettings + { + Key = key, + Host = "http://localhost:8080" + }); + + var fsClient = Client.GetTestInstance( + settings, + new NetmapMock(this.key).GetMock().Object, + new SessionMock(this.key).GetMock().Object, + factory.GetMock().Object, + new ObjectMock(this.key).GetMock().Object); + + var cid = new ContainerId(Base58.Encode(factory.ContainerGuid.ToBytes())); + + var context = new Context(); + + var result = await fsClient.GetContainerAsync(cid, context); + + Assert.NotNull(result); + Assert.Equal(factory.Acl, result.BasicAcl); + Assert.Equal(factory.ContainerGuid, result.Nonce); + Assert.Equal(0, factory.PlacementPolicy.CompareTo(result.PlacementPolicy)); + Assert.Equal(factory.Version.ToString(), result.Version!.ToString()); + } + + // [Fact] + // public async void DeleteObjectAsyncTest() + // { + // } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/ClientTestLive.cs b/src/FrostFS.SDK.Tests/ClientTestLive.cs new file mode 100644 index 0000000..7a849df --- /dev/null +++ b/src/FrostFS.SDK.Tests/ClientTestLive.cs @@ -0,0 +1,208 @@ +using FrostFS.SDK.ClientV2; +using FrostFS.SDK.ClientV2.Interfaces; +using FrostFS.SDK.ModelsV2; +using FrostFS.SDK.ModelsV2.Enums; +using FrostFS.SDK.ModelsV2.Netmap; +using Microsoft.Extensions.Options; + +namespace FrostFS.SDK.Tests; + +public class ClientTestLive +{ + private readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; + private readonly string url = "http://172.29.238.97:8080"; + + [Fact] + public async void NetworkMapTest() + { + var fsClient = Client.GetInstance(GetOptions(key, url)); + + var result = await fsClient.GetNetmapSnapshotAsync(); + + Assert.True(result.Epoch > 0); + Assert.Single(result.NodeInfoCollection); + + var item = result.NodeInfoCollection[0]; + Assert.Equal(2, item.Version.Major); + Assert.Equal(13, item.Version.Minor); + Assert.Equal(NodeState.Online, item.State); + Assert.True(item.PublicKey.Length > 0); + Assert.Single(item.Addresses); + Assert.Equal(9, item.Attributes.Count); + } + + [Fact] + public async void NodeInfoTest() + { + var fsClient = Client.GetInstance(GetOptions(this.key, this.url)); + + var result = await fsClient.GetNodeInfoAsync(); + + Assert.Equal(2, result.Version.Major); + Assert.Equal(13, result.Version.Minor); + Assert.Equal(NodeState.Online, result.State); + Assert.True(result.PublicKey.Length > 0); + Assert.Single(result.Addresses); + Assert.Equal(9, result.Attributes.Count); + } + + [Fact] + public async void SimpleScenarioTest() + { + var fsClient = Client.GetInstance(GetOptions(this.key, this.url)); + + fsClient.GrpcInvoked += (sender, info) => + { + Assert.NotNull(info); + }; + + await Cleanup(fsClient); + + var containerId = await fsClient.CreateContainerAsync( + new ModelsV2.Container(BasicAcl.PublicRW, new PlacementPolicy(true, new Replica(1)))); + + var context = new Context { Timeout = TimeSpan.FromSeconds(10) }; + + var container = await GetContainer(fsClient, containerId, context); + + Assert.NotNull(container); + + var param = new PutObjectParameters + { + Header = new ObjectHeader( + containerId: containerId, + type: ObjectType.Regular, + new ObjectAttribute("fileName", "test")), + Payload = new MemoryStream([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]), + ClientCut = false + }; + + var objectId = await fsClient.PutObjectAsync(param); + + var filter = new ObjectFilter(ObjectMatchType.Equals, "fileName", "test"); + + bool hasObject = false; + await foreach (var objId in fsClient.SearchObjectsAsync(containerId, [filter])) + { + hasObject = true; + + var objHeader = await fsClient.GetObjectHeadAsync(containerId, objectId); + Assert.Equal(10u, objHeader.PayloadLength); + Assert.Single(objHeader.Attributes); + Assert.Equal("fileName", objHeader.Attributes.First().Key); + Assert.Equal("test", objHeader.Attributes.First().Value); + } + + Assert.True(hasObject); + + var @object = await fsClient.GetObjectAsync(containerId, objectId!); + + Assert.Equal([1, 2, 3, 4, 5, 6, 7, 8, 9, 0], @object.Payload); + + await Cleanup(fsClient); + + await Task.Delay(2000); + + await foreach (var cid in fsClient.ListContainersAsync()) + { + Assert.Fail("Containers exist"); + } + } + + [Fact] + public async void ClientCutScenarioTest() + { + var fsClient = Client.GetInstance(GetOptions(this.key, this.url)); + + await Cleanup(fsClient); + + var containerId = await fsClient.CreateContainerAsync( + new ModelsV2.Container(BasicAcl.PublicRW, new PlacementPolicy(true, new Replica(1)))); + + var context = new Context { Timeout = TimeSpan.FromSeconds(10) }; + var container = await GetContainer(fsClient, containerId, context); + + Assert.NotNull(container); + + var param = new PutObjectParameters + { + Header = new ObjectHeader( + containerId: containerId, + type: ObjectType.Regular, + new ObjectAttribute("fileName", "test")), + Payload = new MemoryStream([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]), + ClientCut = true + }; + + var objectId = await fsClient.PutObjectAsync(param); + + var filter = new ObjectFilter(ObjectMatchType.Equals, "fileName", "test"); + + bool hasObject = false; + await foreach (var objId in fsClient.SearchObjectsAsync(containerId, [filter])) + { + hasObject = true; + + var objHeader = await fsClient.GetObjectHeadAsync(containerId, objectId); + Assert.Equal(10u, objHeader.PayloadLength); + Assert.Single(objHeader.Attributes); + Assert.Equal("fileName", objHeader.Attributes.First().Key); + Assert.Equal("test", objHeader.Attributes.First().Value); + } + + Assert.True(hasObject); + + var @object = await fsClient.GetObjectAsync(containerId, objectId!); + + Assert.Equal([1, 2, 3, 4, 5, 6, 7, 8, 9, 0], @object.Payload); + + await Cleanup(fsClient); + + await Task.Delay(2000); + + await foreach (var _ in fsClient.ListContainersAsync()) + { + Assert.Fail("Containers exist"); + } + } + + private static IOptions GetOptions(string key, string url) + { + var settings = new ClientSettings + { + Key = key, + Host = url + }; + + return Options.Create(settings); + } + + static async Task Cleanup(IFrostFSClient fsClient) + { + await foreach (var cid in fsClient.ListContainersAsync()) + { + await fsClient.DeleteContainerAsync(cid); + } + } + + static async Task GetContainer(IFrostFSClient fsClient, ContainerId id, Context ctx) + { + while (true) + { + try + { + await Task.Delay(100); + return await fsClient.GetContainerAsync(id, ctx); + } + catch (ApplicationException) + { + if (DateTime.UtcNow >= ctx.Deadline) + throw new TimeoutException(); + } + catch (Grpc.Core.RpcException) + { + throw; + } + } + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/ContainerServiceMocks/ContainerServiceBase.cs b/src/FrostFS.SDK.Tests/ContainerServiceMocks/ContainerServiceBase.cs new file mode 100644 index 0000000..bbf1476 --- /dev/null +++ b/src/FrostFS.SDK.Tests/ContainerServiceMocks/ContainerServiceBase.cs @@ -0,0 +1,28 @@ +using System.Security.Cryptography; +using FrostFS.Container; +using Moq; + +using FrostFS.SDK.Cryptography; +using FrostFS.SDK.ModelsV2.Enums; +using FrostFS.SDK.ModelsV2.Netmap; + +namespace FrostFS.SDK.Tests; + +public abstract class ServiceBase(string key) +{ + public ECDsa Key { get; private set; } = key.LoadWif(); + public ModelsV2.Version Version { get; set; } = DefaultVersion; + public BasicAcl Acl { get; set; } = DefaultAcl; + public PlacementPolicy PlacementPolicy { get; set; } = DefaultPlacementPolicy; + + public static ModelsV2.Version DefaultVersion { get; } = new(2, 13); + public static BasicAcl DefaultAcl { get; } = BasicAcl.PublicRW; + public static PlacementPolicy DefaultPlacementPolicy { get; } = new PlacementPolicy(true, new Replica(1)); +} + +public abstract class ContainerServiceBase(string key) : ServiceBase (key) +{ + public Guid ContainerGuid { get; set; } = Guid.NewGuid(); + + public abstract Mock GetMock(); +} diff --git a/src/FrostFS.SDK.Tests/ContainerServiceMocks/DeleteContainerMock.cs b/src/FrostFS.SDK.Tests/ContainerServiceMocks/DeleteContainerMock.cs new file mode 100644 index 0000000..6639645 --- /dev/null +++ b/src/FrostFS.SDK.Tests/ContainerServiceMocks/DeleteContainerMock.cs @@ -0,0 +1,69 @@ +using FrostFS.Container; +using FrostFS.Session; +using Google.Protobuf; +using Grpc.Core; +using Moq; + +namespace FrostFS.SDK.Tests; + +public class DeleteContainerMock(string key) : ContainerServiceBase(key) +{ + public override Mock GetMock() + { + var mock = new Mock(); + + var v = mock.Setup(x => x.DeleteAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())); + + + v.Returns((Object.DeleteRequest r, Metadata m, DateTime? dt, CancellationToken ct) => + { + var deleteResponse = new Object.DeleteResponse + { + Body = new Object.DeleteResponse.Types.Body + { + Tombstone = new Refs.Address + { + ContainerId = new Refs.ContainerID { Value = ByteString.CopyFrom([1, 2, 3]) }, + ObjectId = new Refs.ObjectID { Value = ByteString.CopyFrom([4, 5, 6]) } + } + }, + MetaHeader = new ResponseMetaHeader() + }; + + var metadata = new Metadata(); + + return new AsyncUnaryCall( + Task.FromResult(deleteResponse), + Task.FromResult(metadata), + () => new Grpc.Core.Status(StatusCode.OK, string.Empty), + () => metadata, + () => Console.WriteLine("disposed")); + }); + + return mock; + } +} + + + // objectServiceClientMock.Setup(x => x.Head(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + // .Returns((Object.DeleteRequest r, Metadata m, DateTime dt, CancellationToken ct) => + // { + // return new + // { + // Body = new Object.DeleteResponse.Types.Body + // { + // Tombstone = new Refs.Address + // { + // ContainerId = new Refs.ContainerID { Value = ByteString.CopyFrom([1, 2, 3]) }, + // ObjectId = new Refs.ObjectID { Value = ByteString.CopyFrom([4, 5, 6]) } + // } + // } + // }; + // }); + + + \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/ContainerServiceMocks/GetContainerMock.cs b/src/FrostFS.SDK.Tests/ContainerServiceMocks/GetContainerMock.cs new file mode 100644 index 0000000..61feb5e --- /dev/null +++ b/src/FrostFS.SDK.Tests/ContainerServiceMocks/GetContainerMock.cs @@ -0,0 +1,163 @@ +using FrostFS.Container; +using FrostFS.Session; +using Google.Protobuf; +using Grpc.Core; +using Moq; + +using FrostFS.SDK.Cryptography; +using FrostFS.SDK.ClientV2; +using FrostFS.SDK.ModelsV2.Netmap; +using FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; +using FrostFS.SDK.ClientV2.Mappers.GRPC; + +namespace FrostFS.SDK.Tests; + +public class GetContainerMock(string key) : ContainerServiceBase(key) +{ + public override Mock GetMock() + { + var mock = new Mock(); + + var grpcVersion = Version.ToGrpcMessage(); + + var getResponse = new GetResponse + { + Body = new GetResponse.Types.Body + { + Container = new Container.Container + { + Version = grpcVersion, + Nonce = ByteString.CopyFrom(ContainerGuid.ToBytes()), + BasicAcl = (uint)Acl, + PlacementPolicy = PlacementPolicy.ToGrpcMessage() + } + }, + MetaHeader = new ResponseMetaHeader + { + Version = grpcVersion, + Epoch = 100, + Ttl = 1 + } + }; + + getResponse.VerifyHeader = GetResponseVerificationHeader(getResponse); + + var metadata = new Metadata(); + var getContainerResponse = new AsyncUnaryCall( + Task.FromResult(getResponse), + Task.FromResult(metadata), + () => new Grpc.Core.Status(StatusCode.OK, string.Empty), + () => metadata, + () => { }); + + mock.Setup(x => x.GetAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((GetRequest r, Metadata m, DateTime? dt, CancellationToken ct) => + { + Verifier.CheckRequest(r); + + return getContainerResponse; + }); + + return mock; + } + + private ResponseVerificationHeader GetResponseVerificationHeader(GetResponse response) + { + var verifyHeader = new ResponseVerificationHeader + { + MetaSignature = new Refs.Signature + { + Key = ByteString.CopyFrom(Key.PublicKey()), + Scheme = FrostFS.Refs.SignatureScheme.EcdsaRfc6979Sha256, + Sign = ByteString.CopyFrom(Key.SignData(response.MetaHeader.ToByteArray())) + }, + BodySignature = new Refs.Signature + { + Key = ByteString.CopyFrom(Key.PublicKey()), + Scheme = FrostFS.Refs.SignatureScheme.EcdsaRfc6979Sha256, + Sign = ByteString.CopyFrom(Key.SignData(response.GetBody().ToByteArray())) + }, + OriginSignature = new Refs.Signature + { + Key = ByteString.CopyFrom(Key.PublicKey()), + Scheme = FrostFS.Refs.SignatureScheme.EcdsaRfc6979Sha256, + Sign = ByteString.CopyFrom(Key.SignData([])) + } + }; + + return verifyHeader; + } +} + +// objectServiceClientMock.Setup( +// x => x.Put(It.IsAny(), It.IsAny(), It.IsAny())) +// .Returns((Metadata m, DateTime dt, CancellationToken ct) => +// { +// return new AsyncClientStreamingCall(null, null, null, null, null, null); + +// //IClientStreamWriter requestStream, Task responseAsync, Task responseHeadersAsync, Func getStatusFunc, Func getTrailersFunc, Action disposeAction +// }); + +// return objectServiceClientMock; +// } + + +// } + + + // public virtual global::FrostFS.Object.HeadResponse Head(global::FrostFS.Object.HeadRequest request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) + // { + // return Head(request, new grpc::CallOptions(headers, deadline, cancellationToken)); + // } + + // objectServiceClientMock.Setup(x => x.Head(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + // .Returns((Object.DeleteRequest r, Metadata m, DateTime dt, CancellationToken ct) => + // { + // return new + // { + // Body = new Object.DeleteResponse.Types.Body + // { + // Tombstone = new Refs.Address + // { + // ContainerId = new Refs.ContainerID { Value = ByteString.CopyFrom([1, 2, 3]) }, + // ObjectId = new Refs.ObjectID { Value = ByteString.CopyFrom([4, 5, 6]) } + // } + // } + // }; + // }); + + + // objectServiceClientMock.Setup(x => x.Delete(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + // .Returns((Object.DeleteRequest r, Metadata m, DateTime? dt, CancellationToken ct) => + // { + // return new Object.DeleteResponse + // { + // Body = new Object.DeleteResponse.Types.Body + // { + // Tombstone = new Refs.Address + // { + // ContainerId = new Refs.ContainerID { Value = ByteString.CopyFrom([1, 2, 3]) }, + // ObjectId = new Refs.ObjectID { Value = ByteString.CopyFrom([4, 5, 6]) } + // } + // }, + // MetaHeader = new ResponseMetaHeader() + // { + // }, + // VerifyHeader = new ResponseVerificationHeader() + // { + // MetaSignature = new Refs.Signature + // { + // Key = ByteString.CopyFrom(_key.PublicKey()), + // Scheme = Refs.SignatureScheme.EcdsaRfc6979Sha256, + // Sign = ByteString.CopyFrom(_key.SignData(Array.Empty())) + + // // ByteString.CopyFrom(_key.SignData(grpcHeader.Split.Parent.ToByteArray())), + // } + // } + + // }; + // }); \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/ContainerServiceMocks/PutContainerMock.cs b/src/FrostFS.SDK.Tests/ContainerServiceMocks/PutContainerMock.cs new file mode 100644 index 0000000..3243bdf --- /dev/null +++ b/src/FrostFS.SDK.Tests/ContainerServiceMocks/PutContainerMock.cs @@ -0,0 +1,88 @@ +using FrostFS.Container; +using FrostFS.Session; +using Google.Protobuf; +using Grpc.Core; +using Moq; +using FrostFS.SDK.ClientV2; +using FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; +using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Cryptography; +using FrostFS.Refs; + +namespace FrostFS.SDK.Tests; + +public class PutContainerMock(string key) : ContainerServiceBase(key) +{ + public override Mock GetMock() + { + var mock = new Mock(); + + PutResponse response = new() + { + Body = new PutResponse.Types.Body + { + ContainerId = new ContainerID + { + Value = ByteString.CopyFrom(ContainerGuid.ToBytes()) + } + }, + MetaHeader = new ResponseMetaHeader + { + Version = Version is null ? DefaultVersion.ToGrpcMessage() : Version.ToGrpcMessage(), + Epoch = 100, + Ttl = 1 + } + }; + + response.VerifyHeader = PutResponseVerificationHeader(response); + + var metadata = new Metadata(); + var putContainerResponse = new AsyncUnaryCall( + Task.FromResult(response), + Task.FromResult(metadata), + () => new Grpc.Core.Status(StatusCode.OK, string.Empty), + () => metadata, + () => { }); + + mock.Setup(x => x.PutAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((PutRequest r, Metadata m, DateTime? dt, CancellationToken ct) => + { + Verifier.CheckRequest(r); + + return putContainerResponse; + }); + + return mock; + } + + private ResponseVerificationHeader PutResponseVerificationHeader(PutResponse response) + { + var verifyHeader = new ResponseVerificationHeader + { + MetaSignature = new Signature + { + Key = ByteString.CopyFrom(Key.PublicKey()), + Scheme = SignatureScheme.EcdsaRfc6979Sha256, + Sign = ByteString.CopyFrom(Key.SignData(response.MetaHeader.ToByteArray())) + }, + BodySignature = new Signature + { + Key = ByteString.CopyFrom(Key.PublicKey()), + Scheme = SignatureScheme.EcdsaRfc6979Sha256, + Sign = ByteString.CopyFrom(Key.SignData(response.GetBody().ToByteArray())) + }, + OriginSignature = new Signature + { + Key = ByteString.CopyFrom(Key.PublicKey()), + Scheme = SignatureScheme.EcdsaRfc6979Sha256, + Sign = ByteString.CopyFrom(Key.SignData([])) + } + }; + + return verifyHeader; + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/NetmapMock.cs b/src/FrostFS.SDK.Tests/NetmapMock.cs new file mode 100644 index 0000000..0cc3889 --- /dev/null +++ b/src/FrostFS.SDK.Tests/NetmapMock.cs @@ -0,0 +1,14 @@ +using Moq; +using FrostFS.Netmap; + +namespace FrostFS.SDK.Tests; + +public class NetmapMock(string key) : ServiceBase(key) +{ + public Mock GetMock() + { + var mock = new Mock(); + + return mock; + } +} diff --git a/src/FrostFS.SDK.Tests/ObjectMock.cs b/src/FrostFS.SDK.Tests/ObjectMock.cs new file mode 100644 index 0000000..96296f8 --- /dev/null +++ b/src/FrostFS.SDK.Tests/ObjectMock.cs @@ -0,0 +1,14 @@ +using Moq; +using FrostFS.Object; + +namespace FrostFS.SDK.Tests; + +public class ObjectMock(string key) : ServiceBase(key) +{ + public Mock GetMock() + { + var mock = new Mock(); + + return mock; + } +} diff --git a/src/FrostFS.SDK.Tests/SessionMock.cs b/src/FrostFS.SDK.Tests/SessionMock.cs new file mode 100644 index 0000000..8309fde --- /dev/null +++ b/src/FrostFS.SDK.Tests/SessionMock.cs @@ -0,0 +1,14 @@ +using Moq; +using FrostFS.Session; + +namespace FrostFS.SDK.Tests; + +public class SessionMock(string key) : ServiceBase(key) +{ + public Mock GetMock() + { + var mock = new Mock(); + + return mock; + } +} -- 2.45.2