public DistributedHost(
            IWorkQueue workQueue,
            ushort listenPort,
            bool isListener       = true,
            int disconnectTimeout = -1,
            INetLogger logger     = null)
        {
            Contract.Requires(listenPort != 0);

            ListenPort     = listenPort;
            this.workQueue = workQueue;
            this.logger    = logger;

            // determine our IP
            // hat tip https://stackoverflow.com/questions/6803073/get-local-ip-address
            IPHostEntry host = Dns.GetHostEntry(Dns.GetHostName());

            IPAddress ipv4Address = host
                                    .AddressList
                                    .FirstOrDefault(ip => ip.AddressFamily == AddressFamily.InterNetwork);

            SocketAddress = new SerializedSocketAddress(new IPEndPoint(ipv4Address, listenPort).Serialize());

            netManager = new NetManager(new Listener(this))
            {
                BroadcastReceiveEnabled    = true,
                UnconnectedMessagesEnabled = true
            };

            if (disconnectTimeout != -1)
            {
                netManager.DisconnectTimeout = disconnectTimeout;
            }

            netPacketProcessor = new NetPacketProcessor();
            RegisterType <SerializedSocketAddress>();
            RegisterType <DistributedId>();
            netPacketProcessor.SubscribeReusable <AnnounceMessage, IPEndPoint>(OnAnnounceReceived);
            netPacketProcessor.SubscribeReusable <AnnounceResponseMessage, IPEndPoint>(OnAnnounceResponseReceived);

            netDataWriter = new NetDataWriter();

            bool managerStarted;

            if (isListener)
            {
                managerStarted = netManager.Start(ListenPort);
            }
            else
            {
                managerStarted = netManager.Start();
            }

            if (!managerStarted)
            {
                throw new PeerException("Could not start netManager");
            }
        }
        /// <summary>
        /// Get the proxies that are owned by this peer.
        /// </summary>
        public IReadOnlyDictionary <DistributedId, IDistributedObject> ProxiesForPeer(SerializedSocketAddress serializedSocketAddress)
        {
            Contract.Requires(netManager.ConnectedPeerList.Any(peer =>
            {
                SerializedSocketAddress peerAddress = new SerializedSocketAddress(peer);
                bool addressesAreEqual = peerAddress.Equals(serializedSocketAddress);
                return(addressesAreEqual);
            }));

            Contract.Requires(proxies.ContainsKey(serializedSocketAddress));

            return(proxies[serializedSocketAddress]);
        }
            /// <summary>
            /// Handle a disconnected peer by deleting all that peer's proxies' local objects, and dropping
            /// all the proxies.
            /// </summary>
            public void OnPeerDisconnected(NetPeer netPeer, DisconnectInfo disconnectInfo)
            {
                SerializedSocketAddress peerAddress = new SerializedSocketAddress(netPeer);

                if (Host.proxies.TryGetValue(peerAddress, out Dictionary <DistributedId, IDistributedObject> peerObjects))
                {
                    // detach them all
                    foreach (IDistributedObject proxy in peerObjects.Values)
                    {
                        proxy.OnDetach();
                    }

                    // and drop the whole collection of proxies
                    Host.proxies.Remove(peerAddress);
                }
            }
        /// <summary>
        /// An announcement has been received (via broadcast); react accordingly.
        /// </summary>
        private void OnAnnounceReceived(AnnounceMessage message, IPEndPoint endpoint)
        {
            // heed only ipv4 for now... TBD what to do about this
            if (endpoint.AddressFamily == AddressFamily.InterNetwork)
            {
                // is this actually our own announcement!?
                SerializedSocketAddress incomingAddress = new SerializedSocketAddress(endpoint.Serialize());
                if (incomingAddress == this.SocketAddress)
                {
                    // ignore this, we're talking to ourselves
                    return;
                }

                PeerAnnounceCount++;

                // do we know this peer already?
                // (could happen in race scenario)
                if (netManager.ConnectedPeerList.Any(peer => peer.EndPoint.Equals(endpoint)))
                {
                    return;
                }

                // did we already respond to this peer?
                if (AnnouncedEndPoints.Contains(endpoint))
                {
                    return;
                }

                // did this peer know us already? (typical scenario given re-announcements)
                if (message.KnownPeers.Contains(SocketAddress))
                {
                    return;
                }

                logger?.WriteNet(NetLogLevel.Trace, $"DistributedHost.OnAnnounceReceived({endpoint}) -- responding. {ConnectionsStatusString()}]");

                // send announce response
                AnnounceResponseMessage response = new AnnounceResponseMessage {
                };
                SendUnconnectedMessage(response, endpoint);
            }
        }
        private void OnDelete(NetPeer netPeer, DistributedId id, bool isRequest)
        {
            SerializedSocketAddress peerAddress = new SerializedSocketAddress(netPeer);

            if (isRequest)
            {
                // owner id may or may not still exist; it's OK if it doesn't.
                if (!owners.ContainsKey(id))
                {
                    // do nothing; ignore the delete request altogether
                }
                else
                {
                    // we will accept this request... for testing purposes.
                    // TBD if this is the right thing in general!
                    IDistributedObject distributedObject = owners[id];

                    // and tell all proxies
                    foreach (NetPeer proxyPeer in NetPeers)
                    {
                        distributedObject.DistributedType.SendDeleteMessageInternal(proxyPeer, false);
                    }

                    owners.Remove(id);
                    distributedObject.OnDelete();
                }
            }
            else
            {
                // this is an authoritative delete message from the owner.
                // we expect strong consistency here so the id should still exist.
                Contract.Requires(proxies[peerAddress].ContainsKey(id));

                IDistributedObject proxy = proxies[peerAddress][id];
                proxies[peerAddress].Remove(id);
                proxy.Delete();
            }
        }
        /// <summary>
        /// This is a new proxy being created on this peer, owned by netPeer.
        /// </summary>
        private void AddProxy(SerializedSocketAddress peerAddress, IDistributedObject proxy)
        {
            Contract.Requires(!proxy.IsOwner);

            proxies[peerAddress].Add(proxy.Id, proxy);
        }