Esempio n. 1
0
        // NOTE: It's not always strictly a data error. So this method could use a better name.
        /// <summary>
        ///     Call this when a RemotePeer provides invalid network data or otherwise needs to be disconnected.
        ///     If the remote peer is a client, they will stop providing network messages and will be
        ///     disconnected/kicked (depending on our authority) at the next call to Update.
        ///     If the remote peer is the server, we will disconnect from the network and a
        ///     <see cref="NetworkDisconnectionException" /> will be thrown.
        /// </summary>
        /// <remarks>
        ///     Application layer should call this when it cannot throw back into the network system
        ///     (from its event handlers) to get an orderly disconnection (ie: outside of Update).
        /// </remarks>
        public void NetworkDataError(RemotePeer remotePeer, Exception exception)
        {
            if (remotePeer == null)
            {
                throw new ArgumentNullException("remotePeer");
            }
            if (peerManager == null)
            {
                throw new InvalidOperationException("Network not active");
            }
            // (really we should be checking whether the remote peer is our's)

            Log("Network data error from " + remotePeer.PeerInfo);
            LogException(exception);

            // Stop any more messages from being read, until we can disconnect them properly!
            remotePeer.ClearMessageQueue();

            if (remotePeer.PeerInfo.IsServer)
            {
                DisconnectAndThrow(UserVisibleStrings.ErrorInDataFromServer);
            }
            else
            {
                networkDataErrorKickList.Add(remotePeer);
            }
        }
Esempio n. 2
0
 public PendingConnection(double timeOut, RemotePeer remotePeer)
 {
     this.timeOut              = timeOut;
     this.remotePeer           = remotePeer;
     this.incommingConnections = new SortedSet <int>();
     this.outgoingConnections  = new SortedSet <int>();
 }
Esempio n. 3
0
        private void HandleReportDisputeDisconnection(NetIncomingMessage message)
        {
            RemotePeer disputer = (message.SenderConnection.Tag as RemotePeer);
            int        reporterId;

            try
            {
                reporterId = message.ReadInt32();
            }
            catch (Exception e) { throw new ProtocolException("Bad disconnection dispute", e); }


            // Try and find the matching pending disconnect:
            for (int i = pendingDisconnects.Count - 1; i >= 0; i--)
            {
                if (pendingDisconnects[i].failedConnectFrom == reporterId && pendingDisconnects[i].failedConnectTo == disputer.PeerInfo.ConnectionId)
                {
                    RemotePeer reporter = FindRemotePeerById(reporterId);
                    owner.Log(disputer.PeerInfo.ToString() + " successfully disputed disconnection by " + reporter.PeerInfo.ToString());
                    Kick(reporter);
                }
            }

            // If we get to here, there was no matching pending disconnect. But this could be because they were already disconnected.
        }
Esempio n. 4
0
        void BecomeApplicationConnected(RemotePeer remotePeer)
        {
            Debug.Assert(!remotePeer.PeerInfo.IsApplicationConnected);
            remotePeer.PeerInfo.IsApplicationConnected = true;

            owner.Log("Application Connected: " + remotePeer.PeerInfo);
            owner.AddApplicationConnection(remotePeer);


            var listNotApplicationConnected = ListNotApplicationConnected(remotePeer.Connection);

            if (listNotApplicationConnected.Count > 0)
            {
                var notAppConnectedMessage = NetPeer.CreateMessage();
                notAppConnectedMessage.Write(P2PServerMessage.PeerBecameApplicationConnected);
                notAppConnectedMessage.Write(remotePeer.PeerInfo.ConnectionId);
                NetPeer.SendMessage(notAppConnectedMessage, listNotApplicationConnected, NetDeliveryMethod.ReliableOrdered, 0);
            }


            var joinMessage = NetPeer.CreateMessage();

            joinMessage.Write(P2PServerMessage.PeerBecameApplicationConnected);
            joinMessage.Write(remotePeer.PeerInfo.ConnectionId);
            var connectedMessage = NetPeer.CreateMessage();

            connectedMessage.Write(P2PServerMessage.PeerBecameApplicationConnected);
            connectedMessage.Write(remotePeer.PeerInfo.ConnectionId);

            owner.NetworkApplication.JoinOnServer(remotePeer, joinMessage, connectedMessage);

            NetPeer.SendMessage(joinMessage, ListApplicationConnected(remotePeer.Connection), NetDeliveryMethod.ReliableOrdered, 0);
            NetPeer.SendMessage(connectedMessage, remotePeer.Connection, NetDeliveryMethod.ReliableOrdered, 0);
        }
