public override async Task Read(IAsyncStreamReader <ReadReq> requestStream,
                                        IServerStreamWriter <ReadResp> responseStream, ServerCallContext context)
        {
            if (!await requestStream.MoveNext().ConfigureAwait(false))
            {
                return;
            }

            if (requestStream.Current.ContentCase != ReadReq.ContentOneofCase.Options)
            {
                throw new InvalidOperationException();
            }

            var options = requestStream.Current.Options;
            var user    = context.GetHttpContext().User;

            if (!await _authorizationProvider.CheckAccessAsync(user,
                                                               ProcessMessagesOperation.WithParameter(Plugins.Authorization.Operations.Subscriptions.Parameters.StreamId(options.StreamName)), context.CancellationToken).ConfigureAwait(false))
            {
                throw AccessDenied();
            }
            var connectionName =
                context.RequestHeaders.FirstOrDefault(x => x.Key == Constants.Headers.ConnectionName)?.Value ??
                "<unknown>";
            var correlationId   = Guid.NewGuid();
            var uuidOptionsCase = options.UuidOption.ContentCase;

            await using var enumerator = new PersistentStreamSubscriptionEnumerator(correlationId, connectionName,
                                                                                    _publisher, options.StreamName, options.GroupName, options.BufferSize, user, context.CancellationToken);

            var subscriptionId = await enumerator.Started.ConfigureAwait(false);

            var read = requestStream.ForEachAsync(HandleAckNack);

            await responseStream.WriteAsync(new ReadResp {
                SubscriptionConfirmation = new ReadResp.Types.SubscriptionConfirmation {
                    SubscriptionId = subscriptionId
                }
            }).ConfigureAwait(false);

            while (await enumerator.MoveNextAsync().ConfigureAwait(false))
            {
                await responseStream.WriteAsync(new ReadResp {
                    Event = ConvertToReadEvent(enumerator.Current)
                }).ConfigureAwait(false);
            }

            await read.ConfigureAwait(false);

            ValueTask HandleAckNack(ReadReq request)
            {
                _publisher.Publish(request.ContentCase switch {
                    ReadReq.ContentOneofCase.Ack => new ClientMessage.PersistentSubscriptionAckEvents(
                        correlationId, correlationId, new NoopEnvelope(), subscriptionId,
                        request.Ack.Ids.Select(id => Uuid.FromDto(id).ToGuid()).ToArray(), user),
                    ReadReq.ContentOneofCase.Nack =>
                    new ClientMessage.PersistentSubscriptionNackEvents(
                        correlationId, correlationId, new NoopEnvelope(), subscriptionId,
                        request.Nack.Reason, request.Nack.Action switch {
                        ReadReq.Types.Nack.Types.Action.Unknown => NakAction.Unknown,
                        ReadReq.Types.Nack.Types.Action.Park => NakAction.Park,
                        ReadReq.Types.Nack.Types.Action.Retry => NakAction.Retry,
                        ReadReq.Types.Nack.Types.Action.Skip => NakAction.Skip,
                        ReadReq.Types.Nack.Types.Action.Stop => NakAction.Stop,
                        _ => throw new InvalidOperationException()
                    },
                        request.Nack.Ids.Select(id => Uuid.FromDto(id).ToGuid()).ToArray(), user),
                    _ => throw new InvalidOperationException()
                });
        public override async Task Read(IAsyncStreamReader <ReadReq> requestStream,
                                        IServerStreamWriter <ReadResp> responseStream, ServerCallContext context)
        {
            if (!await requestStream.MoveNext().ConfigureAwait(false))
            {
                return;
            }

            if (requestStream.Current.ContentCase != ReadReq.ContentOneofCase.Options)
            {
                throw new InvalidOperationException();
            }

            var options        = requestStream.Current.Options;
            var user           = context.GetHttpContext().User;
            var connectionName =
                context.RequestHeaders.FirstOrDefault(x => x.Key == Constants.Headers.ConnectionName)?.Value ??
                "<unknown>";
            var    correlationId   = Guid.NewGuid();
            var    uuidOptionsCase = options.UuidOption.ContentCase;
            var    source          = new TaskCompletionSource <bool>();
            string subscriptionId  = default;

            await using var _ = context.CancellationToken.Register(source.SetCanceled).ConfigureAwait(false);

#pragma warning disable 4014
            requestStream.ForEachAsync(HandleAckNack);
#pragma warning restore 4014

            await using var enumerator = new PersistentStreamSubscriptionEnumerator(correlationId, connectionName,
                                                                                    _queue, options.StreamName, options.GroupName, options.BufferSize, user, context.CancellationToken);

            subscriptionId = await enumerator.Started.ConfigureAwait(false);

            await responseStream.WriteAsync(new ReadResp {
                SubscriptionConfirmation = new ReadResp.Types.SubscriptionConfirmation {
                    SubscriptionId = subscriptionId
                }
            }).ConfigureAwait(false);

            while (await enumerator.MoveNextAsync().ConfigureAwait(false))
            {
                await responseStream.WriteAsync(new ReadResp {
                    Event = ConvertToReadEvent(enumerator.Current)
                }).ConfigureAwait(false);
            }

            Task HandleAckNack(ReadReq request)
            {
                _queue.Publish(request.ContentCase switch {
                    ReadReq.ContentOneofCase.Ack => (Message)
                    new ClientMessage.PersistentSubscriptionAckEvents(
                        correlationId, correlationId, new NoopEnvelope(), subscriptionId,
                        request.Ack.Ids.Select(id => Uuid.FromDto(id).ToGuid()).ToArray(), user),
                    ReadReq.ContentOneofCase.Nack =>
                    new ClientMessage.PersistentSubscriptionNackEvents(
                        correlationId, correlationId, new NoopEnvelope(), subscriptionId,
                        request.Nack.Reason, request.Nack.Action switch {
                        ReadReq.Types.Nack.Types.Action.Unknown => NakAction.Unknown,
                        ReadReq.Types.Nack.Types.Action.Park => NakAction.Park,
                        ReadReq.Types.Nack.Types.Action.Retry => NakAction.Retry,
                        ReadReq.Types.Nack.Types.Action.Skip => NakAction.Skip,
                        ReadReq.Types.Nack.Types.Action.Stop => NakAction.Stop,
                        _ => throw new InvalidOperationException()
                    },
                        request.Nack.Ids.Select(id => Uuid.FromDto(id).ToGuid()).ToArray(), user),
                    _ => throw new InvalidOperationException()
                });
        public override async Task Read(IAsyncStreamReader <ReadReq> requestStream,
                                        IServerStreamWriter <ReadResp> responseStream, ServerCallContext context)
        {
            if (!await requestStream.MoveNext().ConfigureAwait(false))
            {
                return;
            }

            if (requestStream.Current.ContentCase != ReadReq.ContentOneofCase.Options)
            {
                throw new InvalidOperationException();
            }

            var options = requestStream.Current.Options;
            var user    = context.GetHttpContext().User;

            string streamId = null;

            switch (options.StreamOptionCase)
            {
            case ReadReq.Types.Options.StreamOptionOneofCase.StreamIdentifier:
                streamId = options.StreamIdentifier;
                break;

            case ReadReq.Types.Options.StreamOptionOneofCase.All:
                streamId = SystemStreams.AllStream;
                break;

            default:
                throw new InvalidOperationException();
            }

            if (!await _authorizationProvider.CheckAccessAsync(user,
                                                               ProcessMessagesOperation.WithParameter(Plugins.Authorization.Operations.Subscriptions.Parameters.StreamId(streamId)), context.CancellationToken).ConfigureAwait(false))
            {
                throw RpcExceptions.AccessDenied();
            }
            var connectionName =
                context.RequestHeaders.FirstOrDefault(x => x.Key == Constants.Headers.ConnectionName)?.Value ??
                "<unknown>";
            var correlationId   = Guid.NewGuid();
            var uuidOptionsCase = options.UuidOption.ContentCase;

            await using var enumerator = new PersistentStreamSubscriptionEnumerator(correlationId, connectionName,
                                                                                    _publisher, streamId, options.GroupName, options.BufferSize, user, context.CancellationToken);

            var subscriptionId = await enumerator.Started.ConfigureAwait(false);

            var read = ValueTask.CompletedTask;
            var cts  = new CancellationTokenSource();

            try {
                read = PumpRequestStream(cts.Token);

                await responseStream.WriteAsync(new ReadResp {
                    SubscriptionConfirmation = new ReadResp.Types.SubscriptionConfirmation {
                        SubscriptionId = subscriptionId
                    }
                }).ConfigureAwait(false);

                while (await enumerator.MoveNextAsync().ConfigureAwait(false))
                {
                    await responseStream.WriteAsync(new ReadResp {
                        Event = ConvertToReadEvent(enumerator.Current)
                    }).ConfigureAwait(false);
                }
            } catch (IOException) {
                Log.Information("Subscription {correlationId} to {subscriptionId} disposed. The request stream was closed.", correlationId, subscriptionId);
            } finally {
                // make sure we stop reading the request stream before leaving this method
                cts.Cancel();
                await read.ConfigureAwait(false);
            }

            async ValueTask PumpRequestStream(CancellationToken token)
            {
                try {
                    await requestStream.ForEachAsync(HandleAckNack, token).ConfigureAwait(false);
                } catch {
                }
            }

            ValueTask HandleAckNack(ReadReq request)
            {
                _publisher.Publish(request.ContentCase switch {
                    ReadReq.ContentOneofCase.Ack => new ClientMessage.PersistentSubscriptionAckEvents(
                        correlationId, correlationId, new NoopEnvelope(), subscriptionId,
                        request.Ack.Ids.Select(id => Uuid.FromDto(id).ToGuid()).ToArray(), user),
                    ReadReq.ContentOneofCase.Nack =>
                    new ClientMessage.PersistentSubscriptionNackEvents(
                        correlationId, correlationId, new NoopEnvelope(), subscriptionId,
                        request.Nack.Reason, request.Nack.Action switch {
                        ReadReq.Types.Nack.Types.Action.Unknown => NakAction.Unknown,
                        ReadReq.Types.Nack.Types.Action.Park => NakAction.Park,
                        ReadReq.Types.Nack.Types.Action.Retry => NakAction.Retry,
                        ReadReq.Types.Nack.Types.Action.Skip => NakAction.Skip,
                        ReadReq.Types.Nack.Types.Action.Stop => NakAction.Stop,
                        _ => throw RpcExceptions.InvalidArgument(request.Nack.Action)
                    },
                        request.Nack.Ids.Select(id => Uuid.FromDto(id).ToGuid()).ToArray(), user),
                    _ => throw RpcExceptions.InvalidArgument(request.ContentCase)
                });
        public override async Task Read(IAsyncStreamReader <ReadReq> requestStream,
                                        IServerStreamWriter <ReadResp> responseStream, ServerCallContext context)
        {
            if (!await requestStream.MoveNext())
            {
                return;
            }

            if (requestStream.Current.ContentCase != ReadReq.ContentOneofCase.Options)
            {
                throw new InvalidOperationException();
            }

            var options = requestStream.Current.Options;
            var user    = await GetUser(_authenticationProvider, context.RequestHeaders);

            var    correlationId  = Guid.NewGuid();
            var    source         = new TaskCompletionSource <bool>();
            string subscriptionId = default;

            context.CancellationToken.Register(source.SetCanceled);

#pragma warning disable 4014
            Task.Run(() => requestStream.ForEachAsync(HandleAckNack));
#pragma warning restore 4014

            await using var enumerator = new PersistentStreamSubscriptionEnumerator(correlationId, _queue,
                                                                                    options.StreamName, options.GroupName, options.BufferSize, user, context.CancellationToken);

            subscriptionId = await enumerator.Started;

            await responseStream.WriteAsync(new ReadResp {
                Empty = new ReadResp.Types.Empty()
            });

            while (await enumerator.MoveNextAsync())
            {
                await responseStream.WriteAsync(new ReadResp {
                    Event = ConvertToReadEvent(enumerator.Current)
                });
            }

            Task HandleAckNack(ReadReq request)
            {
                _queue.Publish(request.ContentCase switch {
                    ReadReq.ContentOneofCase.Ack => (Message)
                    new ClientMessage.PersistentSubscriptionAckEvents(
                        correlationId, correlationId, new NoopEnvelope(), subscriptionId,
                        request.Ack.Ids.Select(id => Uuid.FromDto(id).ToGuid()).ToArray(), user),
                    ReadReq.ContentOneofCase.Nack =>
                    new ClientMessage.PersistentSubscriptionNackEvents(
                        correlationId, correlationId, new NoopEnvelope(), subscriptionId,
                        request.Nack.Reason, request.Nack.Action switch {
                        ReadReq.Types.Nack.Types.Action.Unknown => NakAction.Unknown,
                        ReadReq.Types.Nack.Types.Action.Park => NakAction.Park,
                        ReadReq.Types.Nack.Types.Action.Retry => NakAction.Retry,
                        ReadReq.Types.Nack.Types.Action.Skip => NakAction.Skip,
                        ReadReq.Types.Nack.Types.Action.Stop => NakAction.Stop,
                        _ => throw new InvalidOperationException()
                    },
                        request.Nack.Ids.Select(id => Uuid.FromDto(id).ToGuid()).ToArray(), user),
                    _ => throw new InvalidOperationException()
                });