Initial Commit

This commit is contained in:
David Stone
2024-11-30 18:24:12 -07:00
commit e8f7955c1c
5432 changed files with 1397750 additions and 0 deletions

View File

@ -0,0 +1,217 @@
<?php
namespace WP_Ultimo\Dependencies\Amp\Dns\Internal;
use WP_Ultimo\Dependencies\Amp;
use WP_Ultimo\Dependencies\Amp\ByteStream\ResourceInputStream;
use WP_Ultimo\Dependencies\Amp\ByteStream\ResourceOutputStream;
use WP_Ultimo\Dependencies\Amp\ByteStream\StreamException;
use WP_Ultimo\Dependencies\Amp\Deferred;
use WP_Ultimo\Dependencies\Amp\Dns\DnsException;
use WP_Ultimo\Dependencies\Amp\Dns\TimeoutException;
use WP_Ultimo\Dependencies\Amp\Promise;
use WP_Ultimo\Dependencies\LibDNS\Messages\Message;
use WP_Ultimo\Dependencies\LibDNS\Messages\MessageFactory;
use WP_Ultimo\Dependencies\LibDNS\Messages\MessageTypes;
use WP_Ultimo\Dependencies\LibDNS\Records\Question;
use function WP_Ultimo\Dependencies\Amp\call;
/** @internal */
abstract class Socket
{
const MAX_CONCURRENT_REQUESTS = 500;
/** @var ResourceInputStream */
private $input;
/** @var ResourceOutputStream */
private $output;
/** @var array Contains already sent queries with no response yet. For UDP this is exactly zero or one item. */
private $pending = [];
/** @var MessageFactory */
private $messageFactory;
/** @var callable */
private $onResolve;
/** @var int Used for determining whether the socket can be garbage collected, because it's inactive. */
private $lastActivity;
/** @var bool */
private $receiving = \false;
/** @var array Queued requests if the number of concurrent requests is too large. */
private $queue = [];
/**
* @param string $uri
*
* @return Promise<self>
*/
public static abstract function connect(string $uri) : Promise;
/**
* @param Message $message
*
* @return Promise<int>
*/
protected abstract function send(Message $message) : Promise;
/**
* @return Promise<Message>
*/
protected abstract function receive() : Promise;
/**
* @return bool
*/
public abstract function isAlive() : bool;
public function getLastActivity() : int
{
return $this->lastActivity;
}
protected function __construct($socket)
{
$this->input = new ResourceInputStream($socket);
$this->output = new ResourceOutputStream($socket);
$this->messageFactory = new MessageFactory();
$this->lastActivity = \time();
$this->onResolve = function (\Throwable $exception = null, Message $message = null) {
$this->lastActivity = \time();
$this->receiving = \false;
if ($exception) {
$this->error($exception);
return;
}
\assert($message instanceof Message);
$id = $message->getId();
// Ignore duplicate and invalid responses.
if (isset($this->pending[$id]) && $this->matchesQuestion($message, $this->pending[$id]->question)) {
/** @var Deferred $deferred */
$deferred = $this->pending[$id]->deferred;
unset($this->pending[$id]);
$deferred->resolve($message);
}
if (empty($this->pending)) {
$this->input->unreference();
} elseif (!$this->receiving) {
$this->input->reference();
$this->receiving = \true;
$this->receive()->onResolve($this->onResolve);
}
};
}
/**
* @param \LibDNS\Records\Question $question
* @param int $timeout
*
* @return \Amp\Promise<\LibDNS\Messages\Message>
*/
public final function ask(Question $question, int $timeout) : Promise
{
return call(function () use($question, $timeout) {
$this->lastActivity = \time();
if (\count($this->pending) > self::MAX_CONCURRENT_REQUESTS) {
$deferred = new Deferred();
$this->queue[] = $deferred;
(yield $deferred->promise());
}
do {
$id = \random_int(0, 0xffff);
} while (isset($this->pending[$id]));
$deferred = new Deferred();
$pending = new class
{
use Amp\Struct;
public $deferred;
public $question;
};
$pending->deferred = $deferred;
$pending->question = $question;
$this->pending[$id] = $pending;
$message = $this->createMessage($question, $id);
try {
(yield $this->send($message));
} catch (StreamException $exception) {
$exception = new DnsException("Sending the request failed", 0, $exception);
$this->error($exception);
throw $exception;
}
$this->input->reference();
if (!$this->receiving) {
$this->receiving = \true;
$this->receive()->onResolve($this->onResolve);
}
try {
// Work around an OPCache issue that returns an empty array with "return yield ...",
// so assign to a variable first and return after the try block.
//
// See https://github.com/amphp/dns/issues/58.
// See https://bugs.php.net/bug.php?id=74840.
$result = (yield Promise\timeout($deferred->promise(), $timeout));
} catch (Amp\TimeoutException $exception) {
unset($this->pending[$id]);
if (empty($this->pending)) {
$this->input->unreference();
}
throw new TimeoutException("Didn't receive a response within {$timeout} milliseconds.");
} finally {
if ($this->queue) {
$deferred = \array_shift($this->queue);
$deferred->resolve();
}
}
return $result;
});
}
public final function close()
{
$this->input->close();
$this->output->close();
}
private function error(\Throwable $exception)
{
$this->close();
if (empty($this->pending)) {
return;
}
if (!$exception instanceof DnsException) {
$message = "Unexpected error during resolution: " . $exception->getMessage();
$exception = new DnsException($message, 0, $exception);
}
$pending = $this->pending;
$this->pending = [];
foreach ($pending as $pendingQuestion) {
/** @var Deferred $deferred */
$deferred = $pendingQuestion->deferred;
$deferred->fail($exception);
}
}
protected final function read() : Promise
{
return $this->input->read();
}
protected final function write(string $data) : Promise
{
return $this->output->write($data);
}
protected final function createMessage(Question $question, int $id) : Message
{
$request = $this->messageFactory->create(MessageTypes::QUERY);
$request->getQuestionRecords()->add($question);
$request->isRecursionDesired(\true);
$request->setID($id);
return $request;
}
private function matchesQuestion(Message $message, Question $question) : bool
{
if ($message->getType() !== MessageTypes::RESPONSE) {
return \false;
}
$questionRecords = $message->getQuestionRecords();
// We only ever ask one question at a time
if (\count($questionRecords) !== 1) {
return \false;
}
$questionRecord = $questionRecords->getIterator()->current();
if ($questionRecord->getClass() !== $question->getClass()) {
return \false;
}
if ($questionRecord->getType() !== $question->getType()) {
return \false;
}
if ($questionRecord->getName()->getValue() !== $question->getName()->getValue()) {
return \false;
}
return \true;
}
}

