[#11] Add Network Snapshot

Signed-off-by: Pavel Gross <p.gross@yadro.com>
This commit is contained in:
Pavel Gross 2024-06-26 12:29:33 +03:00
parent b69d22966f
commit c988ff3c76
84 changed files with 2238 additions and 933 deletions

1
.gitignore vendored
View file

@ -18,6 +18,7 @@ vendor/
# IDE
.idea
.vscode
.vs
# coverage
coverage.txt

View file

@ -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

View file

@ -1,93 +1,223 @@
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using FrostFS.Container;
using FrostFS.Container;
using FrostFS.Netmap;
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 System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Version = FrostFS.SDK.ModelsV2.Version;
namespace FrostFS.SDK.ClientV2;
public partial class Client: IFrostFSClient
public class Client : IFrostFSClient
{
private GrpcChannel? _channel;
private readonly ECDsa _key;
public readonly OwnerId OwnerId;
public readonly Version Version = new(2, 13);
private bool isDisposed;
private readonly Dictionary<string, ulong> 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<ClientSettings> clientOptions, GrpcChannelOptions channelOptions)
{
return new Client(key, host);
return new Client(clientOptions, channelOptions);
}
private Client(string key, string host)
/// <summary>
/// For test only. Provide custom implementation or mock object to inject required logic instead of internal gRPC client.
/// </summary>
/// <param name="clientOptions">Global setting for client</param>
/// <param name="channelOptions">Setting for gRPC cjannel</param>
/// <param name="containerService">ContainerService.ContainerServiceClient implementation</param>
/// <param name="netmapService">Netmap.NetmapService.NetmapServiceClient implementation</param>
/// <param name="sessionService">Session.SessionService.SessionServiceClient implementation</param>
/// <param name="objectService">Object.ObjectService.ObjectServiceClient implementation</param>
/// <returns></returns>
public static IFrostFSClient GetTestInstance(
IOptions<ClientSettings> clientOptions,
GrpcChannelOptions? channelOptions,
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(clientOptions, channelOptions, containerService, netmapService, sessionService, objectService);
}
private Client(
IOptions<ClientSettings> settings,
GrpcChannelOptions? channelOptions,
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, channelOptions),
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<ClientSettings> options, GrpcChannelOptions channelOptions)
{
var clientSettings = (options?.Value) ?? throw new ArgumentException("Options must be initialized");
clientSettings.Validate();
var ecdsaKey = clientSettings.Key.LoadWif();
var channel = InitGrpcChannel(clientSettings.Host, channelOptions);
ClientCtx = new ClientEnvironment(
key: ecdsaKey,
owner: OwnerId.FromKey(ecdsaKey),
channel: channel,
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()
public void Dispose()
{
var info = await GetNetworkInfoAsync();
Dispose(true);
GC.SuppressFinalize(this);
}
foreach (var param in info.Body.NetworkInfo.NetworkConfig.Parameters)
protected virtual void Dispose(bool disposing)
{
if (disposing && !isDisposed)
{
SetNetworksParam(param);
ClientCtx.Dispose();
isDisposed = true;
}
}
private void SetNetworksParam(Parameter param)
{
var key = Encoding.UTF8.GetString(param.Key.ToByteArray());
public GrpcChannel Channel => ClientCtx.Channel;
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 Task<ModelsV2.Container> GetContainerAsync(ContainerId containerId, Context? ctx = null)
{
var localNodeInfo = await GetLocalNodeInfoAsync();
if (!localNodeInfo.Version.IsSupported(Version))
ValidateEnvironment(ref ctx);
return ClientCtx.ContainerService!.GetContainerAsync(containerId, ctx!);
}
public IAsyncEnumerable<ContainerId> ListContainersAsync(Context? ctx = default)
{
ValidateEnvironment(ref ctx);
return ClientCtx.ContainerService!.ListContainersAsync(ctx!);
}
public Task<ContainerId> 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<NetmapSnapshot> GetNetmapSnapshotAsync(Context? ctx = default)
{
ValidateEnvironment(ref ctx);
return ClientCtx.NetmapService!.GetNetmapSnapshotAsync(ctx!);
}
public Task<ModelsV2.Netmap.NodeInfo> GetNodeInfoAsync(Context? ctx = default)
{
ValidateEnvironment(ref ctx);
return ClientCtx.NetmapService!.GetLocalNodeInfoAsync(ctx!);
}
public Task<ObjectHeader> GetObjectHeadAsync(ContainerId containerId, ObjectId objectId, Context? ctx = default)
{
ValidateEnvironment(ref ctx);
return ClientCtx.ObjectService!.GetObjectHeadAsync(containerId, objectId, ctx!);
}
public Task<ModelsV2.Object> GetObjectAsync(ContainerId containerId, ObjectId objectId, Context? ctx = default)
{
ValidateEnvironment(ref ctx);
return ClientCtx.ObjectService!.GetObjectAsync(containerId, objectId, ctx!);
}
public Task<ObjectId> PutObjectAsync(PutObjectParameters putObjectParameters, Context? ctx = default)
{
ValidateEnvironment(ref ctx);
return ClientCtx.ObjectService!.PutObjectAsync(putObjectParameters, ctx!);
}
public Task<ObjectId> 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<ObjectId> SearchObjectsAsync(
ContainerId containerId,
IEnumerable<ObjectFilter> 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, GrpcChannelOptions? channelOptions)
{
Uri uri;
try
@ -97,49 +227,25 @@ public partial class Client: IFrostFSClient
catch (UriFormatException e)
{
var msg = $"Host '{host}' has invalid format. Error: {e.Message}";
Console.WriteLine(msg);
throw new ArgumentException(msg);
}
ChannelCredentials grpcCredentials;
switch (uri.Scheme)
ChannelCredentials grpcCredentials = uri.Scheme switch
{
case "https":
grpcCredentials = ChannelCredentials.SecureSsl;
break;
case "http":
grpcCredentials = ChannelCredentials.Insecure;
break;
default:
var msg = $"Host '{host}' has invalid URI scheme: '{uri.Scheme}'.";
Console.WriteLine(msg);
throw new ArgumentException(msg);
"https" => ChannelCredentials.SecureSsl,
"http" => ChannelCredentials.Insecure,
_ => throw new ArgumentException($"Host '{host}' has invalid URI scheme: '{uri.Scheme}'.")
};
if (channelOptions != null)
{
return GrpcChannel.ForAddress(uri, channelOptions);
}
_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 HttpClientHandler()
});
}
}

