Files
device-management/app/Network/Clients/Hsgq/HsgqTelnetClient.php
T
Wian Drs 1e80be180e
tests / ci (push) Has been cancelled
Initial commit device-management
2026-08-25 09:42:00 +07:00

133 lines
4.7 KiB
PHP

<?php
namespace App\Network\Clients\Hsgq;
use RuntimeException;
class HsgqTelnetClient implements HsgqCliClient
{
/** @var resource|null */
private $socket;
public function __construct(private readonly string $host, private readonly int $port = 23, private readonly int $timeout = 10) {}
public function connect(string $username, string $password): void
{
$errorNumber = 0;
$errorMessage = '';
$this->socket = @stream_socket_client("tcp://{$this->host}:{$this->port}", $errorNumber, $errorMessage, $this->timeout);
if (! is_resource($this->socket)) {
throw new RuntimeException('TELNET_UNREACHABLE: koneksi Telnet HSGQ port 23 gagal.');
}
stream_set_timeout($this->socket, $this->timeout);
$this->readUntil('/(?:login|username|user\s*name)\s*:\s*$/i');
$this->write($username);
$this->readUntil('/password\s*:\s*$/i');
$this->write($password);
$output = $this->readPrompt();
if (! preg_match('/[>#]\s*$/', trim($output))) {
throw new RuntimeException('AUTHENTICATION_FAILED: login Telnet HSGQ ditolak.');
}
}
public function command(string $command): string
{
$this->write($command);
$output = $this->readPrompt();
$isHelpCommand = in_array(trim($command), ['?', 'help', 'list'], true);
if (! $isHelpCommand && preg_match('/(?:Error|Invalid|Failed|failure|unknown command|Command incomplete|There is no matched command)/i', $output)) {
preg_match('/^(?!.*user\s+(?:add|password)).*(?:Error|Invalid|Failed|failure|unknown command|Command incomplete|There is no matched command).*$/mi', $output, $detail);
throw new RuntimeException('COMMAND_REJECTED: '.str($detail[0] ?? 'HSGQ menolak perintah CLI Telnet.')->trim()->limit(180));
}
return $output;
}
public function enterEnable(string $password): void
{
$this->write('enable');
$output = $this->readUntil('/(?:Password\s*:|#\s*$)/i');
if (preg_match('/Password\s*:/i', $output)) {
$this->write($password);
$output = $this->readPrompt();
}
if (! str_ends_with(trim($output), '#')) {
throw new RuntimeException('ENABLE_FAILED: gagal masuk privileged mode Telnet HSGQ.');
}
}
public function disconnect(): void
{
if (is_resource($this->socket)) {
fclose($this->socket);
}
$this->socket = null;
}
private function write(string $value): void
{
if (! is_resource($this->socket) || fwrite($this->socket, $value."\r\n") === false) {
throw new RuntimeException('CONNECTION_CLOSED: sesi Telnet HSGQ terputus.');
}
}
private function readPrompt(): string
{
return $this->readUntil('/(?:\([^\r\n]+\))?[#>]\s*$/');
}
private function readUntil(string $pattern): string
{
if (! is_resource($this->socket)) {
throw new RuntimeException('CONNECTION_CLOSED: sesi Telnet HSGQ belum tersambung.');
}
$output = '';
$startedAt = microtime(true);
while (microtime(true) - $startedAt < $this->timeout) {
$chunk = fread($this->socket, 4096);
if ($chunk === false || ($chunk === '' && feof($this->socket))) {
throw new RuntimeException('CONNECTION_CLOSED: sesi Telnet HSGQ ditutup perangkat.');
}
if ($chunk !== '') {
$output .= $this->stripNegotiation($chunk);
if (preg_match($pattern, $output)) {
return $output;
}
}
$metadata = stream_get_meta_data($this->socket);
if ($metadata['timed_out']) {
break;
}
}
throw new RuntimeException('CONNECTION_TIMEOUT: prompt Telnet HSGQ tidak diterima.');
}
private function stripNegotiation(string $data): string
{
$clean = '';
$length = strlen($data);
for ($index = 0; $index < $length; $index++) {
if (ord($data[$index]) !== 255) {
$clean .= $data[$index];
continue;
}
if ($index + 2 >= $length) {
break;
}
$command = ord($data[++$index]);
$option = ord($data[++$index]);
if (in_array($command, [251, 252], true)) {
fwrite($this->socket, pack('CCC', 255, 254, $option));
} elseif (in_array($command, [253, 254], true)) {
fwrite($this->socket, pack('CCC', 255, 252, $option));
}
}
return str_replace("\0", '', $clean);
}
}