<commit_before><?php
namespace JithinJose2\WebSocket;

class Server extends Socket
{   
    protected $clients = array();
    protected $applications = array();
	private $_ipStorage = array();
	private $_requestStorage = array();
	
	// server settings:
	private $_checkOrigin = false;
	private $_allowedOrigins = array('127.0.0.12');
	private $_maxClients = 30;
	private $_maxConnectionsPerIp = 5;
	private $_maxRequestsPerMinute = 50;
	private $_hook;
	
    public function __construct($host = 'localhost', $port = 8000, $ssl = false)
    {
        parent::__construct($host, $port, $ssl);
        $this->log('Server started listening to port 8000');
    }

    /**
     * Creates a connection from a socket resource
     *
     * @param resource $resource A socket resource
     * @return Connection
     */
    protected function createConnection($resource)
    {
        return new Connection($this, $resource,$this->_hook);
    }
	
	protected function _encodeData($action, $data)
	{
		if(empty($action))
		{
			return false;
		}
		
		$payload = array(
			'action' => $action,
			'data' => $data
		);
		
		return json_encode($payload);
	}
	
	public function sendAll($action,$data){
		if(!is_array($data)){
			$val = $data;
			$data = array();
			$data['value'] = $val;
		}
		$data['action'] = $action;
		$data = json_encode($data);
		foreach($this->clients as $client){
			if($client->handshaked){
				$client->send($data);
			}
		}
	}
	
	public function sendData($connection_id,$action,$data){
		if(!is_array($data)){
			$val = $data;
			$data = array();
			$data['value'] = $val;
		}
		if(isset($this->clients[$connection_id]) && $this->clients[$connection_id]->handshaked){
			$data['action'] = $action;
			$data = json_encode($data);
			echo $data;
			$this->clients[$connection_id]->send($data);
		}
	}
	
	/**
	 * Main server method. Listens for connections, handles connectes/disconnectes, e.g.
	 */
	public function run()
	{
		while(true)
		{
			$changed_sockets = $this->allsockets;
			@stream_select($changed_sockets, $write = null, $except = null, 1, 5000);
			
			/*foreach($this->clients as $client){
				print_r($client);
			}*/
			 //send broadcast hooks.
			/*if(strlen($this->broadcat_hook)>0){
				$data = $this->broadcat_hook;
				$data = json_encode($data());
				foreach($this->clients as $client){
					$client->send($data);
				}
			}*/
			
			foreach($changed_sockets as $socket)
			{

				if($socket == $this->master)
				{
					
					if(($ressource = stream_socket_accept($this->master)) === false)
					{
						$this->log('Socket error: ' . socket_strerror(socket_last_error($ressource)));
						continue;
					}
					else
					{
						$client = $this->createConnection($ressource);
						$this->clients[(int)$ressource] = $client;
						$this->allsockets[] = $ressource;
						
						if(count($this->clients) > $this->_maxClients)
						{
							$client->onDisconnect();
							continue;
						}
						
						$this->_addIpToStorage($client->getClientIp());
						if($this->_checkMaxConnectionsPerIp($client->getClientIp()) === false)
						{
							$client->onDisconnect();
							continue;
						}
						
						if(!$this->_hook->onConnect((int)$ressource)){
							$client->onDisconnect((int)$ressource);
							continue;
						}
						
						
					}
				}
				else
				{					
					$client = $this->clients[(int)$socket];
					if(!is_object($client))
					{
						unset($this->clients[(int)$socket]);
						continue;
					}
					$data = $this->readBuffer($socket);					
					$bytes = strlen($data);
					
					if($bytes === 0)
					{
						$client->onDisconnect();						
						continue;
					}
					elseif($data === false)
					{
						$this->removeClientOnError($client);
						continue;
					}
					elseif($client->waitingForData === false && $this->_checkRequestLimit($client->getClientId()) === false)
					{
						$client->onDisconnect();
					}
					else
					{
						$data = $client->onData($data);
						$this->_hook->onDataReceive((int)$socket,$data);
					}
				}
			}
		}
	}
	
	public function closeClient($connection_id){
		$this->clients[$connection_id]->close();
	}

	/**
	 * Returns a server application.
	 * 
	 * @param string $key Name of application.
	 * @return object The application object. 
	 */
	public function getApplication($key)
	{
		if(empty($key))
		{
			return false;
		}
		if(array_key_exists($key, $this->applications))
		{
			return $this->applications[$key];
		}
		return false;
	}

	/**
	 * Adds a new application object to the application storage.
	 * 
	 * @param string $key Name of application.
	 * @param object $application The application object.
	 */
    public function registerApplication($key, $application)
    {
        $this->applications[$key] = $application;
		
		// status is kind of a system-app, needs some special cases:
		if($key === 'status')
		{
			$serverInfo = array(
				'maxClients' => $this->_maxClients,
				'maxConnectionsPerIp' => $this->_maxConnectionsPerIp,
				'maxRequetsPerMinute' => $this->_maxRequestsPerMinute,
			);
			$this->applications[$key]->setServerInfo($serverInfo);
		}
    }
    