Esempio n. 5
0
        void BecomeApplicationDisconnected(RemotePeer remotePeer)
        {
            Debug.Assert(remotePeer.PeerInfo.IsApplicationConnected);
            remotePeer.PeerInfo.IsApplicationConnected = false;

            owner.Log("Application Disconnected: " + remotePeer.PeerInfo);
            owner.RemoveApplicationConnection(remotePeer);


            var listNotApplicationConnected = ListNotApplicationConnected(remotePeer.Connection);

            if (listNotApplicationConnected.Count > 0)
            {
                var notAppConnectedMessage = NetPeer.CreateMessage();
                notAppConnectedMessage.Write(P2PServerMessage.PeerLeftNetwork);
                notAppConnectedMessage.Write(remotePeer.PeerInfo.ConnectionId);
                NetPeer.SendMessage(notAppConnectedMessage, listNotApplicationConnected, NetDeliveryMethod.ReliableOrdered, 0);
            }


            var message = NetPeer.CreateMessage();

            message.Write(P2PServerMessage.PeerLeftNetwork);
            message.Write(remotePeer.PeerInfo.ConnectionId);

            owner.NetworkApplication.LeaveOnServer(remotePeer, message);

            NetPeer.SendMessage(message, ListApplicationConnected(remotePeer.Connection), NetDeliveryMethod.ReliableOrdered, 0);
        }
        public UnconnectedRemotePeer(P2PClient owner, RemotePeer remotePeer, int connectionToken, bool initiateConnection)
        {
            this.owner = owner;

            this.remotePeer         = remotePeer;
            this.connectionToken    = connectionToken;
            this.initiateConnection = initiateConnection;

            Debug.Assert(!remotePeer.IsConnected); // <- our job is to wait until we get this connection


            // Timing:
            double now = NetTime.Now;

            nextTick = now + tickRate;

            // The time-out times are calculated in the design document ("P2P Network.pptx")
            // They are dependent on the current Lidgren config, some Lidgren internals, and the punch-through settings (below)
            if (initiateConnection)
            {
                timeOutAt = now + 14; // seconds to receive punch-through message from other end so we can start initiating connection
            }
            else
            {
                timeOutAt = now + 32; // seconds to receive "Connect" (as "Accept") from other end initiating connection
            }
            // Send first punch-through:
            SendNatPunchThrough();
        }
Esempio n. 7
0
        private const double pendingConnectionTimeout = 60;         // seconds (calculated in "P2P Network.pptx")


        private void StartPendingConnection(RemotePeer remotePeer)
        {
            Debug.Assert(!pendingConnections.ContainsKey(remotePeer.PeerInfo.ConnectionId));

            pendingConnections.Add(remotePeer.PeerInfo.ConnectionId,
                                   new PendingConnection(NetTime.Now + pendingConnectionTimeout, remotePeer));
        }
Esempio n. 8
0
        internal void RemoveApplicationConnection(RemotePeer remotePeer)
        {
            Debug.Assert(remotePeerList.Contains(remotePeer));

            remotePeerList.Remove(remotePeer);
            broadcastList.Remove(remotePeer.Connection);
        }
Esempio n. 9
0
 private void NetworkDataError(RemotePeer remotePeer, Exception exception)
 {
     owner.Log("Network data error from " + (remotePeer != null ? remotePeer.PeerInfo.ToString() : "unknown peer"));
     owner.LogException(exception);
     if (remotePeer != null)
     {
         Kick(remotePeer); // buh-bye
     }
 }