View file

@ -6,9 +6,14 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" />
<PackageReference Include="Moq.AutoMock" Version="3.5.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="8.0.2" />
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="8.0.1" />
</ItemGroup>
<ItemGroup>

View file

@ -1,34 +1,41 @@
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;
using Grpc.Net.Client;
namespace FrostFS.SDK.ClientV2.Interfaces;
public interface IFrostFSClient
public interface IFrostFSClient : IDisposable
{
Task<ModelsV2.Container> GetContainerAsync(ContainerId containerId);
Task<ModelsV2.Container> GetContainerAsync(ContainerId containerId, Context? context = default);
IAsyncEnumerable<ContainerId> ListContainersAsync();
IAsyncEnumerable<ContainerId> ListContainersAsync(Context? context = default);
Task<ContainerId> CreateContainerAsync(ModelsV2.Container container);
Task<ContainerId> CreateContainerAsync(ModelsV2.Container container, Context? context = default);
Task DeleteContainerAsync(ContainerId containerId);
Task DeleteContainerAsync(ContainerId containerId, Context? context = default);
Task<ObjectHeader> GetObjectHeadAsync(ContainerId containerId, ObjectId objectId);
Task<ObjectHeader> GetObjectHeadAsync(ContainerId containerId, ObjectId objectId, Context? context = default);
Task<ModelsV2.Object> GetObjectAsync(ContainerId containerId, ObjectId objectId, Context? context = default);
Task<ModelsV2.Object> GetObjectAsync(ContainerId containerId, ObjectId objectId);
Task<ObjectId> PutObjectAsync(PutObjectParameters putObjectParameters, Context? context = default);
Task<ObjectId> PutSingleObjectAsync(ModelsV2.Object obj, Context? context = default);
Task<ObjectId> PutObjectAsync(ObjectHeader header, Stream payload, CancellationToken cancellationToken = default);
Task DeleteObjectAsync(ContainerId containerId, ObjectId objectId, Context? context = default);
Task<ObjectId> PutObjectAsync(ObjectHeader header, byte[] payload, CancellationToken cancellationToken = default);
IAsyncEnumerable<ObjectId> SearchObjectsAsync(ContainerId cid, IEnumerable<ObjectFilter> filters, Context? context = default);
Task<ObjectId> PutSingleObjectAsync(ModelsV2.Object obj, CancellationToken cancellationToken = default);
Task<NetmapSnapshot> GetNetmapSnapshotAsync(Context? context = default);
Task DeleteObjectAsync(ContainerId containerId, ObjectId objectId);
Task<NodeInfo> GetNodeInfoAsync(Context? context = default);
ObjectId CalculateObjectId(ObjectHeader header);
GrpcChannel Channel { get; }
}
IAsyncEnumerable<ObjectId> SearchObjectsAsync(ContainerId cid, params ObjectFilter[] filters);
}

View file

@ -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()
};
}
}
}

View file

@ -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()
};
}
}

View file

@ -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());
}
}

View file

@ -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()
);
}
}

View file

@ -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());
}
}

View file

@ -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);
}
}

View file

@ -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
};
}
}

View file

@ -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)
@ -116,7 +74,7 @@ public static class ObjectHeaderMapper
if (header.Split != null)
{
model.Split = new Split(SplitId.CrateFromBinary(header.Split.SplitId.ToByteArray()))
model.Split = new Split(SplitId.CreateFromBinary(header.Split.SplitId.ToByteArray()))
{
Parent = header.Split.Parent?.ToModel(),
ParentHeader = header.Split.ParentHeader?.ToModel(),
@ -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)
};
}
}

