internal void processMsg(object sender, MsgHandlerEventArgs args)
        {
            bool isClosed         = false;
            AsyncSubscription sub = null;
            Msg raw = null;

            MsgProto mp = new MsgProto();

            ProtocolSerializer.unmarshal(args.Message.Data, mp);

            raw = args.Message;

            lock (mu)
            {
                isClosed = (nc == null);
                subMap.TryGetValue(raw.Subject, out sub);
            }

            if (isClosed || sub == null)
            {
                return;
            }

            sub.processMsg(mp);
        }
        internal void manualAck(StanMsg m)
        {
            if (m == null)
            {
                return;
            }

            rwLock.EnterReadLock();

            string     localAckSubject = ackInbox;
            bool       localManualAck  = options.manualAcks;
            Connection sc = this.sc;

            rwLock.ExitReadLock();

            if (localManualAck == false)
            {
                throw new StanManualAckException();
            }

            if (sc == null)
            {
                throw new StanBadSubscriptionException();
            }

            byte[] b = ProtocolSerializer.createAck(m.proto);
            sc.NATSConnection.Publish(localAckSubject, b);
        }
        internal PublishAck publish(string subject, byte[] data, EventHandler <StanAckHandlerArgs> handler)
        {
            string localAckSubject = null;
            long   localAckTimeout = 0;

            string subj      = this.pubPrefix + "." + subject;
            string guidValue = newGUID();

            byte[] b = ProtocolSerializer.createPubMsg(clientID, guidValue, subject, data);

            PublishAck a = new PublishAck(this, guidValue, handler, opts.PubAckWait);

            lock (mu)
            {
                if (nc == null)
                {
                    throw new StanConnectionClosedException();
                }

                while (!pubAckMap.TryAdd(guidValue, a))
                {
                    var bd = pubAckMap;

                    Monitor.Exit(mu);
                    // Wait for space outside of the lock so
                    // acks can be removed.
                    bd.waitForSpace();
                    Monitor.Enter(mu);

                    if (nc == null)
                    {
                        throw new StanConnectionClosedException();
                    }
                }

                localAckSubject = ackSubject;
                localAckTimeout = opts.ackTimeout;
            }

            try
            {
                nc.Publish(subj, localAckSubject, b);
            }
            catch
            {
                removeAck(guidValue);
                throw;
            }

            return(a);
        }
        internal void unsubscribe(string subject, string inbox, string ackInbox, bool close)
        {
            IConnection lnc;

            lock (mu)
            {
                lnc = nc;
                if (lnc == null)
                {
                    throw new StanConnectionClosedException();
                }
                subMap.Remove(inbox);
            }

            string requestSubject = unsubRequests;

            if (close)
            {
                requestSubject = subCloseRequests;
                if (string.IsNullOrEmpty(requestSubject))
                {
                    throw new StanNoServerSupport();
                }
            }

            UnsubscribeRequest usr = new UnsubscribeRequest();

            usr.ClientID = clientID;
            usr.Subject  = subject;
            usr.Inbox    = ackInbox;
            byte[] b = ProtocolSerializer.marshal(usr);

            var r = lnc.Request(requestSubject, b, 2000);
            SubscriptionResponse sr = new SubscriptionResponse();

            ProtocolSerializer.unmarshal(r.Data, sr);
            if (!string.IsNullOrEmpty(sr.Error))
            {
                throw new StanException(sr.Error);
            }
        }
        private void processAck(object sender, MsgHandlerEventArgs args)
        {
            PubAck pa = new PubAck();

            try
            {
                ProtocolSerializer.unmarshal(args.Message.Data, pa);
            }
            catch (Exception)
            {
                // TODO:  (cls) handle this...
                return;
            }

            PublishAck a = removeAck(pa.Guid);

            if (a != null)
            {
                a.invokeHandler(pa.Guid, pa.Error);
            }
        }
        internal void processMsg(MsgProto mp)
        {
            rwLock.EnterReadLock();

            EventHandler <StanMsgHandlerArgs> cb = handler;
            bool            isManualAck          = options.manualAcks;
            string          localAckSubject      = ackInbox;
            IStanConnection subsSc  = sc;
            IConnection     localNc = null;

            if (subsSc != null)
            {
                localNc = sc.NATSConnection;
            }

            rwLock.ExitReadLock();

            if (cb != null && subsSc != null)
            {
                StanMsgHandlerArgs args = new StanMsgHandlerArgs(new StanMsg(mp, this));
                cb(this, args);
            }

            if (!isManualAck && localNc != null)
            {
                byte[] b = ProtocolSerializer.createAck(mp);
                try
                {
                    localNc.Publish(localAckSubject, b);
                }
                catch (Exception)
                {
                    /*
                     * Ignore - subscriber could have closed the connection
                     * or there's been a connection error.  The server will
                     * resend the unacknowledged messages.
                     */
                }
            }
        }
        // in STAN, much of this code is in the connection module.
        internal void subscribe(string subRequestSubject, string subject, string qgroup, EventHandler <StanMsgHandlerArgs> handler)
        {
            rwLock.EnterWriteLock();
            try
            {
                this.handler += handler;
                this.subject  = subject;

                if (sc == null)
                {
                    throw new StanConnectionClosedException();
                }

                // Listen for actual messages.
                inboxSub = sc.NATSConnection.SubscribeAsync(inbox, sc.processMsg);

                SubscriptionRequest sr = new SubscriptionRequest();
                sr.ClientID      = sc.ClientID;
                sr.Subject       = subject;
                sr.QGroup        = (qgroup == null ? "" : qgroup);
                sr.Inbox         = inbox;
                sr.MaxInFlight   = options.MaxInflight;
                sr.AckWaitInSecs = options.AckWait / 1000;
                sr.StartPosition = options.startAt;
                sr.DurableName   = (options.DurableName == null ? "" : options.DurableName);

                // Conditionals
                switch (sr.StartPosition)
                {
                case StartPosition.TimeDeltaStart:
                    sr.StartTimeDelta = convertTimeSpan(
                        options.useStartTimeDelta ?
                        options.startTimeDelta :
                        (DateTime.UtcNow - options.startTime));
                    break;

                case StartPosition.SequenceStart:
                    sr.StartSequence = options.startSequence;
                    break;
                }

                byte[] b = ProtocolSerializer.marshal(sr);

                // TODO:  Configure request timeout?
                Msg m = sc.NATSConnection.Request(subRequestSubject, b, 2000);

                SubscriptionResponse r = new SubscriptionResponse();
                ProtocolSerializer.unmarshal(m.Data, r);

                if (string.IsNullOrWhiteSpace(r.Error) == false)
                {
                    throw new StanException(r.Error);
                }

                ackInbox = r.AckInbox;
            }
            catch
            {
                if (inboxSub != null)
                {
                    try
                    {
                        inboxSub.Unsubscribe();
                    }
                    catch (NATSTimeoutException)
                    {
                        // NOOP - this is unrecoverable.
                    }
                }
                throw;
            }
            finally
            {
                rwLock.ExitWriteLock();
            }
        }
        public void Close()
        {
            Msg reply = null;

            lock (mu)
            {
                IConnection lnc = nc;
                nc = null;

                if (lnc == null)
                {
                    return;
                }

                if (lnc.IsClosed())
                {
                    return;
                }

                if (ackSubscription != null)
                {
                    ackSubscription.Unsubscribe();
                    ackSubscription = null;
                }

                if (hbSubscription != null)
                {
                    hbSubscription.Unsubscribe();
                    hbSubscription = null;
                }

                if (heartbeatMonitor != null)
                {
                    heartbeatMonitor.Stop();
                }

                CloseRequest req = new CloseRequest();
                req.ClientID = this.clientID;

                try
                {
                    if (this.closeRequests != null)
                    {
                        reply = lnc.Request(closeRequests, ProtocolSerializer.marshal(req));
                    }
                }
                catch (StanBadSubscriptionException)
                {
                    // it's possible we never actually connected.
                    return;
                }

                if (reply != null)
                {
                    CloseResponse resp = new CloseResponse();
                    try
                    {
                        ProtocolSerializer.unmarshal(reply.Data, resp);
                    }
                    catch (Exception e)
                    {
                        throw new StanCloseRequestException(e);
                    }

                    if (!string.IsNullOrEmpty(resp.Error))
                    {
                        throw new StanCloseRequestException(resp.Error);
                    }
                }

                if (ncOwned && lnc != null)
                {
                    lnc.Close();
                }
            }
        }
        internal Connection(string stanClusterID, string clientID, StanOptions options)
        {
            this.clientID = clientID;

            if (options != null)
            {
                opts = new StanOptions(options);
            }
            else
            {
                opts = new StanOptions();
            }

            if (opts.natsConn == null)
            {
                ncOwned = true;
                try
                {
                    nc = new ConnectionFactory().CreateConnection(opts.NatsURL);
                }
                catch (Exception ex)
                {
                    throw new StanConnectionException(ex);
                }
            }
            else
            {
                nc      = opts.natsConn;
                ncOwned = false;
            }

            // create a heartbeat inbox
            string hbInbox = newInbox();

            hbSubscription = nc.SubscribeAsync(hbInbox, processHeartBeat);

            string discoverSubject = opts.discoverPrefix + "." + stanClusterID;

            ConnectRequest req = new ConnectRequest();

            req.ClientID       = this.clientID;
            req.HeartbeatInbox = hbInbox;

            Msg cr;

            try
            {
                cr = nc.Request(discoverSubject,
                                ProtocolSerializer.marshal(req),
                                opts.ConnectTimeout);
            }
            catch (NATSTimeoutException)
            {
                throw new StanConnectRequestTimeoutException();
            }

            ConnectResponse response = new ConnectResponse();

            try
            {
                ProtocolSerializer.unmarshal(cr.Data, response);
            }
            catch (Exception e)
            {
                throw new StanConnectRequestException(e);
            }

            if (!string.IsNullOrEmpty(response.Error))
            {
                throw new StanConnectRequestException(response.Error);
            }

            // capture cluster configuration endpoints to publish and subscribe/unsubscribe
            pubPrefix        = response.PubPrefix;
            subRequests      = response.SubRequests;
            unsubRequests    = response.UnsubRequests;
            subCloseRequests = response.SubCloseRequests;
            closeRequests    = response.CloseRequests;

            // setup the Ack subscription
            ackSubject      = StanConsts.DefaultACKPrefix + "." + newGUID();
            ackSubscription = nc.SubscribeAsync(ackSubject, processAck);

            // TODO:  hardcode or options?
            ackSubscription.SetPendingLimits(1024 * 1024, 32 * 1024 * 1024);

            pubAckMap = new BlockingDictionary <string, PublishAck>(opts.maxPubAcksInflight);

            // Setup server heartbeat monitor
            if (options != null && options.ServerHeartbeatTimeoutMillis > 0)
            {
                heartbeatMonitor = new ServerHeartbeatMonitor(1000, options.ServerHeartbeatTimeoutMillis, () =>
                {
                    options.ServerHeartbeatTimeoutCallback?.Invoke();
                });
                heartbeatMonitor.Start();
            }
        }