Esempio n. 10
0
        public void HandleMessage(NetIncomingMessage message, ref bool recycle)
        {
            Debug.Assert(!didDisconnect);

            RemotePeer senderRemotePeer = null;

            if (message.SenderConnection != null)
            {
                senderRemotePeer = message.SenderConnection.Tag as RemotePeer;
            }

            try
            {
                switch (message.MessageType)
                {
                case NetIncomingMessageType.DiscoveryRequest:                         // (Clients trying to find LAN games)
                {
                    var response = NetPeer.CreateMessage();
                    DiscoveredGame.WriteDiscoveryResponse(owner.appConfig, response, owner.GameInfo, IsFull,
                                                          owner.NetworkApplication.GetDiscoveryData());
                    NetPeer.SendDiscoveryResponse(response, message.SenderEndPoint);
                }
                break;


                case NetIncomingMessageType.StatusChanged:
                    var status = (NetConnectionStatus)message.ReadByte();
                    switch (status)
                    {
                    case NetConnectionStatus.Connected:
                        HandleConnection(message.SenderConnection);
                        break;

                    case NetConnectionStatus.Disconnected:
                        HandleDisconnection(message.SenderConnection, message);
                        break;
                    }

                    break;

                case NetIncomingMessageType.UnconnectedData:
                    HandleUnconnectedMessage(message);
                    break;

                case NetIncomingMessageType.Data:
                    HandleNetworkManagementFromClient(message);
                    break;
                }
            }
            catch (NetworkDataException exception)
            {
                NetworkDataError(senderRemotePeer, exception);
            }
        }
Esempio n. 11
0
        internal void AddApplicationConnection(RemotePeer remotePeer)
        {
            Debug.Assert(remotePeer.PeerInfo.IsApplicationConnected);

            Debug.Assert(!remotePeerList.Contains(remotePeer));
            Debug.Assert(!broadcastList.Contains(remotePeer.Connection));

            remotePeerList.Add(remotePeer);
            if (remotePeer.Connection != null)
            {
                broadcastList.Add(remotePeer.Connection);
            }
        }
Esempio n. 12
0
 private RemotePeer FindRemotePeerBySideChannelId(ulong sideChannelId)
 {
     foreach (NetConnection connection in locallyConnected)
     {
         RemotePeer remotePeer = connection.Tag as RemotePeer;
         Debug.Assert(remotePeer != null);
         if (remotePeer.PeerInfo.SideChannelId == sideChannelId)
         {
             return(remotePeer);
         }
     }
     return(null);
 }
Esempio n. 13
0
 private RemotePeer FindRemotePeerById(int connectionId)
 {
     foreach (NetConnection connection in locallyConnected)
     {
         RemotePeer remotePeer = connection.Tag as RemotePeer;
         Debug.Assert(remotePeer != null);
         if (remotePeer.PeerInfo.ConnectionId == connectionId)
         {
             return(remotePeer);
         }
     }
     return(null);
 }
Esempio n. 14
0
        private void HandleNetworkManagementFromClient(NetIncomingMessage message)
        {
            if (message.SenderConnection == null)
            {
                Debug.Assert(false); // This should never happen, unless Lidgren does something weird
                return;
            }

            RemotePeer remotePeer = message.SenderConnection.Tag as RemotePeer;

            if (remotePeer == null)
            {
                // Should never get here... unless Lidgren is being dumb (race condition?). At least assert our own state:
                Debug.Assert(!locallyConnected.Contains(message.SenderConnection));
                return;
            }

            if (message.DeliveryMethod != NetDeliveryMethod.ReliableOrdered || message.SequenceChannel != 0)
            {
                Debug.Assert(false); // <- Message should have been handled elsewhere
                return;
            }

            var messageType = message.TryReadP2PClientMessage();

            switch (messageType)
            {
            case P2PClientMessage.Connected:
                HandleReportConnected(message);
                break;

            case P2PClientMessage.Disconnected:
                HandleReportDisconnected(message);
                break;

            case P2PClientMessage.DisputeDisconnection:
                HandleReportDisputeDisconnection(message);
                break;

            case P2PClientMessage.RTTInitialised:
                MarkRemoteInitialisedRTT(remotePeer);
                break;

            case P2PClientMessage.AcceptHostMigration:
                AcceptHostMigration(remotePeer);
                break;

            default:
                throw new ProtocolException("Bad network client message: " + messageType);
            }
        }