View file

@ -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;
}
}

View file

@ -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)
};
}
}

View file

@ -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<ModelsV2.Container> 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<ContainerId> 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<ContainerId> 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);
}
}

View file

@ -0,0 +1,113 @@
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<ModelsV2.Container> 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 containerServiceClient.GetAsync(request, null, context.Deadline, context.CancellationToken);
Verifier.CheckResponse(response);
return response.Body.Container.ToModel();
}
internal async IAsyncEnumerable<ContainerId> 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 containerServiceClient.ListAsync(request, null, ctx.Deadline, ctx.CancellationToken);
Verifier.CheckResponse(response);
foreach (var cid in response.Body.ContainerIds)
{
yield return new ContainerId(Base58.Encode(cid.Value.ToByteArray()));
}
}
internal async Task<ContainerId> 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 containerServiceClient.PutAsync(request, null, ctx.Deadline, ctx.CancellationToken);
//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 containerServiceClient.DeleteAsync(request, null, ctx.Deadline, ctx.CancellationToken);
Verifier.CheckResponse(response);
}
}

View file

@ -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<NodeInfo> 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<NetworkInfoResponse> GetNetworkInfoAsync()
{
var request = new NetworkInfoRequest
{
Body = new NetworkInfoRequest.Types.Body { }
};
request.AddMetaHeader();
request.Sign(_key);
return await _netmapServiceClient.NetworkInfoAsync(request);
}
}

View file

@ -0,0 +1,132 @@
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<NetworkSettings> 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<NodeInfo> GetLocalNodeInfoAsync(Context ctx)
{
var request = new LocalNodeInfoRequest
{
Body = new LocalNodeInfoRequest.Types.Body { }
};
request.AddMetaHeader();
request.Sign(Context.Key);
var response = await netmapServiceClient.LocalNodeInfoAsync(request, null, ctx.Deadline, ctx.CancellationToken);
//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<NetworkInfoResponse> GetNetworkInfoAsync(Context ctx)
{
var request = new NetworkInfoRequest
{
Body = new NetworkInfoRequest.Types.Body { }
};
request.AddMetaHeader();
request.Sign(Context.Key);
var response = await netmapServiceClient.NetworkInfoAsync(request, null, ctx.Deadline, ctx.CancellationToken);
Verifier.CheckResponse(response);
return response;
}
internal async Task<NetmapSnapshot> GetNetmapSnapshotAsync(Context ctx)
{
var request = new NetmapSnapshotRequest
{
Body = new NetmapSnapshotRequest.Types.Body { }
};
request.AddMetaHeader();
request.Sign(Context.Key);
var response = await netmapServiceClient.NetmapSnapshotAsync(request, null, ctx.Deadline, ctx.CancellationToken);
Verifier.CheckResponse(response);
return response.ToModel();
}
private static bool GetBoolValue(byte[] bytes)
{
return bytes.Any(b => b != 0);
}
private static ulong GetLongValue(byte[] bytes)
{
ulong val = 0;
for (var i = bytes.Length - 1; i >= 0; i--)
val = (val << 8) + bytes[i];
return val;
}
private static 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;
}
}
}

View file

@ -7,20 +7,20 @@ using FrostFS.Object;
namespace FrostFS.SDK.ClientV2;
internal class ObjectReader : IDisposable
internal class ObjectReader(AsyncServerStreamingCall<GetResponse> call) : IDisposable
{
public AsyncServerStreamingCall<GetResponse> Call { get; set; }
public AsyncServerStreamingCall<GetResponse> Call { get; private set; } = call;
public async Task<Object.Object> 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();
}

View file

