[#20] Client: Optimize memory usage #21

Merged
PavelGrossSpb merged 2 commits from PavelGrossSpb/frostfs-sdk-csharp:misc/memory_optimization into master 2024-09-04 19:51:24 +00:00
46 changed files with 596 additions and 372 deletions
Showing only changes of commit 0ddde467cd - Show all commits

View file

@ -0,0 +1,25 @@

using Microsoft.Extensions.Caching.Memory;
namespace FrostFS.SDK.ClientV2
{
internal static class Cache
{
private static readonly IMemoryCache _ownersCache = new MemoryCache(new MemoryCacheOptions
{
// TODO: get from options?
SizeLimit = 256
});
private static readonly IMemoryCache _containersCache = new MemoryCache(new MemoryCacheOptions
{
// TODO: get from options?
SizeLimit = 1024
});
internal static IMemoryCache Owners => _ownersCache;
internal static IMemoryCache Containers => _containersCache;
}
}

View file

@ -125,7 +125,7 @@ public class Client : IFrostFSClient
public IAsyncEnumerable<ContainerId> ListContainersAsync(PrmContainerGetAll? args = null) public IAsyncEnumerable<ContainerId> ListContainersAsync(PrmContainerGetAll? args = null)
{ {
args = args ?? new PrmContainerGetAll(); args ??= new PrmContainerGetAll();
var service = GetContainerService(args); var service = GetContainerService(args);
return service.ListContainersAsync(args); return service.ListContainersAsync(args);
} }
@ -223,7 +223,10 @@ public class Client : IFrostFSClient
#region ToolsImplementation #region ToolsImplementation
public ObjectId CalculateObjectId(ObjectHeader header) public ObjectId CalculateObjectId(ObjectHeader header)
{ {
return new ObjectTools(ClientCtx).CalculateObjectId(header); if (header == null)
throw new ArgumentNullException(nameof(header));
return ObjectTools.CalculateObjectId(header, ClientCtx);
} }
#endregion #endregion
@ -236,7 +239,6 @@ public class Client : IFrostFSClient
if (!localNodeInfo.Version.IsSupported(ClientCtx.Version)) if (!localNodeInfo.Version.IsSupported(ClientCtx.Version))
{ {
var msg = $"FrostFS {localNodeInfo.Version} is not supported."; var msg = $"FrostFS {localNodeInfo.Version} is not supported.";
Console.WriteLine(msg);
throw new ApplicationException(msg); throw new ApplicationException(msg);
} }
} }

View file

@ -0,0 +1,13 @@
using FrostFS.SDK.Cryptography;
using Google.Protobuf;
using System.Security.Cryptography;
namespace FrostFS.SDK.ClientV2
{
public class ClientKey(ECDsa key)
{
internal ECDsa ECDsaKey { get; } = key;
internal ByteString PublicKeyProto { get; } = ByteString.CopyFrom(key.PublicKey());
}
}

View file

@ -1,12 +1,9 @@
using System;
using FrostFS.SDK.ModelsV2; using FrostFS.SDK.ModelsV2;
using System;
public class ResponseException : Exception namespace FrostFS.SDK.ClientV2;
public class ResponseException(ResponseStatus status) : Exception()
{ {
public Status Status { get; set; } public ResponseStatus Status { get; set; } = status;
public ResponseException(Status status) : base()
{
Status = status;
}
} }

View file