Esempio n. 15
0
        void MarkRemoteInitialisedRTT(RemotePeer remotePeer)
        {
            PendingConnection pc;

            if (pendingConnections.TryGetValue(remotePeer.PeerInfo.ConnectionId, out pc))
            {
                pc.remoteInitialisedRTT = true;
                ReadyCheck(pc);
            }
            else
            {
                throw new ProtocolException("Unexpected RTT initialised report from " + remotePeer.PeerInfo);
            }
        }
Esempio n. 16
0
        public void Kick(RemotePeer remotePeer)
        {
            Debug.Assert(remotePeer.IsConnected == locallyConnected.Contains(remotePeer.Connection));

            if (!remotePeer.IsConnected)
            {
                return; // Already removed
            }
            owner.Log("Kicking " + remotePeer.PeerInfo.ToString());

            locallyConnected.Remove(remotePeer.Connection);
            remotePeer.Disconnect(DisconnectStrings.Kicked);
            RemoveFromNetwork(remotePeer);
        }
Esempio n. 17
0
        private void UpdatePendingDisconnects(double currentTime)
        {
            double now = NetTime.Now;

            // Only take things from the front of the queue (because when something gets kicked, the pendingDisconnects list can change)
            while (pendingDisconnects.Count > 0 && pendingDisconnects[0].timeOut < currentTime)
            {
                // Timed out:
                PendingDisconnect pd = pendingDisconnects[0];

                RemotePeer remotePeer = FindRemotePeerById(pd.failedConnectTo);
                owner.Log("Timed out after disconnect: " + remotePeer.PeerInfo.ToString());
                Kick(remotePeer);
            }
        }
Esempio n. 18
0
        private void HandleReportDisconnected(NetIncomingMessage message)
        {
            RemotePeer reporter = (message.SenderConnection.Tag as RemotePeer);
            int        disconnectedId;

            try
            {
                disconnectedId = message.ReadInt32();
            }
            catch (Exception e) { throw new ProtocolException("Bad disconnected report", e); }


            RemotePeer disconnected = FindRemotePeerById(disconnectedId);

            if (disconnected == null)
            {
                return; // already disconnected (or bad ID)
            }
            // The network trust model says that we can't tell which client is misbehaving (or has a misbehaving link).
            // If one claims to be disconnected from another, unless we involve other clients (over-complicated), we can't tell who is misbehaving.
            // The network requires all clients are connected. So we just have to pick one to disconnect.
            // Prefer to disconnect the "newer" client. But if they reported first, check whether the older client is actually alive.

            if (reporter.PeerInfo.ConnectionId < disconnectedId) // Older client disconnected from newer client
            {
                owner.Log(disconnected.PeerInfo + " was disconnected by " + reporter.PeerInfo);
                Kick(disconnected);
            }
            else if (reporter.PeerInfo.ConnectionId == disconnectedId) // They disconnected themselves! (oooookay...)
            {
                owner.Log(reporter.PeerInfo + " disconnected from self (weird...)");
                Kick(reporter); // You're the boss...
            }
            else // Newer client disconnected from older client
            {
                owner.Log(disconnected.PeerInfo + " was disconnected by " + reporter.PeerInfo + " and has a chance to dispute.");

                // Inform the older client:
                var msg = NetPeer.CreateMessage();
                msg.Write(P2PServerMessage.YouWereDisconnectedBy);
                msg.Write(reporter.PeerInfo.ConnectionId);
                disconnected.Connection.SendMessage(msg, NetDeliveryMethod.ReliableOrdered, 0);

                // And wait for them to respond:
                AddPendingDisconnect(reporter.PeerInfo.ConnectionId, disconnectedId);
            }
        }