@ -1,26 +1,33 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Google.Protobuf;
using System.Threading.Tasks;
using FrostFS.Object;
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<ObjectHeader> 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<ObjectHeader> GetObjectHeadAsync(ContainerId cid, ObjectId oid, Context ctx)
{
var request = new HeadRequest
{
@ -34,18 +41,20 @@ public partial class Client
}
};
request.AddMetaHeader();
request.Sign(_key);
var response = await _objectServiceClient!.HeadAsync(request);
request.Sign(Context.Key);
var response = await objectServiceClient!.HeadAsync(request, null, ctx.Deadline, ctx.CancellationToken);
Verifier.CheckResponse(response);
return response.Body.Header.Header.ToModel();
}
public async Task<ModelsV2.Object> GetObjectAsync(ContainerId cid, ObjectId oid)
internal async Task<ModelsV2.Object> 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,131 @@ 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<ObjectId> PutObjectAsync(ObjectHeader header, Stream payload, CancellationToken cancellationToken = default)
internal Task<ObjectId> PutObjectAsync(PutObjectParameters parameters, Context ctx)
{
return await PutObject(header, payload, cancellationToken);
}
public async Task<ObjectId> PutObjectAsync(ObjectHeader header, byte[] payload, CancellationToken cancellationToken = default)
{
using var stream = new MemoryStream(payload);
return await PutObject(header, stream, cancellationToken);
}
private Task<ObjectId> 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<ObjectId> 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 objectServiceClient!.PutSingleAsync(request, null, ctx.Deadline, ctx.CancellationToken);
Verifier.CheckResponse(response);
return ObjectId.FromHash(obj.ObjectId.Value.ToByteArray());
}
private async Task<ObjectId> 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 objectServiceClient!.DeleteAsync(request, null, ctx.Deadline, ctx.CancellationToken);
Verifier.CheckResponse(response);
}
internal async IAsyncEnumerable<ObjectId> SearchObjectsAsync(
ContainerId cid,
IEnumerable<ObjectFilter> 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<ObjectId> PutClientCutObject(PutObjectParameters parameters, Context ctx)
{
var payloadStream = parameters.Payload!;
var header = parameters.Header!;
ObjectId? objectId;
List<ObjectId> sentObjectIds = [];
ModelsV2.Object? currentObject;
var partSize = (int)NetworkSettings["MaxObjectSize"];
var networkSettings = await Context.NetmapService!.GetNetworkSettingsAsync(ctx);
var partSize = (int)networkSettings.MaxObjectSize;
var buffer = new byte[partSize];
var largeObject = new LargeObject(header.ContainerId);
@ -109,8 +208,6 @@ public partial class Client
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
var bytesCount = await payloadStream.ReadAsync(buffer, 0, partSize);
split.Previous = sentObjectIds.LastOrDefault();
@ -124,37 +221,41 @@ public partial class Client
if (largeObject.PayloadLength == fullLength)
break;
objectId = await PutSingleObjectAsync(currentObject, cancellationToken);
objectId = await PutSingleObjectAsync(currentObject, ctx);
sentObjectIds.Add(objectId!);
}
if (sentObjectIds.Any())
if (sentObjectIds.Count != 0)
{
largeObject.CalculateHash();
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<ObjectId> PutStreamObject(ObjectHeader header, Stream payload, CancellationToken cancellationToken)
private async Task<ObjectId> 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 +279,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 +300,7 @@ public partial class Client
};
request.VerifyHeader = null;
request.Sign(_key);
request.Sign(Context.Key);
await stream.Write(request);
}
@ -211,168 +310,11 @@ public partial class Client
return ObjectId.FromHash(response.Body.ObjectId.Value.ToByteArray());
}
public async Task<ObjectId> PutSingleObjectAsync(ModelsV2.Object @object, CancellationToken cancellationToken = default)
// TODO: add implementation with stream writer!
private async Task<Object.Object> GetObject(GetRequest request, Context ctx)
{
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, null, null, cancellationToken);
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();
}
grpcHeader.Split.Previous = split.Previous?.ToGrpcMessage();
}
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())),
};
using var stream = GetObjectInit(request, ctx);
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<ObjectId> 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<Object.Object> GetObject(GetRequest request)
{
using var stream = GetObjectInit(request);
var obj = await stream.ReadHeader();
var payload = new byte[obj.Header.PayloadLength];
var offset = 0L;
@ -392,57 +334,134 @@ public partial class Client
return obj;
}
private ObjectReader GetObjectInit(GetRequest initRequest)
private ObjectReader GetObjectInit(GetRequest initRequest, Context ctx)
{
if (initRequest is null)
throw new ArgumentNullException(nameof(initRequest));
return new ObjectReader
{
Call = _objectServiceClient!.Get(initRequest)
};
var call = objectServiceClient!.Get(initRequest, null, ctx.Deadline, ctx.CancellationToken);
return new ObjectReader(call);
}
private async Task<ObjectStreamer> PutObjectInit(PutRequest initRequest)
private async Task<ObjectStreamer> PutObjectInit(PutRequest initRequest, Context ctx)
{
if (initRequest is null)
{
throw new ArgumentNullException(nameof(initRequest));
}
var call = _objectServiceClient!.Put();
var call = objectServiceClient!.Put(null, ctx.Deadline, ctx.CancellationToken);
await call.RequestStream.WriteAsync(initRequest);
return new ObjectStreamer(call);
}
private async IAsyncEnumerable<ObjectID> SearchObjects(SearchRequest request)
private async IAsyncEnumerable<ObjectID> SearchObjects(SearchRequest request, Context ctx)
{
using var stream = GetSearchReader(request);
var ids = await stream.Read();
while (ids is not null)
using var stream = GetSearchReader(request, ctx);
while (true)
{
var ids = await stream.Read(ctx.CancellationToken);
if (ids == null)
break;
foreach (var oid in ids)
{
yield return oid;
}
ids = await stream.Read();
}
}
private SearchReader GetSearchReader(SearchRequest initRequest)
private SearchReader GetSearchReader(SearchRequest initRequest, Context ctx)
{
if (initRequest is null)
{
throw new ArgumentNullException(nameof(initRequest));
}
return new SearchReader(_objectServiceClient!.Search(initRequest));
var call = objectServiceClient!.Search(initRequest, null, ctx.Deadline, ctx.CancellationToken);
return new SearchReader(call);
}
private Object.Object CreateObject(ModelsV2.Object @object)
{
var grpcHeader = @object.Header.ToGrpcMessage();
grpcHeader.OwnerId = Context.Owner.ToGrpcMessage();
grpcHeader.Version = Context.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.Count != 0)
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(Context.Key.PublicKey()),
Sign = ByteString.CopyFrom(Context.Key.SignData(grpcHeader.Split.Parent.ToByteArray())),
};
split.Parent = grpcHeader.Split.Parent.ToModel();
}
grpcHeader.Split.Previous = split.Previous?.ToGrpcMessage();
}
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(Context.Key.PublicKey()),
Sign = ByteString.CopyFrom(Context.Key.SignData(obj.ObjectId.ToByteArray())),
};
return obj;
}
public Checksum Sha256Checksum(byte[] data)
private Header CreateHeader(ObjectHeader header, byte[]? payload)
{
var grpcHeader = header.ToGrpcMessage();
grpcHeader.OwnerId = Context.Owner.ToGrpcMessage();
grpcHeader.Version = Context.Version.ToGrpcMessage();
if (header.PayloadCheckSum != null)
grpcHeader.PayloadHash = Sha256Checksum(header.PayloadCheckSum);
else if (payload != null)
grpcHeader.PayloadHash = Sha256Checksum(payload);
return grpcHeader;
}
private static Checksum Sha256Checksum(byte[] data)
{
return new Checksum
{
@ -450,8 +469,14 @@ public partial class Client
Sum = ByteString.CopyFrom(data.Sha256())
};
}
private async Task<Session.SessionToken> GetOrCreateSession(Context ctx)
{
if (string.IsNullOrEmpty(ctx.SessionToken))
{
return await Context.SessionService!.CreateSessionAsync(uint.MaxValue, ctx);
}
return Convert.FromBase64String(ctx.SessionToken).DeserializeSessionToken();
}
}