	/**
	 * Echos a message to standard output.
	 * 
	 * @param string $message Message to display.
	 * @param string $type Type of message.
	 */
    public function log($message, $type = 'info')
    {
        echo date('Y-m-d H:i:s') . ' [' . ($type ? $type : 'error') . '] ' . $message . PHP_EOL;
    }
	
	/**
	 * Removes a client from client storage.
	 * 
	 * @param Object $client Client object.
	 */
	public function removeClientOnClose($client)
	{
		
		$this->_hook->onDisconnect((int)$client->getClientSocket());
		
		$clientId = $client->getClientId();
		$clientIp = $client->getClientIp();
		$clientPort = $client->getClientPort();
		$resource = $client->getClientSocket();
		
		$this->_removeIpFromStorage($client->getClientIp());
		if(isset($this->_requestStorage[$clientId]))
		{
			unset($this->_requestStorage[$clientId]);
		}
		unset($this->clients[(int)$resource]);
		$index = array_search($resource, $this->allsockets);
		unset($this->allsockets[$index], $client);		
		
		// trigger status application:
		if($this->getApplication('status') !== false)
		{
			$this->getApplication('status')->clientDisconnected($clientIp, $clientPort);
		}
		unset($clientId, $clientIp, $clientPort, $resource);		
	}
	
