internal bool VerifyIdentifiers(
            IncomingNetMessage message,
            NetworkEndPoint endPoint,
            out ushort number
            )
        {
            number = 0;
            int payLen = message.m_data.LengthBytes;

            if (payLen < 13)
            {
                if ((m_netBase.m_enabledMessageTypes & NetMessageType.BadMessageReceived) == NetMessageType.BadMessageReceived)
                {
                    m_netBase.NotifyApplication(NetMessageType.BadMessageReceived, "Malformed Discovery message received from " + endPoint, null, message.m_senderEndPoint);
                }
                return(false);
            }

            // check app identifier
            string appIdent2 = message.m_data.ReadString();

            if (appIdent2 != m_netBase.m_config.ApplicationIdentifier)
            {
                if ((m_netBase.m_enabledMessageTypes & NetMessageType.BadMessageReceived) == NetMessageType.BadMessageReceived)
                {
                    m_netBase.NotifyApplication(NetMessageType.BadMessageReceived, "Discovery for different application identification received: " + appIdent2, null, message.m_senderEndPoint);
                }
                return(false);
            }

            // check netbase identifier
            byte[] nbid = message.m_data.ReadBytes(m_netBase.m_localRndSignature.Length);
            if (NetUtility.CompareElements(nbid, m_netBase.m_localRndSignature))
            {
                return(false);                // don't respond to your own discovery request
            }
            // retrieve number
            number = message.m_data.ReadUInt16();

            // it's ok
            return(true);
        }
        /*
         * internal void HandleUserMessage(NetMessage msg)
         * {
         *      int seqNr = msg.m_sequenceNumber;
         *      int chanNr = (int)msg.m_sequenceChannel;
         *      bool isDuplicate = false;
         *
         *      int relation = RelateToExpected(seqNr, chanNr, out isDuplicate);
         *
         *      //
         *      // Unreliable
         *      //
         *      if (msg.m_sequenceChannel == NetChannel.Unreliable)
         *      {
         *              // It's all good; add message
         *              if (isDuplicate)
         *              {
         *                      m_statistics.CountDuplicateMessage(msg);
         *                      m_owner.LogVerbose("Rejecting duplicate " + msg, this);
         *              }
         *              else
         *              {
         *                      AcceptMessage(msg);
         *              }
         *              return;
         *      }
         *
         *      //
         *      // Reliable unordered
         *      //
         *      if (msg.m_sequenceChannel == NetChannel.ReliableUnordered)
         *      {
         *              // send acknowledge (even if duplicate)
         *              m_acknowledgesToSend.Enqueue((chanNr << 16) | msg.m_sequenceNumber);
         *
         *              if (isDuplicate)
         *              {
         *                      m_statistics.CountDuplicateMessage(msg);
         *                      m_owner.LogVerbose("Rejecting duplicate " + msg, this);
         *                      return; // reject duplicates
         *              }
         *
         *              // It's good; add message
         *              AcceptMessage(msg);
         *
         *              return;
         *      }
         *
         *      ushort nextSeq = (ushort)(seqNr + 1);
         *
         *      if (chanNr < (int)NetChannel.ReliableInOrder1)
         *      {
         *              //
         *              // Sequenced
         *              //
         *              if (relation < 0)
         *              {
         *                      // late sequenced message
         *                      m_statistics.CountDroppedSequencedMessage();
         *                      m_owner.LogVerbose("Dropping late sequenced " + msg, this);
         *                      return;
         *              }
         *
         *              // It's good; add message
         *              AcceptMessage(msg);
         *
         *              m_nextExpectedSequence[chanNr] = nextSeq;
         *              return;
         *      }
         *      else
         *      {
         *              //
         *              // Ordered
         *              //
         *
         *              // send ack (regardless)
         *              m_acknowledgesToSend.Enqueue((chanNr << 16) | msg.m_sequenceNumber);
         *
         *              if (relation < 0)
         *              {
         *                      // late ordered message
         #if DEBUG
         *                      if (!isDuplicate)
         *                              m_owner.LogWrite("Ouch, weird! Late ordered message that's NOT a duplicate?! seqNr: " + seqNr + " expecting: " + m_nextExpectedSequence[chanNr], this);
         #endif
         *                      // must be duplicate
         *                      m_owner.LogVerbose("Dropping duplicate message " + seqNr, this);
         *                      m_statistics.CountDuplicateMessage(msg);
         *                      return; // rejected; don't advance next expected
         *              }
         *
         *              if (relation > 0)
         *              {
         *                      // early message; withhold ordered
         *                      m_owner.LogVerbose("Withholding " + msg + " (expecting " + m_nextExpectedSequence[chanNr] + ")", this);
         *                      m_withheldMessages.Add(msg);
         *                      return; // return without advancing next expected
         *              }
         *
         *              // It's right on time!
         *              AcceptMessage(msg);
         *
         *              // ordered; release other withheld messages?
         *              bool released = false;
         *              do
         *              {
         *                      released = false;
         *                      foreach (NetMessage wm in m_withheldMessages)
         *                      {
         *                              if ((int)wm.m_sequenceChannel == chanNr && wm.m_sequenceNumber == nextSeq)
         *                              {
         *                                      m_owner.LogVerbose("Releasing withheld message " + wm, this);
         *                                      m_withheldMessages.Remove(wm);
         *                                      AcceptMessage(wm);
         *                                      // no need to set rounds for this message; it was one when first related() and withheld
         *                                      nextSeq++;
         *                                      if (nextSeq >= NetConstants.NumSequenceNumbers)
         *                                              nextSeq -= NetConstants.NumSequenceNumbers;
         *                                      released = true;
         *                                      break;
         *                              }
         *                      }
         *              } while (released);
         *      }
         *
         *      // Common to Sequenced and Ordered
         *
         *      //m_owner.LogVerbose("Setting next expected for " + (NetChannel)chanNr + " to " + nextSeq);
         *      m_nextExpectedSequence[chanNr] = nextSeq;
         *
         *      return;
         * }
         */

        internal void HandleSystemMessage(IncomingNetMessage msg, double now)
        {
            msg.m_data.Position = 0;
            NetSystemType      sysType  = (NetSystemType)msg.m_data.ReadByte();
            OutgoingNetMessage response = null;

            switch (sysType)
            {
            case NetSystemType.Disconnect:
                if (m_status == NetConnectionStatus.Disconnected)
                {
                    return;
                }
                Disconnect(msg.m_data.ReadString(), 0.75f + ((float)m_currentAvgRoundtrip * 4), false, false);
                break;

            case NetSystemType.ConnectionRejected:
                string reason = msg.m_data.ReadString();
                m_owner.NotifyApplication(NetMessageType.ConnectionRejected, reason, msg.m_sender, msg.m_senderEndPoint);
                Disconnect(reason, 0.0f, false, true);
                break;

            case NetSystemType.Connect:

                // ConnectReponse must have been losts

                string appIdent = msg.m_data.ReadString();
                if (appIdent != m_owner.m_config.ApplicationIdentifier)
                {
                    if ((m_owner.m_enabledMessageTypes & NetMessageType.BadMessageReceived) == NetMessageType.BadMessageReceived)
                    {
                        m_owner.NotifyApplication(NetMessageType.BadMessageReceived, "Connect for different application identification received: " + appIdent, null, msg.m_senderEndPoint);
                    }
                    return;
                }

                // read random identifer
                byte[] rnd = msg.m_data.ReadBytes(8);
                if (NetUtility.CompareElements(rnd, m_owner.m_randomIdentifier))
                {
                    // don't allow self-connect
                    if ((m_owner.m_enabledMessageTypes & NetMessageType.ConnectionRejected) == NetMessageType.ConnectionRejected)
                    {
                        m_owner.NotifyApplication(NetMessageType.ConnectionRejected, "Connection to self not allowed", null, msg.m_senderEndPoint);
                    }
                    return;
                }

                // read hail data
                m_remoteHailData = null;
                int hailBytesCount = (msg.m_data.LengthBits - msg.m_data.Position) / 8;
                if (hailBytesCount > 0)
                {
                    m_remoteHailData = msg.m_data.ReadBytes(hailBytesCount);
                }

                // finalize disconnect if it's in process
                if (m_status == NetConnectionStatus.Disconnecting)
                {
                    FinalizeDisconnect();
                }

                // send response; even if connected
                response = m_owner.CreateSystemMessage(NetSystemType.ConnectResponse);
                if (m_localHailData != null)
                {
                    response.m_data.Write(m_localHailData);
                }
                m_unsentMessages.Enqueue(response);

                break;

            case NetSystemType.ConnectResponse:
                if (m_status != NetConnectionStatus.Connecting && m_status != NetConnectionStatus.Connected)
                {
                    m_owner.LogWrite("Received connection response but we're not connecting...", this);
                    return;
                }

                // read hail data
                m_remoteHailData = null;
                int numHailBytes = (msg.m_data.LengthBits - msg.m_data.Position) / 8;
                if (numHailBytes > 0)
                {
                    m_remoteHailData = msg.m_data.ReadBytes(numHailBytes);
                }

                // Send connectionestablished
                response = m_owner.CreateSystemMessage(NetSystemType.ConnectionEstablished);
                if (m_localHailData != null)
                {
                    response.m_data.Write(m_localHailData);
                }
                m_unsentMessages.Enqueue(response);

                // send first ping 250ms after connected
                m_lastSentPing = now - m_owner.Configuration.PingFrequency + 0.1 + (NetRandom.Instance.NextFloat() * 0.25f);
                m_statistics.Reset();
                SetInitialAveragePing(now - m_handshakeInitiated);
                SetStatus(NetConnectionStatus.Connected, "Connected");
                break;

            case NetSystemType.ConnectionEstablished:
                if (m_status != NetConnectionStatus.Connecting)
                {
                    if ((m_owner.m_enabledMessageTypes & NetMessageType.BadMessageReceived) == NetMessageType.BadMessageReceived)
                    {
                        m_owner.NotifyApplication(NetMessageType.BadMessageReceived, "Received connection response but we're not connecting...", this, msg.m_senderEndPoint);
                    }
                    return;
                }

                // read hail data
                if (m_remoteHailData == null)
                {
                    int hbc = (msg.m_data.LengthBits - msg.m_data.Position) / 8;
                    if (hbc > 0)
                    {
                        m_remoteHailData = msg.m_data.ReadBytes(hbc);
                    }
                }

                // send first ping 100-350ms after connected
                m_lastSentPing = now - m_owner.Configuration.PingFrequency + 0.1 + (NetRandom.Instance.NextFloat() * 0.25f);
                m_statistics.Reset();
                SetInitialAveragePing(now - m_handshakeInitiated);
                SetStatus(NetConnectionStatus.Connected, "Connected");
                break;

            case NetSystemType.Ping:
                // also accepted as ConnectionEstablished
                if (m_isInitiator == false && m_status == NetConnectionStatus.Connecting)
                {
                    m_owner.LogWrite("Received ping; interpreted as ConnectionEstablished", this);
                    m_statistics.Reset();
                    SetInitialAveragePing(now - m_handshakeInitiated);
                    SetStatus(NetConnectionStatus.Connected, "Connected");
                }

                //LogWrite("Received ping; sending pong...");
                SendPong(m_owner, m_remoteEndPoint, now);
                break;

            case NetSystemType.Pong:
                double twoWayLatency = now - m_lastSentPing;
                if (twoWayLatency < 0)
                {
                    break;
                }
                ReceivedPong(twoWayLatency, msg);
                break;

            case NetSystemType.StringTableAck:
                ushort val = msg.m_data.ReadUInt16();
                StringTableAcknowledgeReceived(val);
                break;

            default:
                m_owner.LogWrite("Undefined behaviour in NetConnection for system message " + sysType, this);
                break;
            }
        }
        /*
         * internal void HandleUserMessage(NetMessage msg)
         * {
         *      int seqNr = msg.m_sequenceNumber;
         *      int chanNr = (int)msg.m_sequenceChannel;
         *      bool isDuplicate = false;
         *
         *      int relation = RelateToExpected(seqNr, chanNr, out isDuplicate);
         *
         *      //
         *      // Unreliable
         *      //
         *      if (msg.m_sequenceChannel == NetChannel.Unreliable)
         *      {
         *              // It's all good; add message
         *              if (isDuplicate)
         *              {
         *                      m_statistics.CountDuplicateMessage(msg);
         *                      m_owner.LogVerbose("Rejecting duplicate " + msg, this);
         *              }
         *              else
         *              {
         *                      AcceptMessage(msg);
         *              }
         *              return;
         *      }
         *
         *      //
         *      // Reliable unordered
         *      //
         *      if (msg.m_sequenceChannel == NetChannel.ReliableUnordered)
         *      {
         *              // send acknowledge (even if duplicate)
         *              m_acknowledgesToSend.Enqueue((chanNr << 16) | msg.m_sequenceNumber);
         *
         *              if (isDuplicate)
         *              {
         *                      m_statistics.CountDuplicateMessage(msg);
         *                      m_owner.LogVerbose("Rejecting duplicate " + msg, this);
         *                      return; // reject duplicates
         *              }
         *
         *              // It's good; add message
         *              AcceptMessage(msg);
         *
         *              return;
         *      }
         *
         *      ushort nextSeq = (ushort)(seqNr + 1);
         *
         *      if (chanNr < (int)NetChannel.ReliableInOrder1)
         *      {
         *              //
         *              // Sequenced
         *              //
         *              if (relation < 0)
         *              {
         *                      // late sequenced message
         *                      m_statistics.CountDroppedSequencedMessage();
         *                      m_owner.LogVerbose("Dropping late sequenced " + msg, this);
         *                      return;
         *              }
         *
         *              // It's good; add message
         *              AcceptMessage(msg);
         *
         *              m_nextExpectedSequence[chanNr] = nextSeq;
         *              return;
         *      }
         *      else
         *      {
         *              //
         *              // Ordered
         *              //
         *
         *              // send ack (regardless)
         *              m_acknowledgesToSend.Enqueue((chanNr << 16) | msg.m_sequenceNumber);
         *
         *              if (relation < 0)
         *              {
         *                      // late ordered message
         #if DEBUG
         *                      if (!isDuplicate)
         *                              m_owner.LogWrite("Ouch, weird! Late ordered message that's NOT a duplicate?! seqNr: " + seqNr + " expecting: " + m_nextExpectedSequence[chanNr], this);
         #endif
         *                      // must be duplicate
         *                      m_owner.LogVerbose("Dropping duplicate message " + seqNr, this);
         *                      m_statistics.CountDuplicateMessage(msg);
         *                      return; // rejected; don't advance next expected
         *              }
         *
         *              if (relation > 0)
         *              {
         *                      // early message; withhold ordered
         *                      m_owner.LogVerbose("Withholding " + msg + " (expecting " + m_nextExpectedSequence[chanNr] + ")", this);
         *                      m_withheldMessages.Add(msg);
         *                      return; // return without advancing next expected
         *              }
         *
         *              // It's right on time!
         *              AcceptMessage(msg);
         *
         *              // ordered; release other withheld messages?
         *              bool released = false;
         *              do
         *              {
         *                      released = false;
         *                      foreach (NetMessage wm in m_withheldMessages)
         *                      {
         *                              if ((int)wm.m_sequenceChannel == chanNr && wm.m_sequenceNumber == nextSeq)
         *                              {
         *                                      m_owner.LogVerbose("Releasing withheld message " + wm, this);
         *                                      m_withheldMessages.Remove(wm);
         *                                      AcceptMessage(wm);
         *                                      // no need to set rounds for this message; it was one when first related() and withheld
         *                                      nextSeq++;
         *                                      if (nextSeq >= NetConstants.NumSequenceNumbers)
         *                                              nextSeq -= NetConstants.NumSequenceNumbers;
         *                                      released = true;
         *                                      break;
         *                              }
         *                      }
         *              } while (released);
         *      }
         *
         *      // Common to Sequenced and Ordered
         *
         *      //m_owner.LogVerbose("Setting next expected for " + (NetChannel)chanNr + " to " + nextSeq);
         *      m_nextExpectedSequence[chanNr] = nextSeq;
         *
         *      return;
         * }
         */

        internal void HandleSystemMessage(IncomingNetMessage msg, double timestamp)
        {
            msg.m_data.PositionBits = 0;
            NetSystemType sysType = (NetSystemType)msg.m_data.ReadByte();

            switch (sysType)
            {
            case NetSystemType.Disconnect:
                if (m_status == NetConnectionStatus.Disconnected)
                {
                    return;
                }
                Disconnect(msg.m_data.ReadString(), NetConstants.DisconnectLingerTime, false, false);
                break;

            case NetSystemType.ConnectionRejected:
                string reason = msg.m_data.ReadString();
                m_owner.NotifyApplication(NetMessageType.ConnectionRejected, reason, msg.m_sender, msg.m_senderEndPoint);
                Disconnect(reason, 0.0f, false, true);
                break;

            case NetSystemType.Connect:
            {
                // ConnectReponse must have been losts

                /*if (m_isInitiator) // this might be useful in the future
                 * {
                 *      m_owner.LogWrite("Received connect but we're the initiator...", this);
                 *      return; // ignore
                 * }*/

                if (Status != NetConnectionStatus.Connecting && Status != NetConnectionStatus.Connected)
                {
                    m_owner.LogWrite("Received connect but we're not connecting or connected...", this);
                    return;                                     // ignore if disconnecting
                }

                string appIdent = msg.m_data.ReadString();
                if (appIdent != m_owner.m_config.ApplicationIdentifier)
                {
                    if ((m_owner.m_enabledMessageTypes & NetMessageType.BadMessageReceived) == NetMessageType.BadMessageReceived)
                    {
                        m_owner.NotifyApplication(NetMessageType.BadMessageReceived,
                                                  "Connect for different application identification received: " + appIdent, null,
                                                  msg.m_senderEndPoint);
                    }
                    return;
                }

                // read random identifier
                var remoteRndSignature = msg.m_data.ReadBytes(NetConstants.SignatureByteSize);
                if (!m_owner.Configuration.AllowConnectionToSelf && NetUtility.CompareElements(remoteRndSignature, m_owner.m_localRndSignature))
                {
                    // don't allow self-connect
                    if ((m_owner.m_enabledMessageTypes & NetMessageType.ConnectionRejected) == NetMessageType.ConnectionRejected)
                    {
                        m_owner.NotifyApplication(NetMessageType.ConnectionRejected, "Connection to self not allowed", null,
                                                  msg.m_senderEndPoint);
                    }
                    return;
                }

                int remoteRndSeqNr = msg.m_data.ReadInt32();

                if (m_remoteRndSignature != null)
                {
                    if (!NetUtility.CompareElements(remoteRndSignature, m_remoteRndSignature) || remoteRndSeqNr != m_remoteRndSeqNr)
                    {
                        // this is not the same connection.
                        Disconnect("Not the same connection", 0, false, true);
                        return;
                    }
                }
                else                                 // this might happen if both try and connect at the same time.
                {
                    m_remoteRndSignature = remoteRndSignature;
                    m_remoteRndSeqNr     = remoteRndSeqNr;
                    ResetReliability();
                }

                m_connectLocalSentTime  = msg.m_data.ReadDouble();
                m_connectRemoteRecvTime = timestamp + m_owner.m_localTimeOffset;

                // read hail data
                m_remoteHailData = null;
                int hailBytesCount = (msg.m_data.LengthBits - msg.m_data.PositionBits) / 8;
                if (hailBytesCount > 0)
                {
                    m_remoteHailData = msg.m_data.ReadBytes(hailBytesCount);
                }

                // send response; even if connected
                var responseBuffer = m_owner.GetTempBuffer();
                responseBuffer.Write(m_owner.m_localRndSignature);
                responseBuffer.Write(m_localRndSeqNr);
                responseBuffer.Write(m_remoteRndSignature);
                responseBuffer.Write(m_remoteRndSeqNr);

                double localTimeSent = m_connectLocalSentTime;
                responseBuffer.Write(localTimeSent);
                double remoteTimeRecv = m_connectRemoteRecvTime;
                responseBuffer.Write(remoteTimeRecv);
                double remoteTimeSent = NetTime.Now + m_owner.m_localTimeOffset;
                responseBuffer.Write(remoteTimeSent);

                if (m_localHailData != null)
                {
                    responseBuffer.Write(m_localHailData);
                }

                m_handshakeInitiated = remoteTimeSent;
                SendSingleUnreliableSystemMessage(NetSystemType.ConnectResponse, responseBuffer);
                break;
            }

            case NetSystemType.ConnectResponse:
            {
                /*if (m_isInitiator) // this might be useful in the future
                 * {
                 *      m_owner.LogWrite("Received connect but we're the initiator...", this);
                 *      return; // ignore
                 * }*/

                if (m_status != NetConnectionStatus.Connecting && m_status != NetConnectionStatus.Connected)
                {
                    m_owner.LogWrite("Received connection response but we're not connecting or connected...", this);
                    return;
                }

                var remoteRndSignature = msg.m_data.ReadBytes(NetConstants.SignatureByteSize);
                if (!m_owner.Configuration.AllowConnectionToSelf && NetUtility.CompareElements(remoteRndSignature, m_owner.m_localRndSignature))
                {
                    // don't allow self-connect
                    if ((m_owner.m_enabledMessageTypes & NetMessageType.ConnectionRejected) == NetMessageType.ConnectionRejected)
                    {
                        m_owner.NotifyApplication(NetMessageType.ConnectionRejected, "Connection to self not allowed", null,
                                                  msg.m_senderEndPoint);
                    }
                    return;
                }

                int remoteRndSeqNr = msg.m_data.ReadInt32();

                if (m_remoteRndSignature != null)
                {
                    if (!NetUtility.CompareElements(remoteRndSignature, m_remoteRndSignature) || remoteRndSeqNr != m_remoteRndSeqNr)
                    {
                        // this is not the same connection.
                        Disconnect("Not the same connection", 0, false, true);
                        return;
                    }
                }
                else                                 // this might happen if both try and connect at the same time.
                {
                    m_remoteRndSignature = remoteRndSignature;
                    m_remoteRndSeqNr     = remoteRndSeqNr;
                    ResetReliability();
                }

                var myRndSignature = msg.m_data.ReadBytes(NetConstants.SignatureByteSize);
                var myRndSeqNr     = msg.m_data.ReadInt32();

                if (!NetUtility.CompareElements(myRndSignature, m_owner.m_localRndSignature) || myRndSeqNr != m_localRndSeqNr)
                {
                    // this is not the same connection.
                    Disconnect("Not the same connection", 0, false, true);
                    return;
                }

                double localTimeSent  = msg.m_data.ReadDouble();
                double remoteTimeRecv = msg.m_data.ReadDouble();
                double remoteTimeSent = msg.m_data.ReadDouble();
                double localTimeRecv  = timestamp + m_owner.m_localTimeOffset;

                // read hail data
                m_remoteHailData = null;
                int numHailBytes = (msg.m_data.LengthBits - msg.m_data.PositionBits) / 8;
                if (numHailBytes > 0)
                {
                    m_remoteHailData = msg.m_data.ReadBytes(numHailBytes);
                }

                // Send connection established
                var responseBuffer = m_owner.GetTempBuffer();
                responseBuffer.Write(m_owner.m_localRndSignature);
                responseBuffer.Write(m_localRndSeqNr);
                responseBuffer.Write(m_remoteRndSignature);
                responseBuffer.Write(m_remoteRndSeqNr);

                double intiatorLocalTimeSent = remoteTimeSent;
                responseBuffer.Write(intiatorLocalTimeSent);
                double intiatorRemoteTimeRecv = localTimeRecv;
                responseBuffer.Write(intiatorRemoteTimeRecv);
                double intiatorRemoteTimeSent = NetTime.Now + m_owner.m_localTimeOffset;
                responseBuffer.Write(intiatorRemoteTimeSent);

                if (m_localHailData != null)
                {
                    responseBuffer.Write(m_localHailData);
                }

                SendSingleUnreliableSystemMessage(NetSystemType.ConnectionEstablished, responseBuffer);

                // send first ping 250ms after connected
                m_lastSentPing = timestamp - m_owner.Configuration.PingFrequency + 0.1 + (NetRandom.Instance.NextFloat() * 0.25f);
                m_statistics.Reset();
                SetInitialPongEntry(new PongEntry(localTimeSent, remoteTimeRecv, remoteTimeSent, localTimeRecv));
                SetStatus(NetConnectionStatus.Connected, "Connected");
                break;
            }

            case NetSystemType.ConnectionEstablished:
            {
                if (m_status != NetConnectionStatus.Connecting)
                {
                    if ((m_owner.m_enabledMessageTypes & NetMessageType.BadMessageReceived) == NetMessageType.BadMessageReceived)
                    {
                        m_owner.NotifyApplication(NetMessageType.BadMessageReceived,
                                                  "Received connection response but we're not connecting...", this, msg.m_senderEndPoint);
                    }
                    return;
                }

                var remoteRndSignature = msg.m_data.ReadBytes(NetConstants.SignatureByteSize);
                if (!m_owner.Configuration.AllowConnectionToSelf && NetUtility.CompareElements(remoteRndSignature, m_owner.m_localRndSignature))
                {
                    // don't allow self-connect
                    if ((m_owner.m_enabledMessageTypes & NetMessageType.ConnectionRejected) == NetMessageType.ConnectionRejected)
                    {
                        m_owner.NotifyApplication(NetMessageType.ConnectionRejected, "Connection to self not allowed", null,
                                                  msg.m_senderEndPoint);
                    }
                    return;
                }

                int remoteRndSeqNr = msg.m_data.ReadInt32();

                if (m_remoteRndSignature != null)
                {
                    if (!NetUtility.CompareElements(remoteRndSignature, m_remoteRndSignature) || remoteRndSeqNr != m_remoteRndSeqNr)
                    {
                        // this is not the same connection.
                        Disconnect("Not the same connection", 0, false, true);
                        return;
                    }
                }
                else                                 // this might happen if both try and connect at the same time.
                {
                    m_remoteRndSignature = remoteRndSignature;
                    m_remoteRndSeqNr     = remoteRndSeqNr;
                    ResetReliability();
                }

                var myRndSignature = msg.m_data.ReadBytes(NetConstants.SignatureByteSize);
                var myRndSeqNr     = msg.m_data.ReadInt32();

                if (!NetUtility.CompareElements(myRndSignature, m_owner.m_localRndSignature) || myRndSeqNr != m_localRndSeqNr)
                {
                    // this is not the same connection.
                    Disconnect("Not the same connection", 0, false, true);
                    return;
                }

                double localTimeSent  = msg.m_data.ReadDouble();
                double remoteTimeRecv = msg.m_data.ReadDouble();
                double remoteTimeSent = msg.m_data.ReadDouble();
                double localTimeRecv  = timestamp + m_owner.m_localTimeOffset;

                // read hail data
                if (m_remoteHailData == null)
                {
                    int hbc = (msg.m_data.LengthBits - msg.m_data.PositionBits) / 8;
                    if (hbc > 0)
                    {
                        m_remoteHailData = msg.m_data.ReadBytes(hbc);
                    }
                }

                // send first ping 100-350ms after connected
                m_lastSentPing = timestamp - m_owner.Configuration.PingFrequency + 0.1 + (NetRandom.Instance.NextFloat() * 0.25f);
                m_statistics.Reset();
                SetInitialPongEntry(new PongEntry(localTimeSent, remoteTimeRecv, remoteTimeSent, localTimeRecv));
                SetStatus(NetConnectionStatus.Connected, "Connected");
                break;
            }

            case NetSystemType.Ping:
                // also accepted as ConnectionEstablished
                if (m_isInitiator == false && m_status == NetConnectionStatus.Connecting)
                {
                    m_owner.LogWrite("Received ping; interpreted as ConnectionEstablished", this);
                    m_statistics.Reset();
                    SetInitialPongEntryApprox(timestamp - m_handshakeInitiated);
                    SetStatus(NetConnectionStatus.Connected, "Connected");
                }

                if (m_status != NetConnectionStatus.Connected)
                {
                    m_owner.LogWrite("Received ping but we're not connected...", this);
                    if (m_status == NetConnectionStatus.Disconnected)
                    {
                        // this might cause a issue (especially if both are trying to connect at the same time).
                        Disconnect("We're not connected", 0, true, true);
                    }
                    return;
                }

                ReceivedPing(msg, timestamp + m_owner.m_localTimeOffset);
                break;

            case NetSystemType.Pong:
                ReceivedPong(msg, timestamp + m_owner.m_localTimeOffset);
                break;

            case NetSystemType.StringTableAck:
                ushort val = msg.m_data.ReadUInt16();
                StringTableAcknowledgeReceived(val);
                break;

            default:
                m_owner.LogWrite("Undefined behavior in NetConnection for system message " + sysType, this);
                break;
            }
        }