protected abstract Task OnRequestAsync(StratumClient <TClientContext> client,
                                        Timestamped <JsonRpcRequest> request);
        protected virtual void OnReceiveComplete(StratumClient <TClientContext> client)
        {
            logger.Debug(() => $"[{LogCat}] [{client.ConnectionId}] Received EOF");

            DisconnectClient(client);
        }
 protected abstract void OnConnect(StratumClient <TClientContext> client);
        private void OnClientConnected(Tcp con, IPEndPoint endpointConfig, Loop loop)
        {
            try
            {
                var remoteEndPoint = con.GetPeerEndPoint();

                // get rid of banned clients as early as possible
                if (banManager?.IsBanned(remoteEndPoint.Address) == true)
                {
                    logger.Trace(() => $"[{LogCat}] Disconnecting banned ip {remoteEndPoint.Address}");
                    con.Dispose();
                    return;
                }

                var connectionId = CorrelationIdGenerator.GetNextId();
                logger.Trace(() => $"[{LogCat}] Accepting connection [{connectionId}] from {remoteEndPoint.Address}:{remoteEndPoint.Port}");

                // setup client
                var client = new StratumClient <TClientContext>();
                client.Init(loop, con, ctx, endpointConfig, connectionId);

                // request subscription
                var sub = client.Requests
                          .Do(x => logger.Trace(() => $"[{LogCat}] [{client.ConnectionId}] Received request {x.Value.Method} [{x.Value.Id}]"))
                          .Select(tsRequest => Observable.FromAsync(() => Task.Run(() => // get off of LibUV event-loop-thread immediately
                {
                    var request = tsRequest.Value;
                    logger.Trace(() => $"[{LogCat}] [{client.ConnectionId}] Dispatching request {request.Method} [{request.Id}]");

                    try
                    {
                        // boot pre-connected clients
                        if (banManager?.IsBanned(client.RemoteEndpoint.Address) == true)
                        {
                            logger.Trace(() => $"[{LogCat}] [{connectionId}] Disconnecting banned client @ {remoteEndPoint.Address}");
                            DisconnectClient(client);
                            return;
                        }

                        OnRequestAsync(client, tsRequest).Wait();
                    }

                    catch (Exception ex)
                    {
                        logger.Error(ex, () => $"Error handling request: {request.Method}");
                    }
                })))
                          .Concat()
                          .Subscribe(_ => { }, ex => OnReceiveError(client, ex), () => OnReceiveComplete(client));

                // ensure subscription is disposed on loop thread
                var disposer = loop.CreateAsync((handle) =>
                {
                    sub.Dispose();

                    handle.Dispose();
                });

                client.Subscription = Disposable.Create(() => { disposer.Send(); });

                // register client
                lock (clients)
                {
                    clients[connectionId] = client;
                }

                OnConnect(client);
            }

            catch (Exception ex)
            {
                logger.Error(ex, () => nameof(OnClientConnected));
            }
        }