Esempio n. 19
0
        /// <summary>Call this after host migration, after re-issuing adding authorised users from the side-channel</summary>
        internal void SideChannelValidateAndKick()
        {
            if (!owner.GameInfo.SideChannelAuth)
            {
                owner.Log("ERROR: SideChannelValidateAndKick in non-side-channel game");
                return;
            }

            foreach (NetConnection connection in locallyConnected)
            {
                RemotePeer remotePeer = connection.Tag as RemotePeer;
                Debug.Assert(remotePeer != null);
                if (authTokens == null || !authTokens.ContainsKey(remotePeer.PeerInfo.SideChannelId))
                {
                    Kick(remotePeer);
                }
            }
        }
Esempio n. 20
0
        private void HandleReportConnected(NetIncomingMessage message)
        {
            RemotePeer reporter = (message.SenderConnection.Tag as RemotePeer);

            int connectedId;

            try
            {
                connectedId = message.ReadInt32();
            }
            catch (Exception e) { throw new ProtocolException("Bad connected report", e); }

            RemotePeer connected = FindRemotePeerById(connectedId);

            if (connected == null)
            {
                return; // was disconnected (or bad ID)
            }
            owner.Log(reporter.PeerInfo.ToString() + " connected to " + connected.PeerInfo.ToString());
            MarkConnectionMade(reporter.PeerInfo.ConnectionId, connectedId);
        }
Esempio n. 21
0
        private void RemoveFromNetwork(RemotePeer remotePeer)
        {
            ReleaseInputAssignment(remotePeer.PeerInfo.InputAssignment);

            // Clean up pending:
            RemovePendingDisconnectsFor(remotePeer.PeerInfo.ConnectionId);
            RemovePendingConnectionsFor(remotePeer.PeerInfo.ConnectionId);

            if (!remotePeer.PeerInfo.IsApplicationConnected)
            {
                // Wasn't application connected yet (just send the notification)
                var message = NetPeer.CreateMessage();
                message.Write(P2PServerMessage.PeerLeftNetwork);
                message.Write(remotePeer.PeerInfo.ConnectionId);
                NetPeer.SendMessage(message, locallyConnected, NetDeliveryMethod.ReliableOrdered, 0);
            }
            else
            {
                BecomeApplicationDisconnected(remotePeer);
            }

            // Possible that connecting clients no longer need to connect to this one:
            ReadyCheckAll();
        }
Esempio n. 22
0
        private void HandleDisconnection(NetConnection connection, NetIncomingMessage message)
        {
            RemotePeer remotePeer = connection.Tag as RemotePeer;

            if (remotePeer == null)
            {
                Debug.Assert(!locallyConnected.Contains(connection));
                return; // Already dealt with
            }

            Debug.Assert(remotePeer.Connection == connection);
            Debug.Assert(locallyConnected.Contains(remotePeer.Connection));

            string reason = message.ReadString();

            owner.Log("Disconnected: " + remotePeer.PeerInfo + " (supplied reason: " + reason + ")");

            locallyConnected.Remove(remotePeer.Connection);
            remotePeer.WasDisconnected();
            RemoveFromNetwork(remotePeer);


            CheckDisconnectionForHostMigrationInvalidation(reason);
        }