@ -12,8 +12,11 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" /> <PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="8.0.2" /> <PackageReference Include="Microsoft.Extensions.Options" Version="8.0.2" />
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="8.0.1" /> <PackageReference Include="System.Diagnostics.DiagnosticSource" Version="8.0.1" />
<PackageReference Include="System.Runtime.Caching" Version="8.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -10,12 +10,12 @@ namespace FrostFS.SDK.ClientV2.Mappers.GRPC;
public static class ContainerMapper public static class ContainerMapper
{ {
public static Container.Container ToGrpcMessage(this ModelsV2.Container container) public static Container.Container ToMessage(this ModelsV2.Container container)
{ {
return new Container.Container return new Container.Container
{ {
BasicAcl = (uint)container.BasicAcl, BasicAcl = (uint)container.BasicAcl,
PlacementPolicy = container.PlacementPolicy.ToGrpcMessage(), PlacementPolicy = container.PlacementPolicy.ToMessage(),
Nonce = ByteString.CopyFrom(container.Nonce.ToBytes()) Nonce = ByteString.CopyFrom(container.Nonce.ToBytes())
}; };
} }

View file

@ -2,16 +2,29 @@ using FrostFS.Refs;
using FrostFS.SDK.Cryptography; using FrostFS.SDK.Cryptography;
using FrostFS.SDK.ModelsV2; using FrostFS.SDK.ModelsV2;
using Google.Protobuf; using Google.Protobuf;
using Microsoft.Extensions.Caching.Memory;
using System;
namespace FrostFS.SDK.ClientV2.Mappers.GRPC; namespace FrostFS.SDK.ClientV2.Mappers.GRPC;
public static class ContainerIdMapper public static class ContainerIdMapper
{ {
public static ContainerID ToGrpcMessage(this ContainerId containerId) private static readonly MemoryCacheEntryOptions _oneHourExpiration = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromHours(1))
.SetSize(1);
public static ContainerID ToMessage(this ContainerId model)
{ {
return new ContainerID if (!Cache.Owners.TryGetValue(model, out ContainerID? message))
dstepanov-yadro marked this conversation as resolved Outdated

Why containerID from owners cache?

Why containerID from owners cache?

This is a typo, sure. Fixed.

This is a typo, sure. Fixed.
{ {
Value = ByteString.CopyFrom(Base58.Decode(containerId.Value)) message = new ContainerID
}; {
Value = ByteString.CopyFrom(Base58.Decode(model.Value))
};
Cache.Owners.Set(model, message, _oneHourExpiration);
}
return message!;
} }
} }

View file

@ -1,21 +1,15 @@
using FrostFS.SDK.ModelsV2; using FrostFS.SDK.ModelsV2;
using FrostFS.Session; using FrostFS.Session;
using Version = FrostFS.Refs.Version;
namespace FrostFS.SDK.ClientV2.Mappers.GRPC; namespace FrostFS.SDK.ClientV2.Mappers.GRPC;
public static class MetaHeaderMapper public static class MetaHeaderMapper
{ {
public static RequestMetaHeader ToGrpcMessage(this MetaHeader metaHeader) public static RequestMetaHeader ToMessage(this MetaHeader metaHeader)
{ {
return new RequestMetaHeader return new RequestMetaHeader
{ {
Version = new Version Version = metaHeader.Version.ToMessage(),
{
Major = (uint)metaHeader.Version.Major,
Minor = (uint)metaHeader.Version.Minor,
},
Epoch = (uint)metaHeader.Epoch, Epoch = (uint)metaHeader.Epoch,
Ttl = (uint)metaHeader.Ttl Ttl = (uint)metaHeader.Ttl
}; };

View file

@ -10,6 +10,8 @@ public static class NetmapMapper
{ {
return new NetmapSnapshot( return new NetmapSnapshot(
netmap.Body.Netmap.Epoch, netmap.Body.Netmap.Epoch,
netmap.Body.Netmap.Nodes.Select(n => n.ToModel(netmap.MetaHeader.Version)).ToArray()); netmap.Body.Netmap.Nodes
.Select(n => n.ToModel(netmap.MetaHeader.Version))
.ToArray());
} }
} }

View file

@ -5,7 +5,7 @@ namespace FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap;
public static class PlacementPolicyMapper public static class PlacementPolicyMapper
{ {
public static PlacementPolicy ToGrpcMessage(this ModelsV2.Netmap.PlacementPolicy placementPolicy) public static PlacementPolicy ToMessage(this ModelsV2.Netmap.PlacementPolicy placementPolicy)
{ {
var pp = new PlacementPolicy var pp = new PlacementPolicy
{ {
@ -14,9 +14,10 @@ public static class PlacementPolicyMapper
Replicas = { }, Replicas = { },
Unique = placementPolicy.Unique Unique = placementPolicy.Unique
}; };
foreach (var replica in placementPolicy.Replicas) foreach (var replica in placementPolicy.Replicas)
{ {
pp.Replicas.Add(replica.ToGrpcMessage()); pp.Replicas.Add(replica.ToMessage());
} }
return pp; return pp;

View file

@ -4,7 +4,7 @@ namespace FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap;
public static class ReplicaMapper public static class ReplicaMapper
{ {
public static Replica ToGrpcMessage(this ModelsV2.Netmap.Replica replica) public static Replica ToMessage(this ModelsV2.Netmap.Replica replica)
{ {
return new Replica return new Replica
{ {

View file

@ -5,7 +5,7 @@ namespace FrostFS.SDK.ClientV2.Mappers.GRPC;
public static class ObjectAttributeMapper public static class ObjectAttributeMapper
{ {
public static Header.Types.Attribute ToGrpcMessage(this ObjectAttribute attribute) public static Header.Types.Attribute ToMessage(this ObjectAttribute attribute)
{ {
return new Header.Types.Attribute return new Header.Types.Attribute
{ {

View file

@ -7,7 +7,7 @@ namespace FrostFS.SDK.ClientV2.Mappers.GRPC;
public static class ObjectFilterMapper public static class ObjectFilterMapper
{ {
public static SearchRequest.Types.Body.Types.Filter ToGrpcMessage(this IObjectFilter filter) public static SearchRequest.Types.Body.Types.Filter ToMessage(this IObjectFilter filter)
{ {
var objMatchTypeName = filter.MatchType switch var objMatchTypeName = filter.MatchType switch
{ {

View file

@ -10,7 +10,7 @@ namespace FrostFS.SDK.ClientV2.Mappers.GRPC;
public static class ObjectHeaderMapper public static class ObjectHeaderMapper
{ {
public static Header ToGrpcMessage(this ObjectHeader header) public static Header ToMessage(this ObjectHeader header)
{ {
var objTypeName = header.ObjectType switch var objTypeName = header.ObjectType switch
{ {
@ -22,14 +22,16 @@ public static class ObjectHeaderMapper
var head = new Header var head = new Header
{ {
ContainerId = header.ContainerId.ToGrpcMessage(), OwnerId = header!.OwnerId != null ? header.OwnerId.ToMessage() : null,
Version = header!.Version != null ? header.Version.ToMessage() : null,
ContainerId = header.ContainerId.ToMessage(),
ObjectType = objTypeName, ObjectType = objTypeName,
PayloadLength = header.PayloadLength PayloadLength = header.PayloadLength
}; };
foreach (var attribute in header.Attributes) foreach (var attribute in header.Attributes)
{ {
head.Attributes.Add(attribute.ToGrpcMessage()); head.Attributes.Add(attribute.ToMessage());
} }
var split = header.Split; var split = header.Split;
@ -37,15 +39,8 @@ public static class ObjectHeaderMapper
{ {
head.Split = new Header.Types.Split head.Split = new Header.Types.Split
{ {
Parent = split.Parent?.ToGrpcMessage(),
ParentSignature = split.ParentSignature?.ToGrpcMessage(),
ParentHeader = split.ParentHeader?.ToGrpcMessage(),
Previous = split.Previous?.ToGrpcMessage(),
SplitId = split.SplitId != null ? ByteString.CopyFrom(split.SplitId.ToBinary()) : null SplitId = split.SplitId != null ? ByteString.CopyFrom(split.SplitId.ToBinary()) : null
}; };
if (split.Children != null && split.Children.Count != 0)
head.Split.Children.AddRange(split.Children.Select(id => id.ToGrpcMessage()));
} }
return head; return head;

View file

@ -6,7 +6,7 @@ namespace FrostFS.SDK.ClientV2.Mappers.GRPC;
public static class ObjectIdMapper public static class ObjectIdMapper
{ {
public static ObjectID ToGrpcMessage(this ObjectId objectId) public static ObjectID ToMessage(this ObjectId objectId)
{ {
return new ObjectID return new ObjectID
{ {

View file

@ -1,21 +1,42 @@
using FrostFS.Refs; using FrostFS.Refs;
using FrostFS.SDK.Cryptography;
using FrostFS.SDK.ModelsV2; using FrostFS.SDK.ModelsV2;
using Google.Protobuf; using Google.Protobuf;
using Microsoft.Extensions.Caching.Memory;
using System;
namespace FrostFS.SDK.ClientV2.Mappers.GRPC; namespace FrostFS.SDK.ClientV2.Mappers.GRPC;
public static class OwnerIdMapper public static class OwnerIdMapper
{ {
public static OwnerID ToGrpcMessage(this OwnerId ownerId) private static readonly MemoryCacheEntryOptions _oneHourExpiration = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromHours(1))
.SetSize(1);
public static OwnerID ToMessage(this OwnerId model)
{ {
return new OwnerID if (!Cache.Owners.TryGetValue(model, out OwnerID? message))
{ {
Value = ByteString.CopyFrom(ownerId.ToHash()) message = new OwnerID
}; {
Value = ByteString.CopyFrom(model.ToHash())
};
Cache.Owners.Set(model, message, _oneHourExpiration);
}
return message!;
} }
public static OwnerId ToModel(this OwnerID ownerId) public static OwnerId ToModel(this OwnerID message)
{ {
return new OwnerId(ownerId.ToString()); if (!Cache.Owners.TryGetValue(message, out OwnerId? model))
{
model = new OwnerId(Base58.Encode(message.Value.ToByteArray()));
Cache.Owners.Set(message, model, _oneHourExpiration);
}
return model!;
} }
} }

View file

@ -6,14 +6,15 @@ namespace FrostFS.SDK.ClientV2.Mappers.GRPC;
public static class SignatureMapper public static class SignatureMapper
{ {
public static Refs.Signature ToGrpcMessage(this Signature signature) public static Refs.Signature ToMessage(this Signature signature)
{ {
var scheme = signature.Scheme switch var scheme = signature.Scheme switch
{ {
SignatureScheme.EcdsaRfc6979Sha256 => Refs.SignatureScheme.EcdsaRfc6979Sha256, SignatureScheme.EcdsaRfc6979Sha256 => Refs.SignatureScheme.EcdsaRfc6979Sha256,
SignatureScheme.EcdsaRfc6979Sha256WalletConnect => Refs.SignatureScheme.EcdsaRfc6979Sha256WalletConnect, SignatureScheme.EcdsaRfc6979Sha256WalletConnect => Refs.SignatureScheme.EcdsaRfc6979Sha256WalletConnect,
SignatureScheme.EcdsaSha512 => Refs.SignatureScheme.EcdsaSha512, SignatureScheme.EcdsaSha512 => Refs.SignatureScheme.EcdsaSha512,
_ => throw new ArgumentException(message: $"Unexpected enum value: {signature.Scheme}", paramName: nameof(signature.Scheme)) _ => throw new ArgumentException(message: $"Unexpected enum value: {signature.Scheme}",
paramName: nameof(signature.Scheme))
}; };
return new Refs.Signature return new Refs.Signature

View file

@ -5,13 +5,15 @@ namespace FrostFS.SDK.ClientV2.Mappers.GRPC;
public static class StatusMapper public static class StatusMapper
{ {
public static ModelsV2.Status ToModel(this Status.Status status) public static ModelsV2.ResponseStatus ToModel(this Status.Status status)
{ {
if (status is null) return new ModelsV2.Status(StatusCode.Success); if (status is null)
return new ModelsV2.ResponseStatus(StatusCode.Success);
var codeName = Enum.GetName(typeof(StatusCode), status.Code); var codeName = Enum.GetName(typeof(StatusCode), status.Code);
return codeName is null return codeName is null
? throw new ArgumentException($"Unknown StatusCode. Value: '{status.Code}'.") ? throw new ArgumentException($"Unknown StatusCode. Value: '{status.Code}'.")
: new ModelsV2.Status((StatusCode)Enum.Parse(typeof(StatusCode), codeName), status.Message); : new ModelsV2.ResponseStatus((StatusCode)Enum.Parse(typeof(StatusCode), codeName), status.Message);
} }
} }

View file

@ -1,20 +1,74 @@
using System.Collections;
using System.Threading;
using Version = FrostFS.Refs.Version; using Version = FrostFS.Refs.Version;
namespace FrostFS.SDK.ClientV2.Mappers.GRPC; namespace FrostFS.SDK.ClientV2.Mappers.GRPC;
public static class VersionMapper public static class VersionMapper
{ {
public static Version ToGrpcMessage(this ModelsV2.Version version) private static readonly Hashtable _cacheMessages = [];
private static readonly Hashtable _cacheModels = [];
private static SpinLock _spinlock = new();
public static Version ToMessage(this ModelsV2.Version model)
{ {
return new Version var key = model.Major << 16 + model.Minor;
if (!_cacheMessages.ContainsKey(key))
{ {
Major = (uint)version.Major, bool lockTaken = false;
Minor = (uint)version.Minor try
}; {
_spinlock.Enter(ref lockTaken);
var message = new Version
{
Major = (uint)model.Major,
Minor = (uint)model.Minor
};
_cacheMessages.Add(key, message);
return message;
}
catch (System.ArgumentException)
{
// ignore attempt to add duplicate error.
}
finally
{
if (lockTaken)
_spinlock.Exit(false);
}
}
return (Version)_cacheMessages[key];
} }
public static ModelsV2.Version ToModel(this Version version) public static ModelsV2.Version ToModel(this Version message)
{ {
return new ModelsV2.Version((int)version.Major, (int)version.Minor); var key = (int)message.Major << 16 + (int)message.Minor;
if (!_cacheModels.ContainsKey(key))
{
bool lockTaken = false;
try
{
_spinlock.Enter(ref lockTaken);
var model = new ModelsV2.Version((int)message.Major, (int)message.Minor);
_cacheModels.Add(key, model);
return model;
}
catch (System.ArgumentException)
{
// ignore attempt to add duplicate error.
}
finally
{
if (lockTaken)
_spinlock.Exit(false);
}
}
return (ModelsV2.Version)_cacheModels[key];
} }
} }

View file

@ -41,4 +41,10 @@ public sealed class PrmObjectPut : IContext, ISessionToken
/// <inheritdoc /> /// <inheritdoc />
public SessionToken? SessionToken { get; set; } public SessionToken? SessionToken { get; set; }
internal int MaxObjectSizeCache { get; set; }
internal ulong CurrentStreamPosition { get; set; } = 0;
internal ulong FullLength { get; set; } = 0;
} }

View file

@ -17,7 +17,7 @@ internal class ContainerServiceProvider(ContainerService.ContainerServiceClient
{ {
internal async Task<ModelsV2.Container> GetContainerAsync(PrmContainerGet args) internal async Task<ModelsV2.Container> GetContainerAsync(PrmContainerGet args)
{ {
GetRequest request = GetContainerRequest(args.ContainerId.ToGrpcMessage(), args.XHeaders); GetRequest request = GetContainerRequest(args.ContainerId.ToMessage(), args.XHeaders);
var response = await service.GetAsync(request, null, args.Context!.Deadline, args.Context.CancellationToken); var response = await service.GetAsync(request, null, args.Context!.Deadline, args.Context.CancellationToken);
@ -34,12 +34,12 @@ internal class ContainerServiceProvider(ContainerService.ContainerServiceClient
{ {
Body = new ListRequest.Types.Body Body = new ListRequest.Types.Body
{ {
OwnerId = Context.Owner.ToGrpcMessage() OwnerId = Context.Owner.ToMessage()
} }
}; };
request.AddMetaHeader(args.XHeaders); request.AddMetaHeader(args.XHeaders);
request.Sign(Context.Key); request.Sign(Context.Key.ECDsaKey);
var response = await service.ListAsync(request, null, ctx.Deadline, ctx.CancellationToken); var response = await service.ListAsync(request, null, ctx.Deadline, ctx.CancellationToken);
@ -54,21 +54,21 @@ internal class ContainerServiceProvider(ContainerService.ContainerServiceClient
internal async Task<ContainerId> CreateContainerAsync(PrmContainerCreate args) internal async Task<ContainerId> CreateContainerAsync(PrmContainerCreate args)
{ {
var ctx = args.Context!; var ctx = args.Context!;
var grpcContainer = args.Container.ToGrpcMessage(); var grpcContainer = args.Container.ToMessage();
grpcContainer.OwnerId = Context.Owner.ToGrpcMessage(); grpcContainer.OwnerId = Context.Owner.ToMessage();
grpcContainer.Version = Context.Version.ToGrpcMessage(); grpcContainer.Version = Context.Version.ToMessage();
var request = new PutRequest var request = new PutRequest
{ {
Body = new PutRequest.Types.Body Body = new PutRequest.Types.Body
{ {
Container = grpcContainer, Container = grpcContainer,
Signature = Context.Key.SignRFC6979(grpcContainer) Signature = Context.Key.ECDsaKey.SignRFC6979(grpcContainer)
} }
}; };
request.AddMetaHeader(args.XHeaders); request.AddMetaHeader(args.XHeaders);
request.Sign(Context.Key); request.Sign(Context.Key.ECDsaKey);
var response = await service.PutAsync(request, null, ctx.Deadline, ctx.CancellationToken); var response = await service.PutAsync(request, null, ctx.Deadline, ctx.CancellationToken);
@ -86,14 +86,14 @@ internal class ContainerServiceProvider(ContainerService.ContainerServiceClient
{ {
Body = new DeleteRequest.Types.Body Body = new DeleteRequest.Types.Body
{ {
ContainerId = args.ContainerId.ToGrpcMessage(), ContainerId = args.ContainerId.ToMessage(),
Signature = Context.Key.SignRFC6979(args.ContainerId.ToGrpcMessage().Value) Signature = Context.Key.ECDsaKey.SignRFC6979(args.ContainerId.ToMessage().Value)
} }
}; };
request.AddMetaHeader(args.XHeaders); request.AddMetaHeader(args.XHeaders);
request.Sign(Context.Key); request.Sign(Context.Key.ECDsaKey);
var response = await service.DeleteAsync(request, null, ctx.Deadline, ctx.CancellationToken); var response = await service.DeleteAsync(request, null, ctx.Deadline, ctx.CancellationToken);
@ -113,7 +113,7 @@ internal class ContainerServiceProvider(ContainerService.ContainerServiceClient
}; };
request.AddMetaHeader(xHeaders); request.AddMetaHeader(xHeaders);
request.Sign(Context.Key); request.Sign(Context.Key.ECDsaKey);
return request; return request;
} }

View file

@ -49,7 +49,7 @@ internal class NetmapServiceProvider : ContextAccessor
}; };
request.AddMetaHeader(args.XHeaders); request.AddMetaHeader(args.XHeaders);
request.Sign(Context.Key); request.Sign(Context.Key.ECDsaKey);
var response = await netmapServiceClient.LocalNodeInfoAsync(request, null, ctx.Deadline, ctx.CancellationToken); var response = await netmapServiceClient.LocalNodeInfoAsync(request, null, ctx.Deadline, ctx.CancellationToken);
@ -63,7 +63,7 @@ internal class NetmapServiceProvider : ContextAccessor
var request = new NetworkInfoRequest(); var request = new NetworkInfoRequest();
request.AddMetaHeader(null); request.AddMetaHeader(null);
request.Sign(Context.Key); request.Sign(Context.Key.ECDsaKey);
var response = await netmapServiceClient.NetworkInfoAsync(request, null, ctx.Deadline, ctx.CancellationToken); var response = await netmapServiceClient.NetworkInfoAsync(request, null, ctx.Deadline, ctx.CancellationToken);
@ -79,7 +79,7 @@ internal class NetmapServiceProvider : ContextAccessor
var request = new NetmapSnapshotRequest(); var request = new NetmapSnapshotRequest();
request.AddMetaHeader(args.XHeaders); request.AddMetaHeader(args.XHeaders);
request.Sign(Context.Key); request.Sign(Context.Key.ECDsaKey);
var response = await netmapServiceClient.NetmapSnapshotAsync(request, null, ctx.Deadline, ctx.CancellationToken); var response = await netmapServiceClient.NetmapSnapshotAsync(request, null, ctx.Deadline, ctx.CancellationToken);

View file

@ -13,13 +13,13 @@ using FrostFS.Session;
using FrostFS.SDK.ModelsV2; using FrostFS.SDK.ModelsV2;
using FrostFS.SDK.ClientV2.Extensions; using FrostFS.SDK.ClientV2.Extensions;
using FrostFS.SDK.ClientV2.Parameters; using FrostFS.SDK.ClientV2.Parameters;
using System.Buffers;
namespace FrostFS.SDK.ClientV2; namespace FrostFS.SDK.ClientV2;
internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, ClientEnvironment ctx) : ContextAccessor(ctx), ISessionProvider internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, ClientEnvironment env) : ContextAccessor(env), ISessionProvider
{ {
readonly ObjectTools tools = new(ctx); readonly SessionProvider sessions = new (env);
readonly SessionProvider sessions = new (ctx);
public async ValueTask<Session.SessionToken> GetOrCreateSession(ISessionToken args, Context ctx) public async ValueTask<Session.SessionToken> GetOrCreateSession(ISessionToken args, Context ctx)
{ {
@ -35,8 +35,8 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
{ {
Address = new Address Address = new Address
{ {
ContainerId = args.ContainerId.ToGrpcMessage(), ContainerId = args.ContainerId.ToMessage(),
ObjectId = args.ObjectId.ToGrpcMessage() ObjectId = args.ObjectId.ToMessage()
} }
} }
}; };
@ -46,11 +46,11 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
sessionToken.CreateObjectTokenContext( sessionToken.CreateObjectTokenContext(
request.Body.Address, request.Body.Address,
ObjectSessionContext.Types.Verb.Head, ObjectSessionContext.Types.Verb.Head,
Context.Key); Context.Key.ECDsaKey);
request.AddMetaHeader(args.XHeaders, sessionToken); request.AddMetaHeader(args.XHeaders, sessionToken);
request.Sign(Context.Key); request.Sign(Context.Key.ECDsaKey);
var response = await client!.HeadAsync(request, null, ctx.Deadline, ctx.CancellationToken); var response = await client!.HeadAsync(request, null, ctx.Deadline, ctx.CancellationToken);
@ -69,8 +69,8 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
{ {
Address = new Address Address = new Address
{ {
ContainerId = args.ContainerId.ToGrpcMessage(), ContainerId = args.ContainerId.ToMessage(),
ObjectId = args.ObjectId.ToGrpcMessage() ObjectId = args.ObjectId.ToMessage()
} }
} }
}; };
@ -80,11 +80,11 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
sessionToken.CreateObjectTokenContext( sessionToken.CreateObjectTokenContext(
request.Body.Address, request.Body.Address,
ObjectSessionContext.Types.Verb.Get, ObjectSessionContext.Types.Verb.Get,
Context.Key); Context.Key.ECDsaKey);
request.AddMetaHeader(args.XHeaders, sessionToken); request.AddMetaHeader(args.XHeaders, sessionToken);
request.Sign(Context.Key); request.Sign(Context.Key.ECDsaKey);
return await GetObject(request, ctx); return await GetObject(request, ctx);
} }
@ -98,8 +98,8 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
{ {
Address = new Address Address = new Address
{ {
ContainerId = args.ContainerId.ToGrpcMessage(), ContainerId = args.ContainerId.ToMessage(),
ObjectId = args.ObjectId.ToGrpcMessage() ObjectId = args.ObjectId.ToMessage()
} }
} }
}; };
@ -109,10 +109,10 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
sessionToken.CreateObjectTokenContext( sessionToken.CreateObjectTokenContext(
request.Body.Address, request.Body.Address,
ObjectSessionContext.Types.Verb.Delete, ObjectSessionContext.Types.Verb.Delete,
Context.Key); Context.Key.ECDsaKey);
request.AddMetaHeader(args.XHeaders, sessionToken); request.AddMetaHeader(args.XHeaders, sessionToken);
request.Sign(Context.Key); request.Sign(Context.Key.ECDsaKey);
var response = await client.DeleteAsync(request, null, ctx.Deadline, ctx.CancellationToken); var response = await client.DeleteAsync(request, null, ctx.Deadline, ctx.CancellationToken);
@ -126,24 +126,24 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
{ {
Body = new SearchRequest.Types.Body Body = new SearchRequest.Types.Body
{ {
ContainerId = args.ContainerId.ToGrpcMessage(), ContainerId = args.ContainerId.ToMessage(),
Filters = { }, Filters = { },
Version = 1 // TODO: clarify this param Version = 1 // TODO: clarify this param
} }
}; };
request.Body.Filters.AddRange(args.Filters.Select(f => f.ToGrpcMessage())); request.Body.Filters.AddRange(args.Filters.Select(f => f.ToMessage()));
var sessionToken = await GetOrCreateSession(args, ctx); var sessionToken = await GetOrCreateSession(args, ctx);
sessionToken.CreateObjectTokenContext( sessionToken.CreateObjectTokenContext(
new Address { ContainerId = request.Body.ContainerId }, new Address { ContainerId = request.Body.ContainerId },
ObjectSessionContext.Types.Verb.Search, ObjectSessionContext.Types.Verb.Search,
Context.Key); Context.Key.ECDsaKey);
request.AddMetaHeader(args.XHeaders, sessionToken); request.AddMetaHeader(args.XHeaders, sessionToken);
request.Sign(Context.Key); request.Sign(Context.Key.ECDsaKey);
var objectsIds = SearchObjects(request, ctx); var objectsIds = SearchObjects(request, ctx);
@ -153,7 +153,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
} }
} }
internal Task<ObjectId> PutObjectAsync(PrmObjectPut args) internal async Task<ObjectId> PutObjectAsync(PrmObjectPut args)
{ {
if (args.Header == null) if (args.Header == null)
throw new ArgumentException("Value cannot be null", nameof(args.Header)); throw new ArgumentException("Value cannot be null", nameof(args.Header));
@ -162,15 +162,22 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
throw new ArgumentException("Value cannot be null", nameof(args.Payload)); throw new ArgumentException("Value cannot be null", nameof(args.Payload));
if (args.ClientCut) if (args.ClientCut)
return PutClientCutObject(args); return await PutClientCutObject(args);
else else
return PutStreamObject(args); {
if (args.Header.PayloadLength > 0)
args.FullLength = args.Header.PayloadLength;
else if (args.Payload.CanSeek)
args.FullLength = (ulong)args.Payload.Length;
return (await PutStreamObject(args)).ObjectId;
}
} }
internal async Task<ObjectId> PutSingleObjectAsync(PrmSingleObjectPut args) internal async Task<ObjectId> PutSingleObjectAsync(PrmSingleObjectPut args)
{ {
var ctx = args.Context!; var ctx = args.Context!;
var grpcObject = tools.CreateObject(args.FrostFsObject); var grpcObject = ObjectTools.CreateObject(args.FrostFsObject, env);
var request = new PutSingleRequest var request = new PutSingleRequest
{ {
@ -185,11 +192,11 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
sessionToken.CreateObjectTokenContext( sessionToken.CreateObjectTokenContext(
new Address { ContainerId = grpcObject.Header.ContainerId, ObjectId = grpcObject.ObjectId}, new Address { ContainerId = grpcObject.Header.ContainerId, ObjectId = grpcObject.ObjectId},
ObjectSessionContext.Types.Verb.Put, ObjectSessionContext.Types.Verb.Put,
Context.Key); Context.Key.ECDsaKey);
request.AddMetaHeader(args.XHeaders, sessionToken); request.AddMetaHeader(args.XHeaders, sessionToken);
request.Sign(Context.Key); request.Sign(Context.Key.ECDsaKey);
var response = await client.PutSingleAsync(request, null, ctx.Deadline, ctx.CancellationToken); var response = await client.PutSingleAsync(request, null, ctx.Deadline, ctx.CancellationToken);
@ -201,156 +208,136 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
private async Task<ObjectId> PutClientCutObject(PrmObjectPut args) private async Task<ObjectId> PutClientCutObject(PrmObjectPut args)
{ {
var ctx = args.Context!; var ctx = args.Context!;
var tokenRaw = await GetOrCreateSession(args, ctx); var tokenRaw = await GetOrCreateSession(args, ctx);
var token = new ModelsV2.SessionToken(tokenRaw.Serialize()); var token = new ModelsV2.SessionToken(tokenRaw.Serialize());
args.SessionToken = token;
var payloadStream = args.Payload!; var payloadStream = args.Payload!;
var header = args.Header!; var header = args.Header!;
ObjectId? objectId;
List<ObjectId> sentObjectIds = [];
FrostFsObject? currentObject;
var networkSettings = await Context.Client.GetNetworkSettingsAsync(new PrmNetworkSettings(ctx));
var objectSize = (int)networkSettings.MaxObjectSize;
var fullLength = header.PayloadLength; var fullLength = header.PayloadLength;
if (payloadStream.CanSeek) if (payloadStream.CanSeek && fullLength == 0)
{ fullLength = (ulong)payloadStream.Length;
objectSize = (int)Math.Min(objectSize, payloadStream.Length);
if (fullLength == 0) args.FullLength = fullLength;
fullLength = (ulong)payloadStream.Length;
if (args.MaxObjectSizeCache == 0)
{
var networkSettings = await Context.Client.GetNetworkSettingsAsync(new PrmNetworkSettings(ctx));
args.MaxObjectSizeCache = (int)networkSettings.MaxObjectSize;
} }
if (fullLength == 0) var restBytes = fullLength - args.CurrentStreamPosition;
throw new ArgumentException("Payload stream must be able to seek or PayloadLength must be specified"); var objectSize = restBytes > 0 ? (int)Math.Min((ulong)args.MaxObjectSizeCache, restBytes) : args.MaxObjectSizeCache;
var buffer = new byte[objectSize]; //define collection capacity
var restPart = (restBytes % (ulong)objectSize) > 0 ? 1 : 0;
var objectsCount = fullLength > 0 ? (int)(restBytes / (ulong)objectSize) + restPart : 0;
var largeObject = new LargeObject(header.ContainerId); List<ObjectId> sentObjectIds = new(objectsCount);
var split = new Split(); Split? split = null;
while (true) // keep attributes for the large object
var attributes = args.Header!.Attributes;
// send all parts except the last one as separate Objects
while (restBytes > (ulong)args.MaxObjectSizeCache)
{ {
var bytesCount = await payloadStream.ReadAsync(buffer, 0, objectSize); if (split == null)
{
split = new Split();
args.Header!.Attributes = [];
}
split.Previous = sentObjectIds.LastOrDefault(); split!.Previous = sentObjectIds.LastOrDefault();
args.Header!.Split = split;
largeObject.Header.PayloadLength += (ulong)bytesCount; var result = await PutStreamObject(args);
currentObject = new FrostFsObject(header.ContainerId) sentObjectIds.Add(result.ObjectId);
.SetPayload(bytesCount < objectSize ? buffer[..bytesCount] : buffer)
.SetSplit(split);
if (largeObject.PayloadLength == fullLength) restBytes -= (ulong)result.ObjectSize;
break;
objectId = await PutSingleObjectAsync(new PrmSingleObjectPut(currentObject, ctx) { SessionToken = token });
sentObjectIds.Add(objectId!);
} }
if (sentObjectIds.Count != 0) // send the last part and create linkObject
if (sentObjectIds.Count > 0)
{ {
largeObject.AddAttributes(args.Header!.Attributes); var largeObjectHeader = new ObjectHeader(header.ContainerId) { PayloadLength = fullLength };
currentObject.SetParent(largeObject); largeObjectHeader.Attributes.AddRange(attributes);
var putSingleObjectParams = new PrmSingleObjectPut(currentObject, ctx) { SessionToken = token }; args.Header.Split!.ParentHeader = largeObjectHeader;
objectId = await PutSingleObjectAsync(putSingleObjectParams); var result = await PutStreamObject(args);
sentObjectIds.Add(objectId); sentObjectIds.Add(result.ObjectId);
var linkObject = new LinkObject(header.ContainerId, split.SplitId, largeObject) var linkObject = new LinkObject(header.ContainerId, split!.SplitId, largeObjectHeader)
.AddChildren(sentObjectIds); .AddChildren(sentObjectIds);
linkObject.Header.Attributes.Clear(); _ = await PutSingleObjectAsync(new PrmSingleObjectPut(linkObject) { Context = args.Context});
_ = await PutSingleObjectAsync(new PrmSingleObjectPut(linkObject, ctx){ SessionToken = token }); return split.Parent!;
return tools.CalculateObjectId(largeObject.Header);
} }
currentObject // We are here if the payload is placed to one Object. It means no cut action, just simple PUT.
.SetSplit(null) var singlePartResult = await PutStreamObject(args);
.AddAttributes(args.Header!.Attributes);
return await PutSingleObjectAsync(new PrmSingleObjectPut(currentObject, ctx)); return singlePartResult.ObjectId;
} }
private async Task<ObjectId> PutStreamObject(PrmObjectPut args) struct PutObjectResult(ObjectId objectId, int objectSize)
{
public ObjectId ObjectId = objectId;
public int ObjectSize = objectSize;
}
private async Task<PutObjectResult> PutStreamObject(PrmObjectPut args)
{ {
var ctx = args.Context!; var ctx = args.Context!;
var payload = args.Payload!; var payload = args.Payload!;
var header = args.Header!;
var hdr = header.ToGrpcMessage(); var chunkSize = args.BufferMaxSize > 0 ? args.BufferMaxSize : Constants.ObjectChunkSize;
hdr.OwnerId = Context.Owner.ToGrpcMessage();
hdr.Version = Context.Version.ToGrpcMessage();
var oid = new ObjectID { Value = hdr.Sha256() }; var restBytes = args.FullLength - args.CurrentStreamPosition;
var initRequest = new PutRequest chunkSize = (int)Math.Min(restBytes, (ulong)chunkSize);
var chunkBuffer = ArrayPool<byte>.Shared.Rent(chunkSize);
var sentBytes = 0;
// 0 means no limit from client, so server side cut is performed
var objectLimitSize = args.ClientCut ? args.MaxObjectSizeCache : 0;
var stream = await GetUploadStream(args, ctx);
while (objectLimitSize == 0 || sentBytes < objectLimitSize)
{ {
Body = new PutRequest.Types.Body // send chanks limited to default or user's settings
{ var bufferSize = objectLimitSize > 0 ?
Init = new PutRequest.Types.Body.Types.Init (int)Math.Min(objectLimitSize - sentBytes, chunkSize)
{ : chunkSize;
Header = hdr
}
}
};
var sessionToken = await GetOrCreateSession(args, ctx); var bytesCount = await payload.ReadAsync(chunkBuffer, 0, bufferSize, ctx.CancellationToken);
sessionToken.CreateObjectTokenContext(
new Address { ContainerId = hdr.ContainerId, ObjectId = oid },
ObjectSessionContext.Types.Verb.Put,
Context.Key
);
initRequest.AddMetaHeader(args.XHeaders, sessionToken);
initRequest.Sign(Context.Key);
using var stream = await PutObjectInit(initRequest, ctx);
var bufferSize = args.BufferMaxSize > 0 ? args.BufferMaxSize : Constants.ObjectChunkSize;
if (payload.CanSeek)
{
bufferSize = (int)Math.Min(payload.Length, bufferSize);
}
else if (header.PayloadLength > 0)
{
bufferSize = (int)Math.Min((long)header.PayloadLength, bufferSize);
}
var buffer = new byte[bufferSize];
while (true)
{
var bytesCount = await payload.ReadAsync(buffer, 0, bufferSize, ctx.CancellationToken);
if (bytesCount == 0) if (bytesCount == 0)
break; break;
var chunkRequest = new PutRequest(initRequest) sentBytes += bytesCount;
var chunkRequest = new PutRequest
{ {
Body = new PutRequest.Types.Body Body = new PutRequest.Types.Body
{ {
Chunk = ByteString.CopyFrom(buffer.AsSpan()[..bytesCount]), Chunk = ByteString.CopyFrom(chunkBuffer, 0, bytesCount)
}, }
VerifyHeader = null
}; };
chunkRequest.Sign(Context.Key); chunkRequest.Sign(Context.Key.ECDsaKey);
await stream.Write(chunkRequest); await stream.Write(chunkRequest);
} }
@ -358,7 +345,49 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C
var response = await stream.Close(); var response = await stream.Close();
Verifier.CheckResponse(response); Verifier.CheckResponse(response);
return ObjectId.FromHash(response.Body.ObjectId.Value.ToByteArray()); return new PutObjectResult(ObjectId.FromHash(response.Body.ObjectId.Value.ToByteArray()), sentBytes);
}
private async Task<ObjectStreamer> GetUploadStream(PrmObjectPut args, Context ctx)
{
var header = args.Header!;
header.OwnerId = Context.Owner;
header.Version = Context.Version;
var grpcHeader = header.ToMessage();
if (header.Split != null)
{
ObjectTools.SetSplitValues(grpcHeader, header.Split, env);
}
var oid = new ObjectID { Value = grpcHeader.Sha256() };
var initRequest = new PutRequest
{
Body = new PutRequest.Types.Body
{
Init = new PutRequest.Types.Body.Types.Init
{
Header = grpcHeader
}
}
};
var sessionToken = await GetOrCreateSession(args, ctx);
sessionToken.CreateObjectTokenContext(
new Address { ContainerId = grpcHeader.ContainerId, ObjectId = oid },
ObjectSessionContext.Types.Verb.Put,
Context.Key.ECDsaKey
);
initRequest.AddMetaHeader(args.XHeaders, sessionToken);
initRequest.Sign(Context.Key.ECDsaKey);
return await PutObjectInit(initRequest, ctx);
} }
private async Task<FrostFsObject> GetObject(GetRequest request, Context ctx) private async Task<FrostFsObject> GetObject(GetRequest request, Context ctx)

View file

@ -21,13 +21,13 @@ internal class SessionServiceProvider : ContextAccessor
{ {
Body = new CreateRequest.Types.Body Body = new CreateRequest.Types.Body
{ {
OwnerId = Context.Owner.ToGrpcMessage(), OwnerId = Context.Owner.ToMessage(),
Expiration = args.Expiration Expiration = args.Expiration
} }
}; };
request.AddMetaHeader(args.XHeaders); request.AddMetaHeader(args.XHeaders);
request.Sign(Context.Key); request.Sign(Context.Key.ECDsaKey);
return await CreateSession(request, args.Context!); return await CreateSession(request, args.Context!);
} }

View file

@ -1,7 +1,9 @@
using FrostFS.SDK.ModelsV2; using FrostFS.SDK.ModelsV2;
using Google.Protobuf;
using Grpc.Net.Client; using Grpc.Net.Client;
using System; using System;
using System.Security.Cryptography; using System.Security.Cryptography;
using FrostFS.SDK.Cryptography;
namespace FrostFS.SDK.ClientV2; namespace FrostFS.SDK.ClientV2;
@ -9,11 +11,12 @@ public class ClientEnvironment(Client client, ECDsa key, OwnerId owner, GrpcChan
{ {
internal OwnerId Owner { get; } = owner; internal OwnerId Owner { get; } = owner;
internal GrpcChannel Channel { get; private set; } = channel; internal GrpcChannel Channel { get; private set; } = channel;
internal ECDsa Key { get; } = key;
internal ModelsV2.Version Version { get; } = version; internal ModelsV2.Version Version { get; } = version;
internal NetworkSettings? NetworkSettings { get; set; } internal NetworkSettings? NetworkSettings { get; set; }
internal Client Client { get; set; } = client; internal Client Client { get; } = client;
internal ClientKey Key { get; } = new ClientKey(key);
public void Dispose() public void Dispose()
{ {

View file

@ -7,54 +7,36 @@ using FrostFS.Refs;
using FrostFS.SDK.ClientV2.Mappers.GRPC; using FrostFS.SDK.ClientV2.Mappers.GRPC;
using FrostFS.SDK.Cryptography; using FrostFS.SDK.Cryptography;
using FrostFS.SDK.ModelsV2; using FrostFS.SDK.ModelsV2;
using static FrostFS.Object.Header.Types;
namespace FrostFS.SDK.ClientV2; namespace FrostFS.SDK.ClientV2;
internal class ObjectTools(ClientEnvironment ctx) : ContextAccessor (ctx) internal class ObjectTools
{ {
internal ObjectId CalculateObjectId(ObjectHeader header) internal static ObjectId CalculateObjectId(ObjectHeader header, ClientEnvironment env)
{ {
var grpcHeader = CreateHeader(header, []); var grpcHeader = CreateHeader(header, [], env);
if (header.Split != null)
SetSplitValues(grpcHeader, header.Split, env);
return new ObjectID { Value = grpcHeader.Sha256() }.ToModel(); return new ObjectID { Value = grpcHeader.Sha256() }.ToModel();
} }
internal Object.Object CreateObject(FrostFsObject @object) internal static Object.Object CreateObject(FrostFsObject @object, ClientEnvironment env)
{ {
var grpcHeader = @object.Header.ToGrpcMessage(); @object.Header.OwnerId = env.Owner;
@object.Header.Version = env.Version;
var grpcHeader = @object.Header.ToMessage();
grpcHeader.OwnerId = Context.Owner.ToGrpcMessage();
grpcHeader.Version = Context.Version.ToGrpcMessage();
grpcHeader.PayloadLength = (ulong)@object.Payload.Length; grpcHeader.PayloadLength = (ulong)@object.Payload.Length;
grpcHeader.PayloadHash = Sha256Checksum(@object.Payload); grpcHeader.PayloadHash = Sha256Checksum(@object.Payload);
var split = @object.Header.Split; var split = @object.Header.Split;
if (split != null) if (split != null)
{ {
grpcHeader.Split = new Header.Types.Split SetSplitValues(grpcHeader, split, env);
{
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 var obj = new Object.Object
@ -66,21 +48,49 @@ internal class ObjectTools(ClientEnvironment ctx) : ContextAccessor (ctx)
obj.Signature = new Refs.Signature obj.Signature = new Refs.Signature
{ {
Key = ByteString.CopyFrom(Context.Key.PublicKey()), Key = env.Key.PublicKeyProto,
Sign = ByteString.CopyFrom(Context.Key.SignData(obj.ObjectId.ToByteArray())), Sign = ByteString.CopyFrom(env.Key.ECDsaKey.SignData(obj.ObjectId.ToByteArray())),
}; };
return obj; return obj;
} }
internal Header CreateHeader(ObjectHeader header, byte[]? payload) internal static void SetSplitValues(Header grpcHeader, ModelsV2.Split split, ClientEnvironment env)
{ {
var grpcHeader = header.ToGrpcMessage(); grpcHeader.Split = new Header.Types.Split
{
SplitId = split.SplitId != null ? ByteString.CopyFrom(split.SplitId.ToBinary()) : null
};
grpcHeader.OwnerId = Context.Owner.ToGrpcMessage(); if (split.Children != null && split.Children.Count != 0)
grpcHeader.Version = Context.Version.ToGrpcMessage(); grpcHeader.Split.Children.AddRange(split.Children.Select(id => id.ToMessage()));
if (payload != null) if (split.ParentHeader is not null)
{
var grpcParentHeader = CreateHeader(split.ParentHeader, [], env);
grpcHeader.Split.Parent = new ObjectID { Value = grpcParentHeader.Sha256() };
grpcHeader.Split.ParentHeader = grpcParentHeader;
grpcHeader.Split.ParentSignature = new Refs.Signature
{
Key = env.Key.PublicKeyProto,
Sign = ByteString.CopyFrom(env.Key.ECDsaKey.SignData(grpcHeader.Split.Parent.ToByteArray())),
};
split.Parent = grpcHeader.Split.Parent.ToModel();
}
grpcHeader.Split.Previous = split.Previous?.ToMessage();
}
internal static Header CreateHeader(ObjectHeader header, byte[]? payload, ClientEnvironment env)
{
var grpcHeader = header.ToMessage();
grpcHeader.OwnerId = env.Owner.ToMessage();
grpcHeader.Version = env.Version.ToMessage();
if (payload != null) // && payload.Length > 0
grpcHeader.PayloadHash = Sha256Checksum(payload); grpcHeader.PayloadHash = Sha256Checksum(payload);
return grpcHeader; return grpcHeader;

View file

@ -17,7 +17,7 @@ public static class RequestConstructor
if (request.MetaHeader is not null) if (request.MetaHeader is not null)
return; return;
request.MetaHeader = MetaHeader.Default().ToGrpcMessage(); request.MetaHeader = MetaHeader.Default().ToMessage();
if (sessionToken != null) if (sessionToken != null)
request.MetaHeader.SessionToken = sessionToken; request.MetaHeader.SessionToken = sessionToken;

View file

@ -69,7 +69,7 @@ public static class RequestSigner
var hash = new byte[65]; var hash = new byte[65];
hash[0] = 0x04; hash[0] = 0x04;
key.SignHash(SHA512.Create().ComputeHash(data)).CopyTo(hash, 1); key.SignHash(data.Sha512()).CopyTo(hash, 1);
return hash; return hash;
} }

View file

@ -60,7 +60,7 @@ public static class Verifier
public static bool VerifyData(this ECDsa key, byte[] data, byte[] sig) public static bool VerifyData(this ECDsa key, byte[] data, byte[] sig)
{ {
return key.VerifyHash(SHA512.Create().ComputeHash(data), sig[1..]); return key.VerifyHash(data.Sha512(), sig[1..]);
} }
public static bool VerifyMessagePart(this Signature sig, IMessage data) public static bool VerifyMessagePart(this Signature sig, IMessage data)
@ -101,7 +101,8 @@ public static class Verifier
throw new FormatException($"invalid response, type={resp.GetType()}"); throw new FormatException($"invalid response, type={resp.GetType()}");
var status = resp.MetaHeader.Status.ToModel(); var status = resp.MetaHeader.Status.ToModel();
if (!status.IsSuccess)
if (status != null && !status.IsSuccess)
throw new ResponseException(status); throw new ResponseException(status);
} }

View file

@ -65,7 +65,7 @@ public static class Base58
var firstNonZeroIndex = 0; var firstNonZeroIndex = 0;
while(bytesBigEndian.ElementAt(firstNonZeroIndex) == 0x0) while (bytesBigEndian.ElementAt(firstNonZeroIndex) == 0x0)
firstNonZeroIndex++; firstNonZeroIndex++;
var bytesWithoutLeadingZeros = bytesBigEndian.Skip(firstNonZeroIndex).ToArray(); var bytesWithoutLeadingZeros = bytesBigEndian.Skip(firstNonZeroIndex).ToArray();

View file

@ -1,11 +1,18 @@
using Google.Protobuf; using Google.Protobuf;
using Org.BouncyCastle.Crypto.Digests; using Org.BouncyCastle.Crypto.Digests;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Threading;
namespace FrostFS.SDK.Cryptography; namespace FrostFS.SDK.Cryptography;
public static class Extentions public static class Extentions
{ {
private static readonly SHA256 _sha256 = SHA256.Create();
private static SpinLock _spinlockSha256 = new();
private static readonly SHA512 _sha512 = SHA512.Create();
private static SpinLock _spinlockSha512 = new();
internal static byte[] RIPEMD160(this byte[] value) internal static byte[] RIPEMD160(this byte[] value)
{ {
var hash = new byte[20]; var hash = new byte[20];
@ -16,14 +23,40 @@ public static class Extentions
return hash; return hash;
} }
public static byte[] Sha256(this byte[] value)
{
var sha256 = SHA256.Create();
return sha256.ComputeHash(value);
}
public static ByteString Sha256(this IMessage data) public static ByteString Sha256(this IMessage data)
{ {
return ByteString.CopyFrom(data.ToByteArray().Sha256()); return ByteString.CopyFrom(data.ToByteArray().Sha256());
} }
public static byte[] Sha256(this byte[] value)
{
bool lockTaken = false;
try
{
_spinlockSha256.Enter(ref lockTaken);
return _sha256.ComputeHash(value);
}
finally
{
if (lockTaken)
_spinlockSha256.Exit(false);
}
}
public static byte[] Sha512(this byte[] value)
{
bool lockTaken = false;
try
{
_spinlockSha512.Enter(ref lockTaken);
return _sha512.ComputeHash(value);
}
finally
{
if (lockTaken)
_spinlockSha512.Exit(false);
}
}
} }

View file

@ -133,8 +133,9 @@ public static class KeyExtension
public static ECDsa LoadPrivateKey(this byte[] privateKey) public static ECDsa LoadPrivateKey(this byte[] privateKey)
{ {
var secp256R1 = SecNamedCurves.GetByName("secp256r1"); var secp256R1 = SecNamedCurves.GetByName("secp256r1");
var publicKey = var publicKey = secp256R1.G.Multiply(new Org.BouncyCastle.Math.BigInteger(1, privateKey))
secp256R1.G.Multiply(new Org.BouncyCastle.Math.BigInteger(1, privateKey)).GetEncoded(false)[1..]; .GetEncoded(false)[1..];
var key = ECDsa.Create(new ECParameters var key = ECDsa.Create(new ECParameters
{ {
Curve = ECCurve.NamedCurves.nistP256, Curve = ECCurve.NamedCurves.nistP256,

View file

@ -112,7 +112,7 @@ internal class Murmur3_128 : HashAlgorithm
h2 = seed; h2 = seed;
} }
private ulong Fimix64(ulong k) private static ulong Fimix64(ulong k)
{ {
k ^= k >> 33; k ^= k >> 33;
k *= 0xff51afd7ed558ccd; k *= 0xff51afd7ed558ccd;

View file

@ -77,14 +77,7 @@ namespace System
{ {
get get
{ {
if (_value < 0) return _value < 0 ? ~_value : _value;
{
return ~_value;
}
else
{
return _value;
}
} }
} }
@ -105,12 +98,9 @@ namespace System
var offset = _value; var offset = _value;
if (IsFromEnd) if (IsFromEnd)
{ {
// offset = length - (~value)
// offset = length + (~(~value) + 1)
// offset = length + value + 1
offset += length + 1; offset += length + 1;
} }
return offset; return offset;
} }

View file

@ -16,6 +16,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\FrostFS.SDK.Cryptography\FrostFS.SDK.Cryptography.csproj" /> <ProjectReference Include="..\FrostFS.SDK.Cryptography\FrostFS.SDK.Cryptography.csproj" />
<ProjectReference Include="..\FrostFS.SDK.ProtosV2\FrostFS.SDK.ProtosV2.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -1,3 +1,4 @@
using FrostFS.SDK.Cryptography;
using System; using System;
using System.Security.Cryptography; using System.Security.Cryptography;
@ -8,15 +9,9 @@ public class CheckSum
// type is always Sha256 // type is always Sha256
public byte[]? Hash { get; set; } public byte[]? Hash { get; set; }
public static byte[] GetHash(byte[] content)
{
var sha256 = SHA256.Create();
return sha256.ComputeHash(content);
}
public static CheckSum CreateCheckSum(byte[] content) public static CheckSum CreateCheckSum(byte[] content)
{ {
return new CheckSum { Hash = GetHash(content) }; return new CheckSum { Hash = content.Sha256() };
} }
public override string ToString() public override string ToString()

View file

@ -56,19 +56,17 @@ public class FrostFsObject
/// Applied only for the last Object in chain in case of manual multipart uploading /// Applied only for the last Object in chain in case of manual multipart uploading
/// </summary> /// </summary>
/// <param name="largeObject">Parent for multipart object</param> /// <param name="largeObject">Parent for multipart object</param>
public void SetParent(LargeObject largeObject) public void SetParent(ObjectHeader largeObjectHeader)
{ {
if (Header?.Split == null) if (Header?.Split == null)
throw new Exception("The object is not initialized properly"); throw new Exception("The object is not initialized properly");
Header.Split.ParentHeader = largeObject.Header; Header.Split.ParentHeader = largeObjectHeader;
} }
} }
public class LargeObject(ContainerId container) : FrostFsObject(container) public class LargeObject(ContainerId container) : FrostFsObject(container)
{ {
private readonly SHA256 payloadHash = SHA256.Create();
public ulong PayloadLength public ulong PayloadLength
{ {
get { return Header!.PayloadLength; } get { return Header!.PayloadLength; }
@ -77,11 +75,11 @@ public class LargeObject(ContainerId container) : FrostFsObject(container)
public class LinkObject : FrostFsObject public class LinkObject : FrostFsObject
{ {
public LinkObject(ContainerId containerId, SplitId splitId, LargeObject largeObject) : base (containerId) public LinkObject(ContainerId containerId, SplitId splitId, ObjectHeader largeObjectHeader) : base (containerId)
{ {
Header!.Split = new Split(splitId) Header!.Split = new Split(splitId)
{ {
ParentHeader = largeObject.Header ParentHeader = largeObjectHeader
}; };
} }
} }

View file

@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace FrostFS.SDK.ModelsV2; namespace FrostFS.SDK.ModelsV2;
public class Split(SplitId splitId) public class Split(SplitId splitId)
@ -19,4 +20,6 @@ public class Split(SplitId splitId)
public ObjectHeader? ParentHeader { get; set; } public ObjectHeader? ParentHeader { get; set; }
public List<ObjectId> Children { get; } = []; public List<ObjectId> Children { get; } = [];
public Refs.Signature ParentSignatureGrpc { get; set; }
} }

View file

@ -2,7 +2,7 @@ using FrostFS.SDK.ModelsV2.Enums;
namespace FrostFS.SDK.ModelsV2; namespace FrostFS.SDK.ModelsV2;
public class Status(StatusCode code, string? message = null) public class ResponseStatus(StatusCode code, string? message = null)
{ {
public StatusCode Code { get; set; } = code; public StatusCode Code { get; set; } = code;
public string Message { get; set; } = message ?? string.Empty; public string Message { get; set; } = message ?? string.Empty;

View file

@ -110,6 +110,6 @@ public class ContainerTest : ContainerTestsBase
var request = Mocker.Requests.First(); var request = Mocker.Requests.First();
Assert.Equal(cid.ToGrpcMessage(), request.Request.Body.ContainerId); Assert.Equal(cid.ToMessage(), request.Request.Body.ContainerId);
} }
} }

View file

@ -19,14 +19,14 @@ public class AsyncStreamReaderMock(string key, ObjectHeader objectHeader) : Serv
{ {
var header = new Header var header = new Header
{ {
ContainerId = objectHeader.ContainerId.ToGrpcMessage(), ContainerId = objectHeader.ContainerId.ToMessage(),
PayloadLength = objectHeader.PayloadLength, PayloadLength = objectHeader.PayloadLength,
Version = objectHeader.Version!.ToGrpcMessage(), Version = objectHeader.Version!.ToMessage(),
OwnerId = objectHeader.OwnerId!.ToGrpcMessage() OwnerId = objectHeader.OwnerId!.ToMessage()
}; };
foreach (var attr in objectHeader.Attributes) foreach (var attr in objectHeader.Attributes)
header.Attributes.Add(attr.ToGrpcMessage()); header.Attributes.Add(attr.ToMessage());
var response = new GetResponse var response = new GetResponse
{ {

View file

@ -27,7 +27,7 @@ public abstract class ServiceBase(string key)
public static BasicAcl DefaultAcl { get; } = BasicAcl.PublicRW; public static BasicAcl DefaultAcl { get; } = BasicAcl.PublicRW;
public static PlacementPolicy DefaultPlacementPolicy { get; } = new PlacementPolicy(true, new Replica(1)); public static PlacementPolicy DefaultPlacementPolicy { get; } = new PlacementPolicy(true, new Replica(1));
public Metadata ResponseMetaData => []; public static Metadata ResponseMetaData => [];
protected ResponseVerificationHeader GetResponseVerificationHeader(IResponse response) protected ResponseVerificationHeader GetResponseVerificationHeader(IResponse response)
{ {
@ -58,7 +58,7 @@ public abstract class ServiceBase(string key)
public ResponseMetaHeader ResponseMetaHeader => new() public ResponseMetaHeader ResponseMetaHeader => new()
{ {
Version = Version.ToGrpcMessage(), Version = Version.ToMessage(),
Epoch = 100, Epoch = 100,
Ttl = 1 Ttl = 1
}; };

View file

@ -19,7 +19,7 @@ public class ContainerMocker(string key) : ContainerServiceBase(key)
{ {
var mock = new Mock<ContainerService.ContainerServiceClient>(); var mock = new Mock<ContainerService.ContainerServiceClient>();
var grpcVersion = Version.ToGrpcMessage(); var grpcVersion = Version.ToMessage();
PutResponse putResponse = new() PutResponse putResponse = new()
{ {
@ -32,7 +32,7 @@ public class ContainerMocker(string key) : ContainerServiceBase(key)
}, },
MetaHeader = new ResponseMetaHeader MetaHeader = new ResponseMetaHeader
{ {
Version = Version is null ? DefaultVersion.ToGrpcMessage() : Version.ToGrpcMessage(), Version = (Version is null ? DefaultVersion : Version).ToMessage(),
Epoch = 100, Epoch = 100,
Ttl = 1 Ttl = 1
} }
@ -69,7 +69,7 @@ public class ContainerMocker(string key) : ContainerServiceBase(key)
Version = grpcVersion, Version = grpcVersion,
Nonce = ByteString.CopyFrom(ContainerGuid.ToBytes()), Nonce = ByteString.CopyFrom(ContainerGuid.ToBytes()),
BasicAcl = (uint)Acl, BasicAcl = (uint)Acl,
PlacementPolicy = PlacementPolicy.ToGrpcMessage() PlacementPolicy = PlacementPolicy.ToMessage()
} }
}, },
MetaHeader = ResponseMetaHeader MetaHeader = ResponseMetaHeader

View file

@ -38,12 +38,12 @@ public class ObjectMocker(string key) : ObjectServiceBase(key)
HeadResponse ??= new Header HeadResponse ??= new Header
{ {
CreationEpoch = 99, CreationEpoch = 99,
ContainerId = ObjectHeader.ContainerId.ToGrpcMessage(), ContainerId = ObjectHeader.ContainerId.ToMessage(),
ObjectType = ObjectType.Regular, ObjectType = ObjectType.Regular,
OwnerId = ObjectHeader.OwnerId!.ToGrpcMessage(), OwnerId = ObjectHeader.OwnerId!.ToMessage(),
PayloadLength = 1, PayloadLength = 1,
PayloadHash = new Refs.Checksum { Type = Refs.ChecksumType.Sha256, Sum = ByteString.CopyFrom(SHA256.HashData([0xff])) }, PayloadHash = new Refs.Checksum { Type = Refs.ChecksumType.Sha256, Sum = ByteString.CopyFrom(SHA256.HashData([0xff])) },
Version = ObjectHeader.Version!.ToGrpcMessage() Version = ObjectHeader.Version!.ToMessage()
}; };
HeadResponse headResponse = new() HeadResponse headResponse = new()
@ -89,7 +89,7 @@ public class ObjectMocker(string key) : ObjectServiceBase(key)
} }
if (ResultObjectId != null) if (ResultObjectIds != null)
{ {
PutResponse putResponse = new() PutResponse putResponse = new()
{ {
@ -97,7 +97,7 @@ public class ObjectMocker(string key) : ObjectServiceBase(key)
{ {
ObjectId = new Refs.ObjectID ObjectId = new Refs.ObjectID
{ {
Value = ByteString.CopyFrom(ResultObjectId) Value = ByteString.CopyFrom(ResultObjectIds.ElementAt(0))
} }
}, },
MetaHeader = ResponseMetaHeader, MetaHeader = ResponseMetaHeader,
@ -156,8 +156,8 @@ public class ObjectMocker(string key) : ObjectServiceBase(key)
{ {
Tombstone = new Refs.Address Tombstone = new Refs.Address
{ {
ContainerId = ObjectHeader!.ContainerId.ToGrpcMessage(), ContainerId = ObjectHeader!.ContainerId.ToMessage(),
ObjectId = ObjectId.ToGrpcMessage() ObjectId = ObjectId.ToMessage()
} }
}, },
MetaHeader = ResponseMetaHeader MetaHeader = ResponseMetaHeader
@ -195,7 +195,7 @@ public class ObjectMocker(string key) : ObjectServiceBase(key)
public Header? HeadResponse { get; set; } public Header? HeadResponse { get; set; }
public byte[]? ResultObjectId { get; set; } public List<byte[]>? ResultObjectIds { get; set; }
public ClientStreamWriter? ClientStreamWriter { get; private set; } = new (); public ClientStreamWriter? ClientStreamWriter { get; private set; } = new ();

View file

@ -7,6 +7,7 @@ using FrostFS.SDK.Cryptography;
using FrostFS.SDK.ModelsV2; using FrostFS.SDK.ModelsV2;
using FrostFS.SDK.ModelsV2.Enums; using FrostFS.SDK.ModelsV2.Enums;
using FrostFS.SDK.ModelsV2.Netmap; using FrostFS.SDK.ModelsV2.Netmap;
using FrostFS.SDK.ProtosV2.Interfaces;
using Google.Protobuf; using Google.Protobuf;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using System.Security.Cryptography; using System.Security.Cryptography;
@ -82,7 +83,7 @@ public class ObjectTest : ObjectTestsBase
Assert.NotNull(result); Assert.NotNull(result);
Assert.Equal(Mocker.ObjectHeader!.ContainerId.Value, result.Header.ContainerId.Value); Assert.Equal(Mocker.ObjectHeader!.ContainerId.Value, result.Header.ContainerId.Value);
Assert.Equal(Mocker.ObjectHeader!.OwnerId!.ToGrpcMessage().ToString(), result.Header.OwnerId!.Value); Assert.Equal(Mocker.ObjectHeader!.OwnerId!.Value, result.Header.OwnerId!.Value);
Assert.Equal(Mocker.ObjectHeader.PayloadLength, result.Header.PayloadLength); Assert.Equal(Mocker.ObjectHeader.PayloadLength, result.Header.PayloadLength);
Assert.Single(result.Header.Attributes); Assert.Single(result.Header.Attributes);
Assert.Equal(Mocker.ObjectHeader.Attributes[0].Key, result.Header.Attributes[0].Key); Assert.Equal(Mocker.ObjectHeader.Attributes[0].Key, result.Header.Attributes[0].Key);
@ -92,7 +93,7 @@ public class ObjectTest : ObjectTestsBase
[Fact] [Fact]
public async void PutObjectTest() public async void PutObjectTest()
{ {
Mocker.ResultObjectId = SHA256.HashData([]); Mocker.ResultObjectIds = new([SHA256.HashData([])]);
Random rnd = new(); Random rnd = new();
var bytes = new byte[1024]; var bytes = new byte[1024];
@ -113,7 +114,7 @@ public class ObjectTest : ObjectTestsBase
var body2 = sentMessages.ElementAt(1).GetBody() as Object.PutRequest.Types.Body; var body2 = sentMessages.ElementAt(1).GetBody() as Object.PutRequest.Types.Body;
Assert.NotNull(result); Assert.NotNull(result);
Assert.Equal(Mocker.ResultObjectId, result.ToHash()); Assert.Equal(Mocker.ResultObjectIds.First(), result.ToHash());
Assert.True(Mocker.ClientStreamWriter.CompletedTask); Assert.True(Mocker.ClientStreamWriter.CompletedTask);
@ -137,57 +138,85 @@ public class ObjectTest : ObjectTestsBase
{ {
Header = Mocker.ObjectHeader, Header = Mocker.ObjectHeader,
Payload = new MemoryStream(bytes), Payload = new MemoryStream(bytes),
BufferMaxSize = 1024,
ClientCut = true ClientCut = true
}; };
Random rnd = new();
List<byte[]> objIds = new([new byte[32], new byte[32], new byte[32]]);
rnd.NextBytes(objIds.ElementAt(0));
rnd.NextBytes(objIds.ElementAt(1));
rnd.NextBytes(objIds.ElementAt(2));
Mocker.ResultObjectIds = objIds;
var result = await GetClient().PutObjectAsync(param); var result = await GetClient().PutObjectAsync(param);
var sentMessages = Mocker.PutSingleRequests.ToArray(); var singleObjects = Mocker.PutSingleRequests.ToArray();
Assert.Equal(4, sentMessages.Length); var streamObjects = Mocker.ClientStreamWriter.Messages.ToArray();
var object_0 = sentMessages[0].Body.Object; Assert.Single(singleObjects);
var object_1 = sentMessages[1].Body.Object;
var object_2 = sentMessages[2].Body.Object;
var object_3 = sentMessages[3].Body.Object;
Assert.NotNull(object_0.Header.Split.SplitId); Assert.Equal(11, streamObjects.Length);
Assert.Null(object_0.Header.Split.Previous);
Assert.Equal(blockSize, (int)object_0.Header.PayloadLength);
Assert.Equal(bytes[..blockSize], object_0.Payload);
Assert.True(object_0.Header.Attributes.Count == 0);
Assert.Equal(object_0.Header.Split.SplitId, object_1.Header.Split.SplitId); var bodies = streamObjects.Select(o => ((Object.PutRequest)o).Body).ToArray();
Assert.Equal(object_0.ObjectId, object_1.Header.Split.Previous);
Assert.Equal(blockSize, (int)object_1.Header.PayloadLength); Assert.Equal(3, bodies.Count(b => b.Init != null));
Assert.Equal(bytes[blockSize..(blockSize * 2)], object_1.Payload); Assert.Equal(5, bodies.Count(b => b.Chunk.Length == 1024));
Assert.True(object_1.Header.Attributes.Count == 0);
Assert.Equal(596, ((Object.PutRequest)streamObjects[10]).Body.Chunk.Length);
var linkObject = singleObjects[0].Body.Object;
var header1 = bodies[0].Init.Header;
var header2 = bodies[4].Init.Header;
var header3 = bodies[8].Init.Header;
var payload1 = bodies[1].Chunk
.Concat(bodies[2].Chunk)
.Concat(bodies[3].Chunk)
.ToArray();
var payload2 = bodies[5].Chunk
.Concat(bodies[6].Chunk)
.Concat(bodies[7].Chunk)
.ToArray();
var payload3 = bodies[9].Chunk
.Concat(bodies[10].Chunk)
.ToArray();
Assert.NotNull(header1.Split.SplitId);
Assert.Null(header1.Split.Previous);
Assert.Equal(bytes[..blockSize], payload1);
Assert.True(header1.Attributes.Count == 0);
Assert.Equal(header1.Split.SplitId, header2.Split.SplitId);
Assert.Equal(objIds.ElementAt(0), header2.Split.Previous.Value);
Assert.Equal(bytes[blockSize..(blockSize * 2)], payload2);
Assert.True(header2.Attributes.Count == 0);
// last part // last part
Assert.NotNull(object_2.Header.Split.Parent); Assert.NotNull(header3.Split.Parent);
Assert.NotNull(object_2.Header.Split.ParentHeader); Assert.NotNull(header3.Split.ParentHeader);
Assert.NotNull(object_2.Header.Split.ParentSignature); Assert.NotNull(header3.Split.ParentSignature);
Assert.Equal(object_1.Header.Split.SplitId, object_2.Header.Split.SplitId); Assert.Equal(header2.Split.SplitId, header3.Split.SplitId);
Assert.Equal(object_1.ObjectId, object_2.Header.Split.Previous); Assert.Equal(bytes[((fileLength / blockSize) * blockSize)..fileLength], payload3);
Assert.Equal(fileLength%blockSize, (int)object_2.Header.PayloadLength); Assert.True(header3.Attributes.Count == 0);
Assert.Equal(bytes[((fileLength/blockSize) * blockSize)..fileLength], object_2.Payload);
Assert.True(object_2.Header.Attributes.Count == 0);
// link object //link object
Assert.Equal(object_2.Header.Split.Parent, object_3.Header.Split.Parent); Assert.Equal(header3.Split.Parent, linkObject.Header.Split.Parent);
Assert.Equal(object_2.Header.Split.ParentHeader, object_3.Header.Split.ParentHeader); Assert.Equal(header3.Split.ParentHeader, linkObject.Header.Split.ParentHeader);
Assert.Equal(object_2.Header.Split.SplitId, object_3.Header.Split.SplitId); Assert.Equal(header3.Split.SplitId, linkObject.Header.Split.SplitId);
Assert.Equal(0, (int)object_3.Header.PayloadLength); Assert.Equal(0, (int)linkObject.Header.PayloadLength);
Assert.Contains(object_0.ObjectId, object_3.Header.Split.Children); Assert.True(header3.Attributes.Count == 0);
Assert.Contains(object_2.ObjectId, object_3.Header.Split.Children);
Assert.Contains(object_2.ObjectId, object_3.Header.Split.Children);
Assert.True(object_2.Header.Attributes.Count == 0);
Assert.Single(object_3.Header.Split.ParentHeader.Attributes); Assert.Single(linkObject.Header.Split.ParentHeader.Attributes);
Assert.Equal("k", object_3.Header.Split.ParentHeader.Attributes[0].Key); Assert.Equal("k", linkObject.Header.Split.ParentHeader.Attributes[0].Key);
Assert.Equal("v", object_3.Header.Split.ParentHeader.Attributes[0].Value); Assert.Equal("v", linkObject.Header.Split.ParentHeader.Attributes[0].Value);
var modelObjId = ObjectId.FromHash(object_3.Header.Split.Parent.Value.ToByteArray()); var modelObjId = ObjectId.FromHash(linkObject.Header.Split.Parent.Value.ToByteArray());
Assert.Equal(result.Value, modelObjId.ToString()); Assert.Equal(result.Value, modelObjId.ToString());
} }
@ -201,8 +230,8 @@ public class ObjectTest : ObjectTestsBase
var request = Mocker.DeleteRequests.FirstOrDefault(); var request = Mocker.DeleteRequests.FirstOrDefault();
Assert.NotNull(request); Assert.NotNull(request);
Assert.Equal(ContainerId.ToGrpcMessage(), request.Body.Address.ContainerId); Assert.Equal(ContainerId.ToMessage().Value, request.Body.Address.ContainerId.Value);
Assert.Equal(Mocker.ObjectId.ToGrpcMessage(), request.Body.Address.ObjectId); Assert.Equal(Mocker.ObjectId.ToMessage().Value, request.Body.Address.ObjectId.Value);
} }
[Fact] [Fact]
@ -214,13 +243,13 @@ public class ObjectTest : ObjectTestsBase
var request = Mocker.HeadRequests.FirstOrDefault(); var request = Mocker.HeadRequests.FirstOrDefault();
Assert.NotNull(request); Assert.NotNull(request);
Assert.Equal(ContainerId.ToGrpcMessage(), request.Body.Address.ContainerId); Assert.Equal(ContainerId.ToMessage(), request.Body.Address.ContainerId);
Assert.Equal(Mocker.ObjectId.ToGrpcMessage(), request.Body.Address.ObjectId); Assert.Equal(Mocker.ObjectId.ToMessage(), request.Body.Address.ObjectId);
Assert.NotNull(response); Assert.NotNull(response);
Assert.Equal(ContainerId.Value, response.ContainerId.Value); Assert.Equal(ContainerId.Value, response.ContainerId.Value);
Assert.Equal(Mocker.ObjectHeader!.OwnerId!.ToGrpcMessage().ToString(), response.OwnerId!.Value); Assert.Equal(Mocker.ObjectHeader!.OwnerId!.Value, response.OwnerId!.Value);
Assert.Equal(Mocker.ObjectHeader!.Version!.ToString(), response.Version!.ToString()); Assert.Equal(Mocker.ObjectHeader!.Version!.ToString(), response.Version!.ToString());
Assert.Equal(Mocker.HeadResponse!.PayloadLength, response.PayloadLength); Assert.Equal(Mocker.HeadResponse!.PayloadLength, response.PayloadLength);

View file

@ -72,13 +72,14 @@ public class SmokeTests
[Fact] [Fact]
public async void GetSessionTest() public async void GetSessionTest()
{ {
var ecdsaKey = this.key.LoadWif();
using var client = Client.GetInstance(GetOptions(this.key, this.url)); using var client = Client.GetInstance(GetOptions(this.key, this.url));
var token = await client.CreateSessionAsync(new PrmSessionCreate(100)); var token = await client.CreateSessionAsync(new PrmSessionCreate(100));
var session = new Session.SessionToken().Deserialize(token.Token); var session = new Session.SessionToken().Deserialize(token.Token);
var ecdsaKey = this.key.LoadWif();
var owner = OwnerId.FromKey(ecdsaKey); var owner = OwnerId.FromKey(ecdsaKey);
var ownerHash = Base58.Decode(owner.Value); var ownerHash = Base58.Decode(owner.Value);
@ -217,9 +218,10 @@ public class SmokeTests
await foreach (var objId in client.SearchObjectsAsync(searchParam)) await foreach (var objId in client.SearchObjectsAsync(searchParam))
{ {
resultObjectsCount++; resultObjectsCount++;
var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objId));
} }
Assert.True(1 == resultObjectsCount, $"Filter for {filter.Key} doesn't work"); Assert.True(0 < resultObjectsCount, $"Filter for {filter.Key} doesn't work");
} }
[Theory] [Theory]
@ -449,8 +451,8 @@ public class SmokeTests
var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId)); var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId));
Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength);
Assert.Single(objHeader.Attributes); Assert.Single(objHeader.Attributes);
Assert.Equal("fileName", objHeader.Attributes.First().Key); Assert.Equal("fileName", objHeader.Attributes[0].Key);
Assert.Equal("test", objHeader.Attributes.First().Value); Assert.Equal("test", objHeader.Attributes[0].Value);
} }
Assert.True(hasObject); Assert.True(hasObject);
@ -542,7 +544,7 @@ public class MetricsInterceptor() : Interceptor
watch.Stop(); watch.Stop();
// Do somethins with call info // Do something with call info
// var elapsed = watch.ElapsedTicks * 1_000_000/Stopwatch.Frequency; // var elapsed = watch.ElapsedTicks * 1_000_000/Stopwatch.Frequency;
return response; return response;