#!/usr/local/bin/php
<?php

$GLOBALS['quiet'] = (trim(strtolower($argv[1])) == '-q');

error_reporting(E_PARSE|E_ERROR);
set_time_limit(0);
ob_implicit_flush();
$secsInterval = 0;
$usecsInterval = 500000;

$master = initServer('10.10.123.4', 6789);
$sockets = array($master);
$connections = array();
$w = $e = null;

while(true)
{
	$changed = $sockets;
	socket_select($changed, $w, $e, $secsInterval, $usecsInterval);
	foreach($changed as $socket)
	{
		if($socket == $master)
		{
			if (($client = socket_accept($master)) < 0)
				console("socket_accept() failed");
			else
				connect($client);
		} else {
			if(!@socket_recv($socket, $buffer, 4096, 0))
				disconnect($socket);
			else
			{
				$connex = findConnection($socket);
				if (!$connex->handshake)
					wsHandshake($connex, $buffer);
				else
					receiveMsg($connex, unwrapMsg($buffer));
			}
		}
	}
	checkOutMsgs();
}

function initServer($address, $port)
{
	$master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP) or die("socket_create() failed");
	socket_set_option($master, SOL_SOCKET, SO_REUSEADDR, 1) or die("socket_option() failed");
	socket_bind($master, $address, $port) or die("socket_bind() failed");
	socket_listen($master, 20) or die("socket_listen() failed");
	console('Server Started: ' . date('Y-m-d H:i:s') . "\nListening on $address:$port");
	return $master;
}

function receiveMsg($connex, $msgStr)
{
	$socket = $connex->socket;
	$msg = json_decode($msgStr);
	console('Received Packet');
	console(print_r($msg, true));

	switch($msg->msgType)
	{
		case 'register':
			console('register ' . $msg->msgData);
			$connex->frameworkID = $msg->msgData;
			sendMsg($socket, '', 'registered', $msg->msgData);
			break;

		case 'message':
			// Do something here;
			if ($msg->wantAck)
				sendMsg($socket, $msg->msgID, 'ack', '');
			break;

		case 'query':
			switch (strtolower($msg->msgData))
			{
				case 'date':
					sendMsg($socket, '', 'response', date('m/d/Y', time()));
					break;

				case 'time':
					sendMsg($socket, '', 'response', date('H:i:s', time()));
					break;

				default:
					sendMsg($socket, '', 'response', "What do you mean \"{$msg->msgData}\"?");

			}
			if ($msg->wantAck)
				sendMsg($socket, $msg->msgID, 'ack', '');
			break;

		default:
			sendMsg($socket, $msg->msgID, 'error', "Unknown msgType [{$msg->msgType}]");
	}
}

function sendMsg($socket, $msgID, $msgType, $msgData)
{
	$outMsg['msgID'] = $msgID;
	$outMsg['msgType'] = $msgType;
	$outMsg['msgData'] = $msgData;
	$msgStr = json_encode($outMsg);
	$msgStr = wrapMsg($msgStr);
	socket_write($socket, $msgStr);
}

function checkOutMsgs()
{
	global $connections;
	$fPath = '/www/sites/telemetry/utility';

	foreach($connections as $connection)
	{
		if ($connection->handshake)
		{
			$thisFile = "$fPath/{$connection->frameworkID}";
			if (is_file($thisFile))
			{
				$buff = file_get_contents($thisFile);
				sendMsg($connection->socket, '', 'message', $buff);
				unlink($thisFile);
			}

			$elapse = time() - $connection->connectedAt;
			$now = date('H:i:s', $elapse + 25200);
			if ($now <> $connection->lastSent)
			{
				sendMsg($connection->socket, '', 'servertime', $now);
				$connection->lastSent = $now;
			}
		}
	}
}

function connect($socket)
{
	global $sockets, $connections;

	$connex = new Connection();
	$connex->id = uniqid();
	$connex->socket = $socket;
	$connex->connectedAt = time();
	array_push($connections, $connex);
	array_push($sockets, $socket);
	console($socket . " CONNECTED!");
}

function disconnect($socket)
{
	global $sockets, $connections;

	$found = null;
	$max = count($connections);
	for($i=0; $i<$max; $i++)
	{
		if($connections[$i]->socket == $socket)
		{
			$found = $i;
			break;
		}
	}

	if(!is_null($found))
		array_splice($connections, $found, 1);

	$index = array_search($socket, $sockets);
	socket_close($socket);
	console($socket . " DISCONNECTED!");

	if($index >= 0)
		array_splice($sockets, $index, 1);
}

function wsHandshake($connex, $buffer)
{
	console("\nHandshake...\n$buffer");
	if (preg_match("/GET (.*) HTTP/", $buffer, $match)) $resource = $match[1];
	if (preg_match("/Host: (.*)\r\n/", $buffer, $match)) $host = $match[1];
	if (preg_match("/Origin: (.*)\r\n/", $buffer, $match)) $origin = $match[1];
	if (preg_match("/Sec-WebSocket-Key1: (.*)\r\n/", $buffer, $match)) $strkey1 = $match[1];
	if (preg_match("/Sec-WebSocket-Key2: (.*)\r\n/", $buffer, $match)) $strkey2 = $match[1];
	if (preg_match("/\r\n(.*?)\$/", $buffer, $match)) $data = $match[1];
	$numkey1 = preg_replace('/[^\d]*/', '', $strkey1);
	$numkey2 = preg_replace('/[^\d]*/', '', $strkey2);
	$spaces1 = strlen(preg_replace('/[^ ]*/', '', $strkey1));
	$spaces2 = strlen(preg_replace('/[^ ]*/', '', $strkey2));

	// Here's the checksum...
	if ($spaces1 == 0 || $spaces2 == 0 || fmod($numkey1, $spaces1) != 0 || fmod($numkey2, $spaces2) != 0)
	{
		socket_close($connex->socket);
		console('Handshake failed');
		return false;
	}

	$ctx = hash_init('md5');
	hash_update($ctx, pack("N", $numkey1/$spaces1));
	hash_update($ctx, pack("N", $numkey2/$spaces2));
	hash_update($ctx, $data);
	$hashData = hash_final($ctx,true);

	$headResp[] = 'HTTP/1.1 101 WebSocket Protocol Handshake';
	$headResp[] = 'Upgrade: WebSocket';
	$headResp[] = 'Connection: Upgrade';
	$headResp[] = "Sec-WebSocket-Origin: $origin";
	$headResp[] = "Sec-WebSocket-Location: ws://$host$resource";
	$headResp[] = '';
	$headResp[] = $hashData;
	$hsHead = implode("\r\n", $headResp);
	socket_write($connex->socket, $hsHead, strlen($hsHead));

	$connex->handshake = true;
	console("Handshake:\n$hsHead");
	return true;
}

function findConnection($socket)
{
	global $connections;

	$found = null;
	foreach($connections as $connex)
	{
		if($connex->socket == $socket)
		{
			$found = $connex;
			break;
		}
	}
	return $found;
}

function wrapMsg($msg) { return chr(0) . $msg . chr(255); }
function unwrapMsg($msg) { return substr($msg, 1, strlen($msg) - 2); }
function console($msg) { if (!$GLOBALS['quiet']) echo "$msg\n"; }

class Connection
{
	var $id;
	var $frameworkID;
	var $socket;
	var $handshake;
	var $connectedAt;
	var $lastSend;
}

?>