	/**
	 * Removes a client and all references in case of timeout/error.
	 * @param object $client The client object to remove.
	 */
	public function removeClientOnError($client)
	{		// remove reference in clients app:

		if(method_exists($this->_hook,'onDisconnect')){
			$this->_hook>onDisconnect((int)$client->getClientSocket());	
		}
        
		$resource = $client->getClientSocket();
		$clientId = $client->getClientId();
		$clientIp = $client->getClientIp();
		$clientPort = $client->getClientPort();
		$this->_removeIpFromStorage($client->getClientIp());
		if(isset($this->_requestStorage[$clientId]))
		{
			unset($this->_requestStorage[$clientId]);
		}
		unset($this->clients[(int)$resource]);
		$index = array_search($resource, $this->allsockets);
		unset($this->allsockets[$index], $client);		
		
		// trigger status application:
		if($this->getApplication('status') !== false)
		{<commit_msg>Disconnect bug fixed<commit_after><?php
namespace JithinJose2\WebSocket;

class Server extends Socket
{   
    protected $clients = array();
    protected $applications = array();
	private $_ipStorage = array();
	private $_requestStorage = array();
	
	// server settings:
	private $_checkOrigin = false;
	private $_allowedOrigins = array('127.0.0.12');
	private $_maxClients = 30;
	private $_maxConnectionsPerIp = 5;
	private $_maxRequestsPerMinute = 50;
	private $_hook;
	
    public function __construct($host = 'localhost', $port = 8000, $ssl = false)
    {
        parent::__construct($host, $port, $ssl);
        $this->log('Server started listening to port 8000');
    }

    /**
     * Creates a connection from a socket resource
     *
     * @param resource $resource A socket resource
     * @return Connection
     */
    protected function createConnection($resource)
    {
        return new Connection($this, $resource,$this->_hook);
    }
	
	protected function _encodeData($action, $data)
	{
		if(empty($action))
		{
			return false;
		}
		
		$payload = array(
			'action' => $action,
			'data' => $data
		);
		
		return json_encode($payload);
	}
	
	public function sendAll($action,$data){
		if(!is_array($data)){
			$val = $data;
			$data = array();
			$data['value'] = $val;
		}
		$data['action'] = $action;
		$data = json_encode($data);
		foreach($this->clients as $client){
			if($client->handshaked){
				$client->send($data);
			}
		}
	}
	
	public function sendData($connection_id,$action,$data){
		if(!is_array($data)){
			$val = $data;
			$data = array();
			$data['value'] = $val;
		}
		if(isset($this->clients[$connection_id]) && $this->clients[$connection_id]->handshaked){
			$data['action'] = $action;
			$data = json_encode($data);
			echo $data;
			$this->clients[$connection_id]->send($data);
		}
	}
	
	/**
	 * Main server method. Listens for connections, handles connectes/disconnectes, e.g.
	 */
	public function run()
	{
		while(true)
		{
			$changed_sockets = $this->allsockets;
			@stream_select($changed_sockets, $write = null, $except = null, 1, 5000);
			
			/*foreach($this->clients as $client){
				print_r($client);
			}*/
			 //send broadcast hooks.
			/*if(strlen($this->broadcat_hook)>0){
				$data = $this->broadcat_hook;
				$data = json_encode($data());
				foreach($this->clients as $client){
					$client->send($data);
				}
			}*/
			
			foreach($changed_sockets as $socket)
			{

				if($socket == $this->master)
				{
					
					if(($ressource = stream_socket_accept($this->master)) === false)
					{
						$this->log('Socket error: ' . socket_strerror(socket_last_error($ressource)));
						continue;
					}
					else
					{
						$client = $this->createConnection($ressource);
						$this->clients[(int)$ressource] = $client;
						$this->allsockets[] = $ressource;
						
						if(count($this->clients) > $this->_maxClients)
						{
							$client->onDisconnect();
							continue;
						}
						
						$this->_addIpToStorage($client->getClientIp());
						if($this->_checkMaxConnectionsPerIp($client->getClientIp()) === false)
						{
							$client->onDisconnect();
							continue;
						}
						
						if(!$this->_hook->onConnect((int)$ressource)){
							$client->onDisconnect((int)$ressource);
							continue;
						}
						
						
					}
				}
				else
				{					
					$client = $this->clients[(int)$socket];
					if(!is_object($client))
					{
						unset($this->clients[(int)$socket]);
						continue;
					}
					$data = $this->readBuffer($socket);					
					$bytes = strlen($data);
					
					if($bytes === 0)
					{
						$client->onDisconnect();						
						continue;
					}
					elseif($data === false)
					{
						$this->removeClientOnError($client);
						continue;
					}
					elseif($client->waitingForData === false && $this->_checkRequestLimit($client->getClientId()) === false)
					{
						$client->onDisconnect();
					}
					else
					{
						$data = $client->onData($data);
						$this->_hook->onDataReceive((int)$socket,$data);
					}
				}
			}
		}
	}
	
	public function closeClient($connection_id){
		$this->clients[$connection_id]->close();
	}

	/**
	 * Returns a server application.
	 * 
	 * @param string $key Name of application.
	 * @return object The application object. 
	 */
	public function getApplication($key)
	{
		if(empty($key))
		{
			return false;
		}
		if(array_key_exists($key, $this->applications))
		{
			return $this->applications[$key];
		}
		return false;
	}

	/**
	 * Adds a new application object to the application storage.
	 * 
	 * @param string $key Name of application.
	 * @param object $application The application object.
	 */
    public function registerApplication($key, $application)
    {
        $this->applications[$key] = $application;
		
		// status is kind of a system-app, needs some special cases:
		if($key === 'status')
		{
			$serverInfo = array(
				'maxClients' => $this->_maxClients,
				'maxConnectionsPerIp' => $this->_maxConnectionsPerIp,
				'maxRequetsPerMinute' => $this->_maxRequestsPerMinute,
			);
			$this->applications[$key]->setServerInfo($serverInfo);
		}
    }
    
	/**
	 * Echos a message to standard output.
	 * 
	 * @param string $message Message to display.
	 * @param string $type Type of message.
	 */
    public function log($message, $type = 'info')
    {
        echo date('Y-m-d H:i:s') . ' [' . ($type ? $type : 'error') . '] ' . $message . PHP_EOL;
    }
	
	/**
	 * Removes a client from client storage.
	 * 
	 * @param Object $client Client object.
	 */
	public function removeClientOnClose($client)
	{
		
		$this->_hook->onDisconnect((int)$client->getClientSocket());
		
		$clientId = $client->getClientId();
		$clientIp = $client->getClientIp();
		$clientPort = $client->getClientPort();
		$resource = $client->getClientSocket();
		
		$this->_removeIpFromStorage($client->getClientIp());
		if(isset($this->_requestStorage[$clientId]))
		{
			unset($this->_requestStorage[$clientId]);
		}
		unset($this->clients[(int)$resource]);
		$index = array_search($resource, $this->allsockets);
		unset($this->allsockets[$index], $client);		
		
		// trigger status application:
		if($this->getApplication('status') !== false)
		{
			$this->getApplication('status')->clientDisconnected($clientIp, $clientPort);
		}
		unset($clientId, $clientIp, $clientPort, $resource);		
	}
	
	/**
	 * Removes a client and all references in case of timeout/error.
	 * @param object $client The client object to remove.
	 */
	public function removeClientOnError($client)
	{		// remove reference in clients app:

		if(method_exists($this->_hook,'onDisconnect')){
			$this->_hook->onDisconnect((int)$client->getClientSocket());
		}
        
		$resource = $client->getClientSocket();
		$clientId = $client->getClientId();
		$clientIp = $client->getClientIp();
		$clientPort = $client->getClientPort();
		$this->_removeIpFromStorage($client->getClientIp());
		if(isset($this->_requestStorage[$clientId]))
		{
			unset($this->_requestStorage[$clientId]);
		}
		unset($this->clients[(int)$resource]);
		$index = array_search($resource, $this->allsockets);
		unset($this->allsockets[$index], $client);		
		
		// trigger status application:
		if($this->getApplication('status') !== false)
		{
{'fraction_non_alphanumeric': 0.17011988081214052, 'fraction_numerical': 0.003880534959462269, 'mean_word_length': 3.179554011004923, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 193, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 2, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4461661', 'n_tokens_mistral': 5703, 'n_tokens_neox': 4534, 'n_words': 860}