View file

@ -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<SearchResponse> call) : IDi
{
public AsyncServerStreamingCall<SearchResponse> Call { get; private set; } = call;
public async Task<List<ObjectID>?> Read()
public async Task<List<ObjectID>?> 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();

View file

@ -5,28 +5,38 @@ using FrostFS.Session;
namespace FrostFS.SDK.ClientV2;
public partial class Client
{
private async Task<SessionToken> 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<SessionToken> 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<SessionToken> CreateSession(CreateRequest request)
internal async Task<SessionToken> 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
{

View file

@ -0,0 +1,40 @@
using FrostFS.SDK.ModelsV2;
using Grpc.Net.Client;
using System;
using System.Security.Cryptography;
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 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();
}
}
}

View file

@ -0,0 +1,8 @@
namespace FrostFS.SDK.ClientV2;
internal class ContextAccessor(ClientEnvironment context)
{
protected ClientEnvironment Context { get; set; } = context;
}

View file

@ -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<string, object> UnnamedSettings { get; } = [];
}

View file

@ -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;
}
}

View file

@ -41,10 +41,10 @@ namespace System
}
/// <summary>Create an Index pointing at first element.</summary>
public static Index Start => new Index(0);
public static Index Start => new(0);
/// <summary>Create an Index pointing at beyond last element.</summary>
public static Index End => new Index(~0);
public static Index End => new(~0);
/// <summary>Create an Index from the start at the position indicated by the value.</summary>
/// <param name="value">The index value from the start.</param>
@ -116,7 +116,7 @@ namespace System
/// <summary>Indicates whether the current Index object is equal to another object of the same type.</summary>
/// <param name="value">An object to compare with this object</param>
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;
/// <summary>Indicates whether the current Index object is equal to another Index object.</summary>
/// <param name="other">An object to compare with this object</param>
@ -147,22 +147,16 @@ namespace System
/// int[] subArray2 = someArray[1..^0]; // { 2, 3, 4, 5 }
/// </code>
/// </remarks>
internal readonly struct Range : IEquatable<Range>
/// <remarks>Construct a Range object using the start and end indexes.</remarks>
/// <param name="start">Represent the inclusive start index of the range.</param>
/// <param name="end">Represent the exclusive end index of the range.</param>
internal readonly struct Range(Index start, Index end) : IEquatable<Range>
{
/// <summary>Represent the inclusive start index of the Range.</summary>
public Index Start { get; }
public Index Start { get; } = start;
/// <summary>Represent the exclusive end index of the Range.</summary>
public Index End { get; }
/// <summary>Construct a Range object using the start and end indexes.</summary>
/// <param name="start">Represent the inclusive start index of the range.</param>
/// <param name="end">Represent the exclusive end index of the range.</param>
public Range(Index start, Index end)
{
Start = start;
End = end;
}
public Index End { get; } = end;
/// <summary>Indicates whether the current Range object is equal to another object of the same type.</summary>
/// <param name="value">An object to compare with this object</param>
@ -188,13 +182,13 @@ namespace System
}
/// <summary>Create a Range object starting from start index to the end of the collection.</summary>
public static Range StartAt(Index start) => new Range(start, Index.End);
public static Range StartAt(Index start) => new(start, Index.End);
/// <summary>Create a Range object starting from first element in the collection to the end Index.</summary>
public static Range EndAt(Index end) => new Range(Index.Start, end);
public static Range EndAt(Index end) => new(Index.Start, end);
/// <summary>Create a Range object starting from first element to the end.</summary>
public static Range All => new Range(Index.Start, Index.End);
public static Range All => new(Index.Start, Index.End);
/// <summary>Calculate the start offset and length of range object using a collection length.</summary>
/// <param name="length">The length of the collection that the range will be used with. length has to be a positive value.</param>
@ -252,7 +246,7 @@ namespace System.Runtime.CompilerServices
if (length == 0)
{
return Array.Empty<T>();
return [];
}
var dest = new T[length];

