<?php

require "geoip2.phar";
use GeoIp2\Database\Reader;

class mumble{
	
	public function __construct($reader_location = "GeoLite2-Country.mmdb"){
		
		$this->reader = new Reader($reader_location);
	}
	
	public function ping($domain = "0.0.0.0", $port = 64738){
		
		if(($ip = $this->validate_domain($domain)) === false){
			
			throw new Exception("Server IP points to bad host");
		}
		
		$port = (int)$port;
		
		if(!is_int($port)){
			
			throw new Exception("Invalid port specified");
		}
		
		$this->socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);

		// packet timeout
		socket_set_option($this->socket, SOL_SOCKET, SO_RCVTIMEO, ["sec" => 2, "usec" => 0]);
		socket_set_option($this->socket, SOL_SOCKET, SO_SNDTIMEO, ["sec" => 2, "usec" => 0]);
		
		$data = [
			"version" => null,
			//"ident" => null,
			"online" => null,
			"max" => null,
			"bandwidth" => null
		];
		
		if(!@socket_connect($this->socket, $ip, $port)){
			
			// could not connect!
			socket_close($this->socket);
			throw new Exception("Server is offline");
		}
		
		$payload = null;
		
		$payload .= pack("i", 0); // request type (int)
		$payload .= pack("q", 0); // identify response (long long)
		
		socket_write($this->socket, $payload);
		
		$data["ping"] = microtime(true);
		$this->socket_recv($rawdata, 24);
		
		$data["ping"] = round((microtime(true) - $data["ping"]) * 1000);
		
		// separate data
		$zero = true;
		
		for($i=0; $i<4; $i++){
			
			$str = hexdec(bin2hex($rawdata[$i]));
			
			if(
				$zero === true &&
				$str != 0
			){
				$zero = false;
			}
			
			if($zero === false){
				$data["version"] .= $str;
				
				if($i !== 3){
					
					$data["version"] .= ".";
				}
			}
		}
		
		if($data["version"] == null){
			
			$data["version"] = "1.0.0";
		}
		
		/*
		for($i=4; $i<12; $i++){
			
			$data["ident"] .= $rawdata[$i];
		}*/
		
		for($i=12; $i<16; $i++){
			
			$data["online"] .= $rawdata[$i];
		}
		
		for($i=16; $i<20; $i++){
			
			$data["max"] .= $rawdata[$i];
		}
		
		for($i=20; $i<24; $i++){
			
			$data["bandwidth"] .= $rawdata[$i];
		}
		
		/*
			Get name and website
		*/
		$stream = fopen("list.json", "r");
		$json = fread($stream, filesize("list.json"));
		fclose($stream);
		
		$json = json_decode($json, true);
		
		$offset = -1;
		
		for($i=0; $i<count($json); $i++){
			
			for($k=0; $k<count($json[$i]["ip"]); $k++){
				
				if(
					(
						$json[$i]["ip"][$k] == $domain ||
						$json[$i]["ip"][$k] == $ip
					) &&
					$json[$i]["port"] === $port
				){
					
					$offset = $i;
					break 2;
				}
			}
		}
		
		if($offset !== -1){
			
			$data["name"] = $json[$offset]["name"];
			$data["website"] = $json[$offset]["url"];
			
			if(!preg_match('/^https?:\/\//', $data["website"])){
				
				$data["website"] = "https://" . $data["website"];
			}
			
			$data["domains"] = $json[$offset]["ip"];
		}else{
			
			$data["name"] = null;
			$data["website"] = null;
			
			$data["domains"][] = $domain;
			if($domain != $ip){
				
				$data["domains"][] = $ip;
			}
		}
		
		// get country
		try{
			$db = $this->reader->country($ip);
			
			$data["countryname"] = $db->country->name;
			$data["countrycode"] = $db->country->isoCode;
		}catch(Exception $error){
			
			$data["countryname"] = "Unknown";
			$data["countrycode"] = "UN";
		}
		
		// unpack
		$data = [
			"status" => "ok",
			"server" => [
				"name" => $data["name"],
				"domains" => $data["domains"],
				"ping" => $data["ping"],
				"online" => hexdec(bin2hex($data["online"])),
				"max" => hexdec(bin2hex($data["max"])),
				//"ident" => unpack("q", $data["ident"])[1],
				"bandwidth" => (hexdec(bin2hex($data["bandwidth"])) / 1000) . " kbit/s",
				"country" => [
					"name" => $data["countryname"],
					"code" => $data["countrycode"],
				],
				"version" => $data["version"],
				"website" => $data["website"]
			]
		];
		
		return $data;
	}
	
	
	private function validate_domain($url){
		
		$ip = 
			str_replace(
				["[", "]"], // handle ipv6
				"",
				$url
			);
		
		// if its not an IP
		if(!filter_var($ip, FILTER_VALIDATE_IP)){
			
			// resolve domain's IP
			try{
				$ip = gethostbyname($url . ".");
				
			}catch(Exception $error){
				
				return false;
			}
		}
		
		// check if its localhost
		return filter_var(
			$ip,
			FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
		);
	}
	
	
	private function socket_recv(&$data, $len){
		
		error_reporting(0);
		$bytes = socket_recv($this->socket, $data, $len, MSG_WAITALL);
		error_reporting(1);
		
		if(
			$bytes === false || // no data
			$bytes === 0 // disconnected by remote peer
		){
			
			throw new Exception("Timed out");
		}
	}
}
?>