ReceiveEvent HandleNetworkEvent()
	{
		ReceiveEvent e;
		using( BitReader reader = GetPooledReader() )
		{
			DataStream stream = reader.GetStream();

			int connectionId;
			ChannelIndex channel;
			e = PollReceive( stream, out connectionId, out channel );

			if( e == ReceiveEvent.Error || e == ReceiveEvent.NoMoreEvents ) { return e; }

			// on the server, the connectionId is one to one with the client index
			NodeIndex client = new NodeIndex( (uint)connectionId );

			switch( e )
			{
				case ReceiveEvent.Connect:
					//_profiler.RecordEvent(TickType.Receive, (uint)receivedSize, MessageManager.reverseChannels[channelId], "TRANSPORT_CONNECT");
					Log.Info( $"Client {client.GetClientIndex()} sent initial connection packet" );
					ClientConnection c;
					if( _commonSettings.enableEncryption )
					{
						// This client is required to complete the crypto-hail exchange.
						EllipticDiffieHellman keyExchange = SendCryptoHail();

						c = new ClientConnection( client, ClientConnection.Status.PendingConnectionChallengeResponse, keyExchange );
					}
					else
					{
						c = new ClientConnection( client, ClientConnection.Status.PendingConnectionRequest, null );		
					}
					_connection.Add( c.GetIndex(), c );
					// TODO: cozeroff NO, NO, NO, check for timeouts of clients manually
					//StartCoroutine(ApprovalTimeout(clientId));
					break;

				case ReceiveEvent.Message:
					HandleMessage( reader, client, channel );
					break;
					
				case ReceiveEvent.Disconnect:
					//_profiler.RecordEvent(TickType.Receive, 0, "NONE", "TRANSPORT_DISCONNECT");
					Log.Info( "Disconnect Event From " + client.GetClientIndex() );

					// TODO: cozeroff
					//OnClientDisconnectServer( client );

					if( _onClientDisconnect != null )
					{
						_onClientDisconnect.Invoke( client );
					}
					break;
			}
		}

		return e;
	}
	bool Send( NodeIndex client, InternalMessage message, ChannelIndex channel, BitWriter writer, bool sendImmediately, out string error )
	{
		int clientIndex = client.GetClientIndex();	
		byte[] key = _connection.GetAt(clientIndex-1).GetSharedSecretKey();
		// for the server, connectionId == clientIndex
		return base.Send( clientIndex, key, message, channel, writer, sendImmediately, out error );
	}
	void HandleMessage( BitReader reader, NodeIndex client, ChannelIndex channel )
	{
		InternalMessage messageType = UnwrapMessage( reader, client );
		if( messageType == InternalMessage.INVALID ) { return; }

		//_profiler.StartEvent(TickType.Receive, size, channelId, messageType);

		Log.Info( $"Handling message {AlpacaConstant.GetName(messageType)} from client {client.GetClientIndex()}" );

		ClientConnection connection = _connection.GetAt( client.GetClientIndex() - 1 );
		ClientConnection.Status state = connection.GetState();
		
		if( (state == ClientConnection.Status.PendingConnectionChallengeResponse) && (messageType != InternalMessage.ConnectionResponse ) )
		{
			Log.Error( $"Client {client.GetClientIndex()} is pending connection response, but client sent message {AlpacaConstant.GetName(messageType)} instead." );
			return;
		}

		if( (state == ClientConnection.Status.PendingConnectionRequest) && (messageType != InternalMessage.ConnectionRequest) )
		{
			Log.Error( $"Client {client.GetClientIndex()} is pending connection request, but client sent message {AlpacaConstant.GetName(messageType)} instead." );
			return;
		}

		switch( messageType )
		{
			case InternalMessage.ConnectionRequest:
				OnMessageConnectionRequest( client, reader );
				break;
			case InternalMessage.ConnectionResponse:
				// TODO: cozeroff crypto implementation
				Log.Error( "Crypto not implemented yet!" );
				break;
			case InternalMessage.CustomServer:
				OnMessageCustomServer( client, reader );
				break;
			default:
				Log.Error( $"Read unrecognized messageType{AlpacaConstant.GetName(messageType)}" );
				break;
		}

		//_profiler.EndEvent();
	}
	void OnMessageCustomServer( NodeIndex clientNode, BitReader reader )
	{
		if( _onMessageCustomServer != null )
		{
			_onMessageCustomServer.Invoke( clientNode, reader );
		}
		else
		{
			Log.Error( $"Received custom message from client {clientNode.GetClientIndex()} but no custom handler is set" ); 
		}
	}
	void OnMessageConnectionRequest( NodeIndex clientNode, BitReader reader )
	{
		// update ClientConnection state
		int clientIndex = clientNode.GetClientIndex();
		ClientConnection connection = _connection.GetAt( clientIndex - 1 );
		connection.SetConnected();

		// send the new client the data it needs, plus spawn instructions for all current entities
		using( BitWriter writer = GetPooledWriter() )
		{
			writer.Packed<Int32>( clientIndex  );
			writer.Packed<float>( _networkTime );
			writer.Packed<Int32>( NetworkTransport.GetNetworkTimestamp() );

			int entityCount = _entity.GetCount();
			writer.Packed<Int32>( entityCount );
			
			Entity e;
			for( int i = 0; i < entityCount; ++i )
			{
				e = _entity.GetAt(i);
				e.MakeSpawn().Write( writer );
			}

			string error;
			if( !SendInternal( clientNode, InternalMessage.ConnectionApproved, InternalChannel.Reliable, writer, true, out error ) )
			{
				Log.Error( $"OnMessageConnectionRequest failed to send connection approval data to new client {clientIndex}.\n{error}" );
			}
		}

		// Inform old clients of the new client
		using( BitWriter writer = GetPooledWriter() )
		{
			writer.Packed<Int32>( clientIndex );

			for( int i = 0; i < _connection.GetCount(); ++i )
			{
				if( i == (clientIndex - 1) ) { continue; } // skip the new client

				ClientConnection sibling = _connection.GetAt(i);
				string error;
				if( !SendInternal( sibling.GetId(), InternalMessage.SiblingConnected, InternalChannel.Reliable, writer, false, out error ) )
				{
					Log.Error( $"OnMessageConnectionRequest failed to send SiblingConnected message to all other clients.\n{error}" );
				}
			}
		}

		// callback
		//if( _onClientConnect != null ) { _onClientConnect.Invoke( clientNode ); }
	}
	public Entity SpawnEntityServer( NodeIndex owner, EntityPrefabIndex prefabIndex, Vector3 position, Quaternion rotation, out string error )
	{
		if( owner != NodeIndex.SERVER_NODE_INDEX )
		{
			ClientConnection client = _connection.Get( owner );
			if( client == null )
			{
				error = $"Cannot spawn entity with ownerClientId {owner.GetClientIndex()}, client not yet connected!";
				return null;
			}
		}

		// Generate unique network id
		uint unique_id = _entityCounter;
		++_entityCounter;
		EntityIndex entityIndex = new EntityIndex( unique_id );

		// spawn
		Entity.Data data = new Entity.Data() { id = entityIndex, owner = owner, prefabIndex = prefabIndex };
		Entity.Spawn spawn = new Entity.Spawn( data, position, rotation );
		Entity entity = Entity.SpawnEntity( _commonSettings.entityPrefab, spawn, _localIndex );

		_entity.Add( entityIndex, entity );

		// notify all clients that an entity was created
		for( int i = 0; i < _connection.GetCount(); ++i )
		{
			string clientSendError;
			ClientConnection client = _connection.GetAt(i);
			using( BitWriter writer = GetPooledWriter() )
			{
				spawn.Write( writer );
				if( !SendInternal( client.GetId(), InternalMessage.EntityCreate, InternalChannel.Reliable, writer, false, out clientSendError ) )
				{
					error = $"Failed to send spawn to client {i}, error is:\n{clientSendError}\n";
					return null;
				}
			}
		}

		if( _onEntitySpawn != null ) { _onEntitySpawn.Invoke( entity ); }

		error = null;
		return entity;
	}