View file

@ -17,13 +17,15 @@ public static class RequestConstructor
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 +36,7 @@ public static class RequestConstructor
},
Verb = verb
};
request.MetaHeader.SessionToken.Body.Object = ctx;
request.MetaHeader.SessionToken.Signature = key.SignMessagePart(request.MetaHeader.SessionToken.Body);
}

View file

@ -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);
}
}

View file

@ -68,7 +68,7 @@ public static class Verifier
return false;
using var key = sig.Key.ToByteArray().LoadPublicKey();
var data2Verify = data is null ? Array.Empty<byte>() : 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());
}
/// <summary>
/// This method is intended for unit tests for request verification.
/// </summary>
/// <param name="request">Created by SDK request to gRpc proxy</param>
public static void CheckRequest(IRequest request)
{
if (!request.Verify())
throw new FormatException($"invalid response, type={request.GetType()}");
}
}

View file

@ -17,4 +17,4 @@ using System.Runtime.InteropServices;
[assembly: Guid("08a8487e-39ce-41fb-9c24-13f73ff2bde0")]
[assembly: InternalsVisibleToAttribute("FrostFS.SDK.Cryptography.Test")]
[assembly: InternalsVisibleTo("FrostFS.SDK.Cryptography.Test")]

View file

@ -34,9 +34,11 @@ public static class Base58
byte[] checksum = data.ToArray().Sha256().Sha256();
Span<byte> buffer = stackalloc byte[data.Length + 4];
data.CopyTo(buffer);
checksum[..4].AsSpan().CopyTo(buffer[data.Length..]);
var ret = Encode(buffer);
buffer.Clear();
return ret;
}

View file

@ -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)
{

View file

@ -6,6 +6,10 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BouncyCastle.Cryptography" Version="2.4.0" />
<PackageReference Include="Google.Protobuf" Version="3.27.0" />

View file

@ -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;

View file

@ -41,10 +41,10 @@ namespace System
}
/// <summary>Create an Index pointing at first element.</summary>
public static Index Start => new Index(0);
public static Index Start => new(0);
/// <summary>Create an Index pointing at beyond last element.</summary>
public static Index End => new Index(~0);
public static Index End => new(~0);
/// <summary>Create an Index from the start at the position indicated by the value.</summary>
/// <param name="value">The index value from the start.</param>
@ -116,7 +116,7 @@ namespace System
/// <summary>Indicates whether the current Index object is equal to another object of the same type.</summary>
/// <param name="value">An object to compare with this object</param>
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;
/// <summary>Indicates whether the current Index object is equal to another Index object.</summary>
/// <param name="other">An object to compare with this object</param>
@ -147,22 +147,16 @@ namespace System
/// int[] subArray2 = someArray[1..^0]; // { 2, 3, 4, 5 }
/// </code>
/// </remarks>
internal readonly struct Range : IEquatable<Range>
/// <remarks>Construct a Range object using the start and end indexes.</remarks>
/// <param name="start">Represent the inclusive start index of the range.</param>
/// <param name="end">Represent the exclusive end index of the range.</param>
internal readonly struct Range (Index start, Index end) : IEquatable<Range>
{
/// <summary>Represent the inclusive start index of the Range.</summary>
public Index Start { get; }
public Index Start { get; } = start;
/// <summary>Represent the exclusive end index of the Range.</summary>
public Index End { get; }
/// <summary>Construct a Range object using the start and end indexes.</summary>
/// <param name="start">Represent the inclusive start index of the range.</param>
/// <param name="end">Represent the exclusive end index of the range.</param>
public Range(Index start, Index end)
{
Start = start;
End = end;
}
public Index End { get; } = end;
/// <summary>Indicates whether the current Range object is equal to another object of the same type.</summary>
/// <param name="value">An object to compare with this object</param>
@ -188,13 +182,13 @@ namespace System
}
/// <summary>Create a Range object starting from start index to the end of the collection.</summary>
public static Range StartAt(Index start) => new Range(start, Index.End);
public static Range StartAt(Index start) => new(start, Index.End);
/// <summary>Create a Range object starting from first element in the collection to the end Index.</summary>
public static Range EndAt(Index end) => new Range(Index.Start, end);
public static Range EndAt(Index end) => new(Index.Start, end);
/// <summary>Create a Range object starting from first element to the end.</summary>
public static Range All => new Range(Index.Start, Index.End);
public static Range All => new(Index.Start, Index.End);
/// <summary>Calculate the start offset and length of range object using a collection length.</summary>
/// <param name="length">The length of the collection that the range will be used with. length has to be a positive value.</param>
@ -252,7 +246,7 @@ namespace System.Runtime.CompilerServices
if (length == 0)
{
return Array.Empty<T>();
return [];
}
var dest = new T[length];

