void AddClockSyncData(PeerInfo peerInfo, ClockSyncData newData)
        {
            peerInfo.ClockSyncDataByAge.Enqueue(newData);

            int newDataIndex = 0;

            // Sort by RTT is using BestRTT sampling rule.
            // Otherwise, just "sort" by recency by inserting at index = 0.
            if (ClockSyncSamplingRule == ClockSyncSamplingRule.BestRTTs)
            {
                // Add the new clock sync data into the RTT-sorted list.
                // We keep a persistent list instead of sorting on the fly, since that would be slow.
                for (newDataIndex = 0; newDataIndex < peerInfo.ClockSyncDataByImportance.Count; newDataIndex++)
                {
                    if (peerInfo.ClockSyncDataByImportance[newDataIndex].RTT >= newData.RTT)
                    {
                        break;
                    }
                }
            }

            peerInfo.ClockSyncDataByImportance.Insert(newDataIndex, newData);

            // Limit clock sync history.
            while (peerInfo.ClockSyncDataByAge.Count > _clockSyncHistorySize)
            {
                // Here is an awesome way to remove from the queue and the RTT-sorted list at the same time.
                peerInfo.ClockSyncDataByImportance.Remove(peerInfo.ClockSyncDataByAge.Dequeue());
            }

            if (PeerUpdated != null)
            {
                PeerUpdated(peerInfo.Index, newData.RTT, (int)(peerInfo.BytesToTLS * _pingRate * 0.008), (int)(peerInfo.BytesFromTLS * _pingRate * 0.008));
            }
            peerInfo.BytesToTLS   = 0;
            peerInfo.BytesFromTLS = 0;
        }
        /// <summary>
        /// Process a message from one of the peers
        /// </summary>
        /// <param name="peerIndex">Index number for the peer.</param>
        /// <param name="message">The message to be processed.</param>
        public void ProcessIncomingMessage(ushort peerIndex, TimelineMessage message)
        {
            lock (_peerLock)
            {
                lock (_timelineLock)
                {
                    PeerInfo peerInfo = null;

                    if (!_peersByIndex.TryGetValue(peerIndex, out peerInfo))
                    {
                        return;
                    }

                    peerInfo.BytesToTLS += message.Data.Length;

                    // Be sure to arrange the message types in descending order of frequency.
                    // For example, sets are most frequent, so they are checked first.

                    if (message.MessageType == TimelineMessageType.RelayAbsolute)
                    {
                        BinaryReader reader = new BinaryReader(new MemoryStream(message.Data));
                        // We only need to read the timeline index, since we are only relaying, not receiving.
                        ushort timelineIndex = reader.ReadUInt16();
                        reader.Close();

                        // Get the timeline this message was intended for.
                        TimelineInfo timelineInfo = null;

                        if (!peerInfo.ConnectedTimelines.TryGetValue(timelineIndex, out timelineInfo))
                        {
                            return;
                        }

                        // Add message to cache queue
                        timelineInfo.CachedEntries.Add(message);

                        if (TimelineSet != null)
                        {
                            TimelineSet(timelineIndex, timelineInfo.ID);
                        }

                        while (timelineInfo.CachedEntries.Count > timelineInfo.EntryCacheSize)
                        {
                            timelineInfo.CachedEntries.RemoveAt(0);
                        }

                        // Loop through all the subscribers except for the sender...
                        foreach (var otherPeer in timelineInfo.ConnectedPeers)
                        {
                            if (peerInfo == otherPeer.Key)
                            {
                                continue;
                            }

                            // Create a new message to relay.
                            TimelineMessage relayMessage = CreateRelayMessageFromTemplate(message, otherPeer.Value);
                            otherPeer.Key.OutgoingMessages.Enqueue(relayMessage);
                        }
                    }
                    else if (message.MessageType == TimelineMessageType.ClockSyncPong)
                    {
                        BinaryReader reader         = new BinaryReader(new MemoryStream(message.Data));
                        double       localPingTime  = reader.ReadDouble();
                        double       remotePongTime = reader.ReadDouble();
                        double       localPongTime  = (localPingTime + _now) / 2;
                        reader.Close();

                        ClockSyncData newData = new ClockSyncData()
                        {
                            RTT    = (float)(_now - localPingTime),
                            Offset = (float)(localPongTime - remotePongTime)
                        };

                        AddClockSyncData(peerInfo, newData);
                        float correction = GetClockSyncCorrection(peerInfo);

                        TimelineMessage correctionMessage = new TimelineMessage(TimelineMessageType.ClockSyncCorrection,
                                                                                BitConverter.GetBytes(correction), DeliveryMode.Unreliable);

                        peerInfo.OutgoingMessages.Enqueue(correctionMessage);
                        peerInfo.Pinging = false;
                    }
                    else if (message.MessageType == TimelineMessageType.ConnectTimeline)
                    {
                        // Message format:
                        // 2 bytes (ushort) remote timeline index
                        // byte array (byte[]) timeline id

                        BinaryReader reader = new BinaryReader(new MemoryStream(message.Data));
                        ushort       remoteTimelineIndex = reader.ReadUInt16();
                        byte[]       timelineId          = reader.ReadBytes(message.Data.Length - sizeof(ushort));
                        reader.Close();

                        ConnectPeerToTimeline(peerInfo, remoteTimelineIndex, timelineId);
                    }
                    else if (message.MessageType == TimelineMessageType.DisconnectTimeline)
                    {
                        // Call disconnect method
                        BinaryReader reader = new BinaryReader(new MemoryStream(message.Data));
                        ushort       remoteTimelineIndex = reader.ReadUInt16();
                        reader.Close();

                        TimelineInfo timelineInfo;

                        if (peerInfo.ConnectedTimelines.TryGetValue(remoteTimelineIndex, out timelineInfo))
                        {
                            DisconnectPeerFromTimeline(peerInfo, timelineInfo);
                        }
                    }
                    else if (message.MessageType == TimelineMessageType.CacheSize)
                    {
                        BinaryReader reader = new BinaryReader(new MemoryStream(message.Data));
                        ushort       remoteTimelineIndex = reader.ReadUInt16();
                        ushort       cacheSize           = reader.ReadUInt16();
                        reader.Close();

                        TimelineInfo timelineInfo;

                        if (peerInfo.ConnectedTimelines.TryGetValue(remoteTimelineIndex, out timelineInfo))
                        {
                            timelineInfo.EntryCacheSize = cacheSize;

                            while (timelineInfo.CachedEntries.Count > cacheSize)
                            {
                                timelineInfo.CachedEntries.RemoveAt(0);
                            }
                        }
                    }
                }
            }
        }