using System; using System.Collections.Generic; using System.Security.Cryptography; using System.Threading.Tasks; using FrostFS.Container; using FrostFS.Refs; using FrostFS.SDK.Client; using FrostFS.SDK.Client.Mappers.GRPC; using FrostFS.SDK.Cryptography; using FrostFS.Session; namespace FrostFS.SDK.Client; internal sealed class ContainerServiceProvider(ContainerService.ContainerServiceClient service, ClientContext clientCtx) : ContextAccessor(clientCtx) { private SessionProvider? sessions; public async ValueTask GetDefaultSession(ISessionToken args, CallContext ctx) { sessions ??= new(ClientContext); if (!ClientContext.SessionCache!.TryGetValue(ClientContext.SessionCacheKey, out var token)) { var protoToken = await sessions.GetDefaultSession(args, ctx).ConfigureAwait(false); token = new FrostFsSessionToken(protoToken); ClientContext.SessionCache.SetValue(ClientContext.SessionCacheKey, token); } if (token == null) { throw new FrostFsException("Cannot create session"); } return token; } internal async Task GetContainerAsync(PrmContainerGet args, CallContext ctx) { GetRequest request = GetContainerRequest(args.Container.GetContainerID(), args.XHeaders, ClientContext.Key.ECDsaKey); var response = await service.GetAsync(request, null, ctx.GetDeadline(), ctx.CancellationToken); Verifier.CheckResponse(response); return response.Body.Container.ToModel(); } internal async IAsyncEnumerable ListContainersAsync(PrmContainerGetAll args, CallContext ctx) { var request = new ListRequest { Body = new() { OwnerId = ClientContext.Owner.OwnerID } }; request.AddMetaHeader(args.XHeaders); request.Sign(ClientContext.Key.ECDsaKey); var response = await service.ListAsync(request, null, ctx.GetDeadline(), ctx.CancellationToken); Verifier.CheckResponse(response); foreach (var cid in response.Body.ContainerIds) { yield return new FrostFsContainerId(Base58.Encode(cid.Value.Span)); } } internal async Task CreateContainerAsync(PrmContainerCreate args, CallContext ctx) { var grpcContainer = args.Container.GetContainer(); grpcContainer.OwnerId ??= ClientContext.Owner.OwnerID; grpcContainer.Version ??= ClientContext.Version.VersionID; var request = new PutRequest { Body = new PutRequest.Types.Body { Container = grpcContainer, Signature = ClientContext.Key.ECDsaKey.SignRFC6979(grpcContainer) } }; var sessionToken = (args.SessionToken ?? await GetDefaultSession(args, ctx).ConfigureAwait(false)) ?? throw new FrostFsException("Cannot create session token"); var protoToken = sessionToken.CreateContainerToken( null, ContainerSessionContext.Types.Verb.Put, ClientContext.Key); request.AddMetaHeader(args.XHeaders, protoToken); request.Sign(ClientContext.Key.ECDsaKey); var response = await service.PutAsync(request, null, ctx.GetDeadline(), ctx.CancellationToken); Verifier.CheckResponse(response); await WaitForContainer(WaitExpects.Exists, response.Body.ContainerId, args.WaitParams, ctx).ConfigureAwait(false); return new FrostFsContainerId(response.Body.ContainerId); } internal async Task DeleteContainerAsync(PrmContainerDelete args, CallContext ctx) { var request = new DeleteRequest { Body = new DeleteRequest.Types.Body { ContainerId = args.ContainerId.ToMessage(), Signature = ClientContext.Key.ECDsaKey.SignRFC6979(args.ContainerId.ToMessage().Value) } }; var sessionToken = args.SessionToken ?? await GetDefaultSession(args, ctx).ConfigureAwait(false); var protoToken = sessionToken.CreateContainerToken( request.Body.ContainerId, ContainerSessionContext.Types.Verb.Delete, ClientContext.Key); request.AddMetaHeader(args.XHeaders, protoToken); request.Sign(ClientContext.Key.ECDsaKey); var response = await service.DeleteAsync(request, null, ctx.GetDeadline(), ctx.CancellationToken); Verifier.CheckResponse(response); await WaitForContainer(WaitExpects.Removed, request.Body.ContainerId, args.WaitParams, ctx) .ConfigureAwait(false); Verifier.CheckResponse(response); } private static GetRequest GetContainerRequest(ContainerID id, string[] xHeaders, ECDsa key) { var request = new GetRequest { Body = new GetRequest.Types.Body { ContainerId = id } }; request.AddMetaHeader(xHeaders); request.Sign(key); return request; } private enum WaitExpects { Exists, Removed } private async Task WaitForContainer(WaitExpects expect, ContainerID id, PrmWait waitParams, CallContext ctx) { var request = GetContainerRequest(id, [], ClientContext.Key.ECDsaKey); async Task action() { var response = await service.GetAsync(request, null, ctx.GetDeadline(), ctx.CancellationToken); Verifier.CheckResponse(response); } await WaitFor(action, expect, waitParams).ConfigureAwait(false); } private static async Task WaitFor( Func action, WaitExpects expect, PrmWait waitParams) { var deadLine = waitParams.GetDeadline(); while (true) { try { await action().ConfigureAwait(false); if (expect == WaitExpects.Exists) return; if (DateTime.UtcNow >= deadLine) throw new TimeoutException(); await Task.Delay(waitParams.PollInterval).ConfigureAwait(false); } catch (FrostFsResponseException ex) { if (DateTime.UtcNow >= deadLine) throw new TimeoutException(); if (ex.Status?.Code != FrostFsStatusCode.ContainerNotFound) throw; if (expect == WaitExpects.Removed) return; await Task.Delay(waitParams.PollInterval).ConfigureAwait(false); } } } }