View file

@ -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());
}
}

View file

@ -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; }
}

View file

@ -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;
}

View file

@ -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,

View file

@ -6,6 +6,10 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\FrostFS.SDK.Cryptography\FrostFS.SDK.Cryptography.csproj" />
</ItemGroup>

View file

@ -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;
}

View file

@ -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()
{

View file

@ -0,0 +1,10 @@
using System.Collections.Generic;
namespace FrostFS.SDK.ModelsV2.Netmap;
public class NetmapSnapshot(ulong epoch, IReadOnlyList<NodeInfo> nodeInfoCollection)
{
public ulong Epoch { get; private set; } = epoch;
public IReadOnlyList<NodeInfo> NodeInfoCollection { get; private set; } = nodeInfoCollection;
}

View file

@ -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<string> addresses,
IReadOnlyDictionary<string, string> attributes,
ReadOnlyMemory<byte> 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<string> Addresses { get; private set; }
public IReadOnlyDictionary<string, string> Attributes { get; private set; }
public ReadOnlyMemory<byte> PublicKey { get; private set; }
}

View file

@ -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<PlacementPolicy>
{
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;
}
}

View file

@ -0,0 +1,9 @@
using System;
using System.Threading.Tasks;
namespace FrostFS.SDK.ModelsV2;
public interface IObjectReader : IDisposable
{
Task<byte[]?> ReadChunk();
}

View file

@ -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
};

View file

@ -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,

View file

@ -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)
{

View file

@ -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; }
}

View file

@ -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;
}

View file

@ -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; }
}

View file

@ -0,0 +1,7 @@
namespace FrostFS.SDK.ModelsV2;
public enum SignatureScheme {
EcdsaSha512,
EcdsaRfc6979Sha256,
EcdsaRfc6979Sha256WalletConnect
}

View file

@ -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 CreateFromBinary(byte[] binaryData)
{
return new SplitId(binaryData);
}
public static SplitId CreateFromString(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();
}
}

View file

@ -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<ObjectId> 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();
}
}

View file

@ -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()
{

View file

@ -6,6 +6,10 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.26.1" />
<PackageReference Include="Grpc.Net.Client" Version="2.62.0" />

View file

@ -1,6 +1,6 @@
namespace FrostFS.Session;
public interface IRequest : IVerificableMessage
public interface IRequest : IVerifiableMessage
{
RequestMetaHeader MetaHeader { get; set; }
RequestVerificationHeader VerifyHeader { get; set; }

View file

@ -1,6 +1,6 @@
namespace FrostFS.Session;
public interface IResponse : IVerificableMessage
public interface IResponse : IVerifiableMessage
{
ResponseMetaHeader MetaHeader { get; set; }
ResponseVerificationHeader VerifyHeader { get; set; }

View file

@ -2,7 +2,7 @@ using Google.Protobuf;
namespace FrostFS.Session;
public interface IVerificableMessage : IMessage
public interface IVerifiableMessage : IMessage
{
IMetaHeader GetMetaHeader();
void SetMetaHeader(IMetaHeader metaHeader);

View file

@ -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;
}

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -0,0 +1,87 @@
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,
null,
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,
null,
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()
// {
// }
}

View file

@ -0,0 +1,229 @@
using FrostFS.SDK.ClientV2;
using FrostFS.SDK.ClientV2.Interfaces;
using FrostFS.SDK.ModelsV2;
using FrostFS.SDK.ModelsV2.Enums;
using FrostFS.SDK.ModelsV2.Netmap;
using Grpc.Core;
using Grpc.Net.Client;
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 channelOptions = new GrpcChannelOptions
{
Credentials = ChannelCredentials.Insecure,
HttpHandler = new HttpClientHandler()
};
using var fsClient = Client.GetInstance(GetOptions(this.key, this.url), channelOptions);
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 channelOptions = new GrpcChannelOptions
{
Credentials = ChannelCredentials.Insecure,
HttpHandler = new HttpClientHandler()
};
using var fsClient = Client.GetInstance(GetOptions(this.key, this.url), channelOptions);
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 channelOptions = new GrpcChannelOptions
{
Credentials = ChannelCredentials.Insecure,
HttpHandler = new HttpClientHandler()
};
using var fsClient = Client.GetInstance(GetOptions(this.key, this.url), channelOptions);
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 _ in fsClient.ListContainersAsync())
{
Assert.Fail("Containers exist");
}
}
[Fact]
public async void ClientCutScenarioTest()
{
var channelOptions = new GrpcChannelOptions
{
Credentials = ChannelCredentials.Insecure,
HttpHandler = new HttpClientHandler()
};
using var fsClient = Client.GetInstance(GetOptions(this.key, this.url), channelOptions);
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<ClientSettings> 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<ModelsV2.Container> 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;
}
}
}
}