View File

@ -0,0 +1,97 @@
<?php
namespace WP_Ultimo\Dependencies\Amp\Dns\Internal;
use WP_Ultimo\Dependencies\Amp;
use WP_Ultimo\Dependencies\Amp\Deferred;
use WP_Ultimo\Dependencies\Amp\Dns\DnsException;
use WP_Ultimo\Dependencies\Amp\Dns\TimeoutException;
use WP_Ultimo\Dependencies\Amp\Loop;
use WP_Ultimo\Dependencies\Amp\Parser\Parser;
use WP_Ultimo\Dependencies\Amp\Promise;
use WP_Ultimo\Dependencies\Amp\Success;
use WP_Ultimo\Dependencies\LibDNS\Decoder\DecoderFactory;
use WP_Ultimo\Dependencies\LibDNS\Encoder\EncoderFactory;
use WP_Ultimo\Dependencies\LibDNS\Messages\Message;
use function WP_Ultimo\Dependencies\Amp\call;
/** @internal */
final class TcpSocket extends Socket
{
/** @var \LibDNS\Encoder\Encoder */
private $encoder;
/** @var \SplQueue */
private $queue;
/** @var Parser */
private $parser;
/** @var bool */
private $isAlive = \true;
public static function connect(string $uri, int $timeout = 5000) : Promise
{
if (!($socket = @\stream_socket_client($uri, $errno, $errstr, 0, \STREAM_CLIENT_ASYNC_CONNECT))) {
throw new DnsException(\sprintf("Connection to %s failed: [Error #%d] %s", $uri, $errno, $errstr));
}
\stream_set_blocking($socket, \false);
return call(function () use($uri, $socket, $timeout) {
$deferred = new Deferred();
$watcher = Loop::onWritable($socket, static function () use($socket, $deferred) {
$deferred->resolve(new self($socket));
});
try {
return (yield Promise\timeout($deferred->promise(), $timeout));
} catch (Amp\TimeoutException $e) {
throw new TimeoutException("Name resolution timed out, could not connect to server at {$uri}");
} finally {
Loop::cancel($watcher);
}
});
}
public static function parser(callable $callback) : \Generator
{
$decoder = (new DecoderFactory())->create();
while (\true) {
$length = (yield 2);
$length = \unpack("n", $length)[1];
$rawData = (yield $length);
$callback($decoder->decode($rawData));
}
}
protected function __construct($socket)
{
parent::__construct($socket);
$this->encoder = (new EncoderFactory())->create();
$this->queue = new \SplQueue();
$this->parser = new Parser(self::parser([$this->queue, 'push']));
}
protected function send(Message $message) : Promise
{
$data = $this->encoder->encode($message);
$promise = $this->write(\pack("n", \strlen($data)) . $data);
$promise->onResolve(function ($error) {
if ($error) {
$this->isAlive = \false;
}
});
return $promise;
}
protected function receive() : Promise
{
if ($this->queue->isEmpty()) {
return call(function () {
do {
$chunk = (yield $this->read());
if ($chunk === null) {
$this->isAlive = \false;
throw new DnsException("Reading from the server failed");
}
$this->parser->push($chunk);
} while ($this->queue->isEmpty());
return $this->queue->shift();
});
}
return new Success($this->queue->shift());
}
public function isAlive() : bool
{
return $this->isAlive;
}
}

