context: #region LICENSE
  
  // The contents of this file are subject to the Common Public Attribution
  // License Version 1.0. (the "License"); you may not use this file except in
  // compliance with the License. You may obtain a copy of the License at
  // https://github.com/NiclasOlofsson/MiNET/blob/master/LICENSE.
  // The License is based on the Mozilla Public License Version 1.1, but Sections 14
  // and 15 have been added to cover use of software over a computer network and
  // provide for limited attribution for the Original Developer. In addition, Exhibit A has
  // been modified to be consistent with Exhibit B.
  // 
  // Software distributed under the License is distributed on an "AS IS" basis,
  // WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
  // the specific language governing rights and limitations under the License.
  // 
  // The Original Code is MiNET.
  // 
  // The Original Developer is the Initial Developer.  The Initial Developer of
  // the Original Code is Niclas Olofsson.
  // 
  // All portions of the code written by Niclas Olofsson are Copyright (c) 2014-2020 Niclas Olofsson.
  // All Rights Reserved.
  
  #endregion
  
  using System;
  using System.Collections.Concurrent;
  using System.Collections.Generic;
  using System.Diagnostics;
  using System.Diagnostics.CodeAnalysis;
  using System.IO;
  using System.Net;
  using System.Text.RegularExpressions;
  using System.Threading;
  using System.Threading.Tasks;
  using MiNET;
  using MiNET.Net;
  using MiNET.Net.RakNet;
  using MiNET.Utils;
  using MiNET.Utils.Collections;
  using MiNET.Utils.IO;
  using MiNET.Utils.Nbt;
  using Newtonsoft.Json;
  using Newtonsoft.Json.Converters;
  using NLog;
  using ConnectionInfo = Alex.Common.Utils.ConnectionInfo;
  using LogManager = NLog.LogManager;
  
  namespace Alex.Networking.Bedrock.RakNet
  {
  	public class RaknetSession : INetworkHandler
  	{
  		private static readonly ILogger Log = LogManager.GetCurrentClassLogger();
  
  		private readonly RaknetConnection _connection;
  
  		private long _lastOrderingIndex   = -1; // That's the first message with wrapper
  
  
  		private readonly ConcurrentDictionary<long, Packet> _orderingBufferQueue = new ConcurrentDictionary<long, Packet>();
  		private          CancellationTokenSource                          _cancellationToken;
  
  		public ConnectionInfo ConnectionInfo { get; }
  
  		public ICustomMessageHandler CustomMessageHandler { get; set; }
  		
  		public IPEndPoint EndPoint { get; }
  		public short MtuSize { get; }
  		public long NetworkIdentifier { get; set; }
  
  		//public int DatagramSequenceNumber = -1;
  		public int ReliableMessageNumber = -1;
  		public int SplitPartId = 0;
  		public int OrderingIndex = -1;
  	
  		public bool Evicted { get; set; }
  
  		public ConnectionState State { get; set; } = ConnectionState.Unconnected;
  
  		public DateTime LastUpdatedTime { get; set; }
  		//public int ResendCount { get; set; }
  
  		public ICongestionManager CongestionManager { get; }
  
  		public const long InactivityTimeout = 30000;
  
  		public ConcurrentDictionary<int, SplitPartPacket[]> Splits { get; } = new ConcurrentDictionary<int, SplitPartPacket[]>();
  		private ConcurrentQueue<int> OutgoingAckQueue { get; } = new ConcurrentQueue<int>();
  		private ConcurrentQueue<int> OutgoingNackQueue { get; } = new ConcurrentQueue<int>();
  		public ConcurrentDictionary<int, Datagram> WaitingForAckQueue { get; } = new ConcurrentDictionary<int, Datagram>();
  
  		public int CompressionThreshold { get; set; } = -1;
  		private HighPrecisionTimer _tickerHighPrecisionTimer;
  		public RaknetSession(ConnectionInfo connectionInfo, RaknetConnection connection, IPEndPoint endPoint, short mtuSize, ICustomMessageHandler messageHandler = null)
  		{
  			_connection = connection;
  			ConnectionInfo = connectionInfo;
  			CustomMessageHandler = messageHandler;
  			EndPoint = endPoint;
  			MtuSize = mtuSize;
  
  			_cancellationToken = new CancellationTokenSource();
  
  			CongestionManager = new SlidingWindow(mtuSize);
  			_tickerHighPrecisionTimer = new HighPrecisionTimer(10, SendTick);
  		}
  
  		/// <summary>
  		///     Main receive entry to this layer. Will receive and handle messages
  		///     on RakNet message level. May come from either UDP or TCP, matters not.
  		/// </summary>
  		/// <param name="message"></param>
  		internal void HandleRakMessage(Packet message)
  		{
  			if (message == null) return;
  			
  			// This is not completely finished. Ordering and sequence streams (32 unique channels/streams each)
  			// needs to work by their channel index. Right now, it's only one channel per reliability type.
  			// According to Dylan order and sequence streams can run on the same channel, but documentation
  			// says it can not. So I'll go with documentation until proven wrong.
  
  			switch (message.ReliabilityHeader.Reliability)
  			{
  				case Reliability.ReliableOrdered:
  				case Reliability.ReliableOrderedWithAckReceipt:
  					AddToOrderedChannel(message);
  					break;
  				case Reliability.UnreliableSequenced:
  				case Reliability.ReliableSequenced:
  					Log.Warn($"Sequenced: {message.ReliabilityHeader.SequencingIndex} | OrderIndex: {message.ReliabilityHeader.OrderingIndex}");
  					AddToOrderedChannel(message);
  					break;
  				case Reliability.Unreliable:
  				case Reliability.UnreliableWithAckReceipt:
  				case Reliability.Reliable:
  				case Reliability.ReliableWithAckReceipt:
  					HandlePacket(message);
  					break;
  				case Reliability.Undefined:
  					Log.Error($"Receive packet with undefined reliability");
  					break;
  				default:
  					Log.Warn($"Receive packet with unexpected reliability={message.ReliabilityHeader.Reliability}");
  					break;
  			}
  		}
  
  		private AutoResetEvent _orderingResetEvent = new AutoResetEvent(false);
  
  		private Thread _orderingThread = null;
  		private void AddToOrderedChannel(Packet message)
  		{
  
  			try
  			{
  				if (_cancellationToken.Token.IsCancellationRequested) return;
  
  				var current = message.ReliabilityHeader.OrderingIndex.IntValue();
  
  				if (Interlocked.Read(ref _lastOrderingIndex) >= current)
  					return;
  				
  				if (_orderingThread == null)
  				{
  					var task = new Task(ProcessOrderedQueue, _cancellationToken.Token, TaskCreationOptions.LongRunning);
  					
  					/*_orderingThread = new Thread(
  						ProcessOrderedQueue)
  					{
  						Name = $"RaknetSession Ordering"
  					};
  					_orderingThread.Start();*/
  					task.Start();
  				}
  
  				if (_orderingBufferQueue.TryAdd(current, message))
  					_orderingResetEvent.Set();
  			}
  			catch (Exception e)
  			{
  				Log.Error(e, "Something went wrong!");
  			}
  		}
  
  		public bool IsOutOfOrder => _orderingBufferQueue.Count > 1;
  		private void ProcessOrderedQueue()
  		{
  			_orderingThread = Thread.CurrentThread;
  			_orderingThread.Name = $"RaknetSession Ordering ({EndPoint})";
  			int count = 0;
  			
  			try
  			{
  				while (!_cancellationToken.IsCancellationRequested && !Evicted)
  				{
  					var key = Interlocked.Read(ref _lastOrderingIndex) + 1;
  					if (_orderingBufferQueue.TryRemove(key, out var value))
  					{
  						Interlocked.Exchange(ref _lastOrderingIndex, key);
  						HandlePacket(value);
  						continue;
  					}
  					
  					_orderingResetEvent.WaitOne();
  				}
  
  			}
  			catch (ObjectDisposedException)
  			{
  				// Ignore. Comes from the reset events being waited on while being disposed. Not a problem.
  			}
  			catch (Exception e)
  			{
  				Log.Error(e, $"Exit receive handler task for player");
  			}
  
  			_orderingThread = null;
  		}
  
  		private void HandlePacket(Packet message)
  		{
  			if (message == null) return;
  
  			try
  			{
  				//	RakOfflineHandler.TraceReceive(Log, message);
  				if (message.Id < (int) DefaultMessageIdTypes.ID_USER_PACKET_ENUM)
  				{
  					// Standard RakNet online message handlers
  					switch (message)
  					{
  						case ConnectedPing connectedPing:
  							HandleConnectedPing(connectedPing);
  
  							break;
  
  						case ConnectedPong connectedPong:
  							//HandleConnectedPong(connectedPong);
  
  							break;
  
  						case DetectLostConnections _:
  							break;
  
  						case ConnectionRequest connectionRequest:
  							HandleConnectionRequest(connectionRequest);
  
  							break;
  
  						case ConnectionRequestAccepted connectionRequestAccepted:
  							HandleConnectionRequestAccepted(connectionRequestAccepted);
  
  							break;
  
  						case NewIncomingConnection newIncomingConnection:
  							HandleNewIncomingConnection(newIncomingConnection);
  
  							break;
  
  						case DisconnectionNotification _:
  							HandleDisconnectionNotification();
  
  							break;
  
  						default:
  							Log.Error(
  								$"Unhandled packet: {message.GetType().Name} 0x{message.Id:X2} IP {EndPoint.Address}");
  
  							if (Log.IsDebugEnabled)
  								Log.Warn($"Unknown packet 0x{message.Id:X2}\n{Packet.HexDump(message.Bytes)}");
  
  							break;
  					}
  				}
  				else
  				{
  					try
  					{
  						CustomMessageHandler.HandlePacket(message);
  					}
  					catch (Exception e)
  					{
  						// ignore
  						if (e is InvalidDataException)
  						{
  							throw;
  						}
  						
  						Log.Warn(e, $"Custom message handler error");
  					}
  				}
  				
  			}
  			catch (Exception e)
  			{
  				Log.Error(e, "Packet handling error!");
  				Disconnect("Packet handling error!", false);
  				
  				throw;
  			}
  			finally
  			{
  				message?.PutPool();
  			}
  		}
  
  		protected virtual void HandleConnectedPing(ConnectedPing message)
  		{
  			var packet = ConnectedPong.CreateObject();
  			packet.sendpingtime = message.sendpingtime;
  			packet.sendpongtime = DateTime.UtcNow.Ticks / TimeSpan.TicksPerMillisecond;
  			SendPacket(packet);
  		}
  
  		protected virtual void HandleConnectionRequest(ConnectionRequest message)
  		{
  			Log.Debug("Connection request from: {0}", EndPoint.Address);
  
  
gt: 			var response = ConnectionRequestAccepted.CreateObject();