View file

@ -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<ContainerService.ContainerServiceClient> GetMock();
}

View file

@ -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<ContainerService.ContainerServiceClient> GetMock()
{
var mock = new Mock<ContainerService.ContainerServiceClient>();
var v = mock.Setup(x => x.DeleteAsync(
It.IsAny<DeleteRequest>(),
It.IsAny<Metadata>(),
It.IsAny<DateTime?>(),
It.IsAny<CancellationToken>()));
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<Object.DeleteResponse>(
Task.FromResult(deleteResponse),
Task.FromResult(metadata),
() => new Grpc.Core.Status(StatusCode.OK, string.Empty),
() => metadata,
() => { });
});
return mock;
}
}
// objectServiceClientMock.Setup(x => x.Head(It.IsAny<Object.HeadRequest>(), It.IsAny<Metadata>(), It.IsAny<DateTime>(), It.IsAny<CancellationToken>()))
// .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]) }
// }
// }
// };
// });

View file

@ -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<ContainerService.ContainerServiceClient> GetMock()
{
var mock = new Mock<ContainerService.ContainerServiceClient>();
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<GetResponse>(
Task.FromResult(getResponse),
Task.FromResult(metadata),
() => new Grpc.Core.Status(StatusCode.OK, string.Empty),
() => metadata,
() => { });
mock.Setup(x => x.GetAsync(
It.IsAny<GetRequest>(),
It.IsAny<Metadata>(),
It.IsAny<DateTime?>(),
It.IsAny<CancellationToken>()))
.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<Metadata>(), It.IsAny<DateTime>(), It.IsAny<CancellationToken>()))
// .Returns((Metadata m, DateTime dt, CancellationToken ct) =>
// {
// return new AsyncClientStreamingCall<FrostFS.Object.PutRequest, FrostFS.Object.PutResponse>(null, null, null, null, null, null);
// //IClientStreamWriter<TRequest> requestStream, Task<TResponse> responseAsync, Task<Metadata> responseHeadersAsync, Func<Status> getStatusFunc, Func<Metadata> 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<Object.HeadRequest>(), It.IsAny<Metadata>(), It.IsAny<DateTime>(), It.IsAny<CancellationToken>()))
// .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<Object.DeleteRequest>(), It.IsAny<Metadata>(), It.IsAny<DateTime?>(), It.IsAny<CancellationToken>()))
// .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<byte>()))
// // ByteString.CopyFrom(_key.SignData(grpcHeader.Split.Parent.ToByteArray())),
// }
// }
// };
// });

View file

@ -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<ContainerService.ContainerServiceClient> GetMock()
{
var mock = new Mock<ContainerService.ContainerServiceClient>();
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<PutResponse>(
Task.FromResult(response),
Task.FromResult(metadata),
() => new Grpc.Core.Status(StatusCode.OK, string.Empty),
() => metadata,
() => { });
mock.Setup(x => x.PutAsync(
It.IsAny<PutRequest>(),
It.IsAny<Metadata>(),
It.IsAny<DateTime?>(),
It.IsAny<CancellationToken>()))
.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;
}
}

View file

@ -0,0 +1,14 @@
using Moq;
using FrostFS.Netmap;
namespace FrostFS.SDK.Tests;
public class NetmapMock(string key) : ServiceBase(key)
{
public Mock<NetmapService.NetmapServiceClient> GetMock()
{
var mock = new Mock<NetmapService.NetmapServiceClient>();
return mock;
}
}

View file

@ -0,0 +1,14 @@
using Moq;
using FrostFS.Object;
namespace FrostFS.SDK.Tests;
public class ObjectMock(string key) : ServiceBase(key)
{
public Mock<ObjectService.ObjectServiceClient> GetMock()
{
var mock = new Mock<ObjectService.ObjectServiceClient>();
return mock;
}
}

View file

@ -0,0 +1,14 @@
using Moq;
using FrostFS.Session;
namespace FrostFS.SDK.Tests;
public class SessionMock(string key) : ServiceBase(key)
{
public Mock<SessionService.SessionServiceClient> GetMock()
{
var mock = new Mock<SessionService.SessionServiceClient>();
return mock;
}
}