View File

@ -0,0 +1,51 @@
<?php
namespace WP_Ultimo\Dependencies\Amp\Dns\Internal;
use WP_Ultimo\Dependencies\Amp\Dns\DnsException;
use WP_Ultimo\Dependencies\Amp\Promise;
use WP_Ultimo\Dependencies\Amp\Success;
use WP_Ultimo\Dependencies\LibDNS\Decoder\DecoderFactory;
use WP_Ultimo\Dependencies\LibDNS\Encoder\EncoderFactory;
use WP_Ultimo\Dependencies\LibDNS\Messages\Message;
use function WP_Ultimo\Dependencies\Amp\call;
/** @internal */
final class UdpSocket extends Socket
{
/** @var \LibDNS\Encoder\Encoder */
private $encoder;
/** @var \LibDNS\Decoder\Decoder */
private $decoder;
public static function connect(string $uri) : Promise
{
if (!($socket = @\stream_socket_client($uri, $errno, $errstr, 0, \STREAM_CLIENT_ASYNC_CONNECT))) {
throw new DnsException(\sprintf("Connection to %s failed: [Error #%d] %s", $uri, $errno, $errstr));
}
return new Success(new self($socket));
}
protected function __construct($socket)
{
parent::__construct($socket);
$this->encoder = (new EncoderFactory())->create();
$this->decoder = (new DecoderFactory())->create();
}
protected function send(Message $message) : Promise
{
$data = $this->encoder->encode($message);
return $this->write($data);
}
protected function receive() : Promise
{
return call(function () {
$data = (yield $this->read());
if ($data === null) {
throw new DnsException("Reading from the server failed");
}
return $this->decoder->decode($data);
});
}
public function isAlive() : bool
{
return \true;
}
}