Esempio n. 23
0
        /// <summary>
        /// Update network state, and poll for and handle messages.
        /// </summary>
        public void Update()
        {
            try
            {
                // Kick anyone who was queued up to be kicked
                if (peerManager != null)
                {
                    foreach (var remotePeer in networkDataErrorKickList)
                    {
                        peerManager.KickDueToNetworkDataError(remotePeer);
                    }
                }
                networkDataErrorKickList.Clear();


                NetIncomingMessage message;
                while ((message = netPeer.ReadMessage()) != null)
                {
                    bool recycle = true;

                    LogNetMessage(message);

                    switch (message.MessageType)
                    {
                    // Discovery:
                    case NetIncomingMessageType.DiscoveryResponse:
                        if (Discovery != null)
                        {
                            Discovery.ReceiveDiscoveryResponse(message);
                        }
                        break;

                    // Endpoint requests
                    case NetIncomingMessageType.EndpointRequestResult:
                        if (OnExternalEndpointDiscovered != null)
                        {
                            OnExternalEndpointDiscovered(message.SenderEndPoint);
                        }
                        break;

                    // Application-layer data:
                    case NetIncomingMessageType.Data:
                        if (message.DeliveryMethod == NetDeliveryMethod.ReliableOrdered && message.SequenceChannel == 0)    // ... except network management
                        {
                            if (peerManager != null)
                            {
                                peerManager.HandleMessage(message, ref recycle);
                            }
                        }
                        else
                        {
                            RemotePeer remotePeer = message.SenderConnection.Tag as RemotePeer;
                            if (remotePeer != null)
                            {
                                remotePeer.QueueMessage(message, ref recycle);
                            }
                            else
                            {
                                // Warn if an unknown peer sends us anything (unless it's lidgren being dumb and giving us data for a connection they told us disconnected)
                                Debug.Assert(message.SenderConnection.Status == NetConnectionStatus.Disconnected ||
                                             message.SenderConnection.Status == NetConnectionStatus.Disconnecting);
                            }
                        }
                        break;

                    // All other messages are presumed to be for the peer manager (or garbage):
                    default:
                        if (peerManager != null)
                        {
                            peerManager.HandleMessage(message, ref recycle);
                        }
                        break;
                    }

                    if (recycle)
                    {
                        netPeer.Recycle(message);
                    }
                }

                if (peerManager != null)
                {
                    peerManager.Update();
                }
            }
            catch (NetworkDisconnectionException)
            {
                Debug.Assert(peerManager == null); // <- we did actually get disconnected, right?
            }


            if (Discovery != null)
            {
                Discovery.Update();
            }
        }
Esempio n. 24
0
 void AcceptHostMigration(RemotePeer remotePeer)
 {
     owner.Log("Host migration accepted by: " + remotePeer.PeerInfo);
     ValidateHostMigration();
 }
Esempio n. 25
0
 public void KickDueToNetworkDataError(RemotePeer remotePeer)
 {
     Debug.Assert(remotePeer != null);
     Kick(remotePeer); // buh-bye
 }
