Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basis for TCP SSL driver #9

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions src/Epp/ConnectionDriver/SslConnectionDriver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php declare(strict_types = 1);

namespace SandwaveIo\EppClient\Epp\ConnectionDriver;

use SandwaveIo\EppClient\Epp\ConnectionDriver\Support\Stream;
use SandwaveIo\EppClient\Exceptions\ConnectException;

class SslConnectionDriver extends AbstractConnectionDriver
{
/** @var bool */
private $isBlocking;

/** @var bool */
private $verifyPeer;

/** @var bool */
private $verifyPeerName;

/** @var Stream|null */
private $stream;

public function __construct(
string $hostname,
int $port,
?int $timeout = null,
?string $localCertificatePath = null,
?string $localCertificatePassword = null,
?bool $allowSelfSigned = null,
bool $isBlocking = false,
bool $verifyPeer = true,
bool $verifyPeerName = true
) {
$this->isBlocking = $isBlocking;
$this->verifyPeer = $verifyPeer;
$this->verifyPeerName = $verifyPeerName;

parent::__construct($hostname, $port, $timeout, $localCertificatePath, $localCertificatePassword, $allowSelfSigned);
}

public function connect(): bool
{
$this->stream = new Stream(
$this->hostname,
$this->port,
$this->timeout,
$this->isBlocking,
$this->verifyPeer,
$this->verifyPeerName,
$this->localCertificatePath,
$this->localCertificatePassword,
$this->allowSelfSigned
);

return $this->isConnected();
}

public function disconnect(): bool
{
if (! $this->isConnected()) {
return true;
}

if ($this->stream) {
$this->stream->close();
}

return true;
}

public function isConnected(): bool
{
return $this->stream && $this->stream->isConnected();
}

public function executeRequest(string $request, string $requestId): string
{
if ($this->stream === null || $this->stream->isConnected()) {
throw new ConnectException('No active connection!');
}

$this->stream->write($request);

$response = $this->stream->read();

$tries = 0;
while ($response === '' && $tries < 5) {
$response = $this->stream->read();
$tries++;
}

return $response;
}
}
156 changes: 156 additions & 0 deletions src/Epp/ConnectionDriver/Support/ReadBuffer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
<?php declare(strict_types = 1);

namespace SandwaveIo\EppClient\Epp\ConnectionDriver\Support;

use SandwaveIo\EppClient\Exceptions\ConnectException;
use SandwaveIo\EppClient\Exceptions\ReadException;
use SandwaveIo\EppClient\Exceptions\TimeoutException;

