Skip to content

Commit

Permalink
Add basis for TCP SSL driver
Browse files Browse the repository at this point in the history
This reverts commit 218cdf7.
  • Loading branch information
FrontEndCoffee committed Sep 7, 2020
1 parent 218cdf7 commit 52d6c19
Show file tree
Hide file tree
Showing 5 changed files with 506 additions and 0 deletions.
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

0 comments on commit 52d6c19

Please sign in to comment.