Esempio n. 26
0
        private void HandleConnection(NetConnection connection)
        {
            // Read hail message:
            IPEndPoint internalEndPoint;
            ulong      sideChannelId;
            int        sideChannelToken;
            string     requestedName;

            byte[] remotePlayerData = null;
            try
            {
                UInt16 theirAppVersion = connection.RemoteHailMessage.ReadUInt16();
                if (theirAppVersion != owner.appConfig.ApplicationVersion)
                {
                    owner.Log("Disconnected client with wrong application version");
                    connection.Disconnect(DisconnectStrings.BadGameVersion);
                    return;
                }

                int    theirAppSignatureLength = (int)connection.RemoteHailMessage.ReadVariableUInt32();
                byte[] theirAppSignature;
                if (theirAppSignatureLength < 0 || theirAppSignatureLength > NetworkAppConfig.ApplicationSignatureMaximumLength)
                {
                    theirAppSignature = null;
                }
                else
                {
                    theirAppSignature = connection.RemoteHailMessage.ReadBytes(theirAppSignatureLength);
                }

                if (theirAppSignature == null || !owner.appConfig.ApplicationSignature.SequenceEqual(theirAppSignature))
                {
                    owner.Log("Disconnected client with wrong application signature");
                    connection.Disconnect(DisconnectStrings.BadGameVersion);
                    return;
                }


                internalEndPoint = connection.RemoteHailMessage.ReadIPEndPoint();
                sideChannelId    = connection.RemoteHailMessage.ReadUInt64();
                sideChannelToken = connection.RemoteHailMessage.ReadInt32();
                requestedName    = connection.RemoteHailMessage.ReadString();
                remotePlayerData = connection.RemoteHailMessage.ReadByteArray();
            }
            catch
            {
                owner.UnconnectedProtocolError("Bad hail message");
                connection.Disconnect(DisconnectStrings.BadHailMessage);
                return;
            }


            if (owner.GameInfo.SideChannelAuth)
            {
                int expectedToken;
                if (sideChannelToken == 0 || authTokens == null || !authTokens.TryGetValue(sideChannelId, out expectedToken) || expectedToken != sideChannelToken)
                {
                    owner.UnconnectedProtocolError("Bad side-channel authentication");
                    connection.Disconnect(DisconnectStrings.BadSideChannelAuth);
                }
            }


            requestedName = requestedName.FilterNameNoDuplicates(locallyConnected, owner.LocalPeerInfo.PlayerName);


            // Check whether the server is full:
            // (Could possibly handle this as Approve/Deny, but would then have to track slots for pending connections.)
            if (IsFull)
            {
                owner.Log("Rejecting incoming connection (player name: " + requestedName + "): game is full");
                connection.Disconnect(DisconnectStrings.GameFull);
                return;
            }


            // Setup remote peer:
            PeerInfo peerInfo = new PeerInfo()
            {
                ConnectionId           = nextConnectionId++,
                PlayerName             = requestedName,
                PlayerData             = remotePlayerData,
                InputAssignment        = AssignInput(),
                InternalEndPoint       = internalEndPoint,
                ExternalEndPoint       = connection.RemoteEndPoint,
                SideChannelId          = sideChannelId,
                IsApplicationConnected = false,
                IsServer = false,
            };


            RemotePeer remotePeer = new RemotePeer(connection, peerInfo);

            owner.Log("New connection: " + peerInfo.ToString());


            // Setup connection tokens:
            int[] connectionTokens = new int[locallyConnected.Count];
            for (int i = 0; i < connectionTokens.Length; i++)
            {
                connectionTokens[i] = random.Next();
            }


            // Send info to joining peer:
            {
                var message = NetPeer.CreateMessage();
                message.Write(P2PServerMessage.NetworkStartInfo);
                owner.GameInfo.WriteTo(message);
                peerInfo.WriteTo(message, true);      // Send their peer info (except: they already have their own playerData)
                owner.LocalPeerInfo.WriteTo(message); // Send our peer info

                // Write out a list of who to connect to:
                Debug.Assert(locallyConnected.Count < byte.MaxValue);
                message.Write((byte)(locallyConnected.Count));
                for (int i = 0; i < locallyConnected.Count; i++)
                {
                    (locallyConnected[i].Tag as RemotePeer).PeerInfo.WriteTo(message);
                    message.Write(connectionTokens[i]);
                }

                remotePeer.Connection.SendMessage(message, NetDeliveryMethod.ReliableOrdered, 0);
            }


            // Send connect instructions to other peers:
            for (int i = 0; i < locallyConnected.Count; i++)
            {
                var message = NetPeer.CreateMessage();
                message.Write(P2PServerMessage.PeerJoinedNetwork);
                peerInfo.WriteTo(message);
                message.Write(connectionTokens[i]);

                locallyConnected[i].SendMessage(message, NetDeliveryMethod.ReliableOrdered, 0);
            }


            // Add new connection to the local connection list:
            locallyConnected.Add(connection);

            // Start waiting for this new client to connect to the full network:
            StartPendingConnection(remotePeer);

            // Although the first connection won't need to wait:
            if (locallyConnected.Count == 1)
            {
                ReadyCheckAll();
            }
        }