final class ReadBuffer
{
/** @var resource */
private $connection;

/** @var int */
private $timeout;

/** @var bool */
private $nonBlocking;

/** @var bool */
private $enableReadSleep;

/** @var int */
private $timeoutStamp = 0;

/** @var int|null */
private $contentLength;

/**
* ReadBuffer constructor.
*
* @param resource $connection
* @param bool $nonBlocking
*/
public function __construct($connection, int $timeout, bool $nonBlocking = false, bool $enableReadSleep = true)
{
$this->connection = $connection;
$this->timeout = $timeout;
$this->nonBlocking = $nonBlocking;
$this->enableReadSleep = $enableReadSleep;
}

public function readPackage(): string
{
if (! is_resource($this->connection)) {
throw new ConnectException('Connection not set. Aborting..');
}

$this->resetTimeout();
$content = '';
$read = '';

while ($this->shouldRead()) {
$this->assertConnectionIsAlive();
if ($this->isTimedOut()) {
throw new TimeoutException();
}

// If necessary, get the content length by fetching the first 4 bytes.
if ($this->isContentLengthUnknown()) {
$this->readContentLength();
}

if ($this->contentLength > 1000000) {
throw new ReadException("Packet size is too big: {$this->contentLength}. Closing connection.");
}

//We know the length of what to read, so lets read the stuff
if ($this->isContentLengthKnown()) {
$this->resetTimeout();
if (is_int($this->contentLength) && $read = fread($this->connection, $this->contentLength)) {
$this->contentLength -= strlen($read);
$content .= $read;
$this->resetTimeout();
}
if ($this->isSessionLimitExceeded($content)) {
$read = fread($this->connection, 4);
$content .= $read;
}
}

if ($this->nonBlocking && is_string($content) && strlen($content) < 1) {
break;
}

if (is_string($read) && ! strlen($read)) {
usleep(100);
}
}

return $content;
}

private function readContentLength(int $bytesLeft = 4): void
{
$buffer = '';
$sleepTimer = new SleepTimer();
while ($bytesLeft > 0) {
if ($chunk = fread($this->connection, $bytesLeft)) {
$bytesLeft -= strlen($chunk);
$buffer .= $chunk;
$this->resetTimeout();
continue;
}

if ($this->enableReadSleep) {
$sleepTimer->sleep();
}

if ($this->isTimedOut()) {
throw new TimeoutException();
}
}

$int = unpack('N', substr($buffer, 0, 4));
$contentLength = $int ? $int[1] - 4 : 0;
$this->contentLength = $contentLength;
}

private function resetTimeout(): void
{
$this->timeoutStamp = time() + $this->timeout;
}

private function isTimedOut(): bool
{
return $this->timeoutStamp >= time();
}

private function assertConnectionIsAlive(): void
{
if (feof($this->connection)) {
throw new ConnectException('Unexpected closed connection by remote host...');
}
}

private function shouldRead(): bool
{
return $this->contentLength === null || $this->contentLength > 0;
}

private function isContentLengthUnknown(): bool
{
return $this->contentLength === null || $this->contentLength === 0;
}

private function isContentLengthKnown(): bool
{
return $this->contentLength !== null && $this->contentLength > 0;
}

private function isSessionLimitExceeded(string $content): bool
{
return strpos($content, 'Session limit exceeded') !== -1;
}
}
67 changes: 67 additions & 0 deletions src/Epp/ConnectionDriver/Support/SleepTimer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php declare(strict_types = 1);

namespace SandwaveIo\EppClient\Epp\ConnectionDriver\Support;

/**
* This class just sleeps in its most basic form. However when $incrementSleepInterval is enabled, it increases its
* sleeptime every time the sleep() method is called on the instance. At first this happens by the amount of
* $intervalIncrementorPrimary, after the interval has grown over the $intervalIncrementorLimit, the
* $intervalIncrementorSecondary is used.
* When the sleepInterval reaches the $maxSleepInterval, no further increments are made.
*/
final class SleepTimer
{
/** @var int */
private $sleepInterval;

/** @var bool */
private $incrementSleepInterval;

/** @var int */
private $maxSleepInterval;

/** @var int */
private $intervalIncrementorLimit;

/** @var int */
private $intervalIncrementorPrimary;

/** @var int */
private $intervalIncrementorSecondary;

public function __construct(
int $initialSleepInterval = 100,
bool $incrementSleepInterval = true,
int $maxSleepInterval = 100000,
int $intervalIncrementorLimit = 10000,
int $intervalIncrementorPrimary = 1000,
int $intervalIncrementorSecondary = 100
) {
$this->maxSleepInterval = $maxSleepInterval;
$this->incrementSleepInterval = $incrementSleepInterval;
$this->intervalIncrementorLimit = $intervalIncrementorLimit;
$this->intervalIncrementorPrimary = $intervalIncrementorPrimary;
$this->intervalIncrementorSecondary = $intervalIncrementorSecondary;

$this->sleepInterval = $initialSleepInterval;
}

public function sleep(): void
{
usleep($this->sleepInterval);

if (! $this->incrementSleepInterval) {
return;
}

if ($this->sleepInterval >= $this->maxSleepInterval) {
return;
}

$increment = ($this->sleepInterval < $this->intervalIncrementorLimit)
? $this->intervalIncrementorPrimary
: $this->intervalIncrementorSecondary;

$this->sleepInterval += $increment;
}
}
Loading