<?php
class RouterosAPI {
    var $error = '';
    var $connected = false;
    public $socket;
    private $timeout = 3;
    private $deadline = 0;

    public function connect($ip, $login, $pass, $port = 8728, $timeout = 3) {
        $this->timeout = $timeout;
        $this->deadline = microtime(true) + $timeout;
        $this->error = '';
        
        $remaining = $this->deadline - microtime(true);
        if ($remaining <= 0) {
            $this->error = "Timeout reached before connection";
            return false;
        }
        
        $this->socket = @fsockopen($ip, (int)$port, $errno, $errstr, $remaining);
        
        if (!$this->socket) {
            $this->error = "Connection failed: $errstr ($errno)";
            return false;
        }
        
        stream_set_timeout($this->socket, $timeout);
        
        // Cek greeting (max 0.2 detik)
        if ($this->hasData(0.2)) {
            $this->readResponse();
        }
        
        if (microtime(true) >= $this->deadline) {
            $this->error = "Timeout reached before login";
            $this->disconnect();
            return false;
        }

        // Login plain text (RouterOS v6.43+)
        $this->write('/login');
        $this->write('=name=' . $login);
        $this->write('=password=' . $pass);
        $this->write('');
        
        $response = $this->readResponse();
        
        foreach ($response as $line) {
            if ($line === '!done') {
                $this->connected = true;
                return true;
            }
            if (strpos($line, '!trap') !== false || strpos($line, '!fatal') !== false) {
                $this->error = "Login failed: " . implode(', ', $response);
                $this->disconnect();
                return false;
            }
        }
        
        // Fallback untuk RouterOS < v6.43
        if (preg_match('/ret=([0-9a-fA-F]+)/', implode('', $response), $matches)) {
            $challenge = pack('H*', $matches[1]);
            $this->write('/login');
            $this->write('=name=' . $login);
            $this->write('=response=00' . md5(chr(0) . $pass . $challenge)); 
            $this->write('');
            
            $response = $this->readResponse();
            
            foreach ($response as $line) {
                if ($line === '!done') {
                    $this->connected = true;
                    return true;
                }
            }
        }
        
        $this->error = "Login failed: Invalid credentials or API not enabled";
        $this->disconnect();
        return false;
    }

    private function hasData($seconds) {
        $read = [$this->socket];
        $write = null;
        $except = null;
        $sec = (int)floor($seconds);
        $usec = (int)(($seconds - $sec) * 1000000);
        return @stream_select($read, $write, $except, $sec, $usec) > 0;
    }

    private function waitForData() {
        $remaining = $this->deadline - microtime(true);
        if ($remaining <= 0) return false;
        
        $read = [$this->socket];
        $write = null;
        $except = null;
        $sec = (int)floor($remaining);
        $usec = (int)(($remaining - $sec) * 1000000);
        
        return @stream_select($read, $write, $except, $sec, $usec) > 0;
    }

    private function readResponse() {
        $response = [];
        
        while (microtime(true) < $this->deadline) {
            if (!$this->waitForData()) break;
            
            $byte = fread($this->socket, 1);
            if ($byte === false || $byte === '') continue;
            
            $len = ord($byte);
            if ($len === 0) break;
            
            if (($len & 0x80) !== 0) {
                if (($len & 0xC0) === 0x80) {
                    $len = (($len & 0x3F) << 8) + ord($this->readByte());
                } elseif (($len & 0xE0) === 0xC0) {
                    $len = (($len & 0x1F) << 16) + (ord($this->readByte()) << 8) + ord($this->readByte());
                } elseif (($len & 0xF0) === 0xE0) {
                    $len = (($len & 0x0F) << 24) + (ord($this->readByte()) << 16) + (ord($this->readByte()) << 8) + ord($this->readByte());
                } elseif (($len & 0xF8) === 0xF0) {
                    $len = 0x7FFFFFFF;
                }
            }
            
            $data = '';
            $remainingLen = $len;
            while ($remainingLen > 0 && microtime(true) < $this->deadline) {
                if (!$this->waitForData()) break;
                $chunk = fread($this->socket, $remainingLen);
                if ($chunk === false || $chunk === '') continue;
                $data .= $chunk;
                $remainingLen -= strlen($chunk);
            }
            
            $response[] = $data;
        }
        
        return $response;
    }

    private function readByte() {
        if (!$this->waitForData()) return '';
        $byte = fread($this->socket, 1);
        return ($byte === false || $byte === '') ? '' : $byte;
    }

    public function write($command) {
        if (!$this->socket) return false;
        
        $data = '';
        $len = strlen($command);
        
        if ($len < 0x80) {
            $data .= chr($len);
        } else {
            $data .= chr(($len >> 8) | 0x80);
            $data .= chr($len & 0xFF);
        }
        
        $data .= $command;
        fwrite($this->socket, $data);
        return true;
    }

    public function comm($command, $params = []) {
        if (!$this->socket) return [];
        
        $this->deadline = microtime(true) + $this->timeout;
        
        $this->write($command);
        foreach ($params as $k => $v) {
            $this->write("=$k=$v");
        }
        $this->write('');
        
        $response = $this->readResponse();
        $result = [];
        $current = [];
        
        foreach ($response as $line) {
            if ($line === '!re') {
                if (!empty($current)) $result[] = $current;
                $current = [];
            } elseif ($line === '!done') {
                if (!empty($current)) $result[] = $current;
                break;
            } elseif (strpos($line, '=') === 0) {
                $parts = explode('=', $line, 3);
                if (count($parts) === 3) {
                    $current[$parts[1]] = $parts[2];
                }
            }
        }
        
        return $result;
    }

    public function disconnect() {
        if ($this->socket) {
            fclose($this->socket);
            $this->socket = null;
        }
        $this->connected = false;
    }
}