2017-06-08 06:33:13 +02:00
|
|
|
<?php
|
2017-11-10 18:35:47 +01:00
|
|
|
|
2017-06-08 06:33:13 +02:00
|
|
|
namespace Amp\Parallel\Sync;
|
|
|
|
|
|
|
|
use Amp\Parser\Parser;
|
|
|
|
|
2018-10-21 17:54:46 +02:00
|
|
|
final class ChannelParser extends Parser
|
2018-10-07 16:50:45 +02:00
|
|
|
{
|
2017-06-08 06:33:13 +02:00
|
|
|
const HEADER_LENGTH = 5;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param callable(mixed $data) Callback invoked when data is parsed.
|
|
|
|
*/
|
2018-10-07 16:50:45 +02:00
|
|
|
public function __construct(callable $callback)
|
|
|
|
{
|
2019-02-21 00:22:48 +01:00
|
|
|
parent::__construct(self::parser($callback));
|
2017-06-08 06:33:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param mixed $data Data to encode to send over a channel.
|
|
|
|
*
|
|
|
|
* @return string Encoded data that can be parsed by this class.
|
|
|
|
*
|
|
|
|
* @throws \Amp\Parallel\Sync\SerializationException
|
|
|
|
*/
|
2018-10-07 16:50:45 +02:00
|
|
|
public function encode($data): string
|
|
|
|
{
|
2017-06-08 06:33:13 +02:00
|
|
|
try {
|
|
|
|
$data = \serialize($data);
|
|
|
|
} catch (\Throwable $exception) {
|
|
|
|
throw new SerializationException(
|
2017-07-21 06:58:13 +02:00
|
|
|
"The given data cannot be sent because it is not serializable.",
|
2018-10-25 06:02:24 +02:00
|
|
|
0,
|
2017-07-21 06:58:13 +02:00
|
|
|
$exception
|
2017-06-08 06:33:13 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return \pack("CL", 0, \strlen($data)) . $data;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-01-22 23:12:55 +01:00
|
|
|
* @param callable $push
|
2017-06-08 06:33:13 +02:00
|
|
|
*
|
|
|
|
* @return \Generator
|
|
|
|
*
|
|
|
|
* @throws \Amp\Parallel\Sync\ChannelException
|
|
|
|
* @throws \Amp\Parallel\Sync\SerializationException
|
|
|
|
*/
|
2019-02-21 00:22:48 +01:00
|
|
|
private static function parser(callable $push): \Generator
|
2018-10-07 16:50:45 +02:00
|
|
|
{
|
2017-06-08 06:33:13 +02:00
|
|
|
while (true) {
|
2017-06-17 20:46:22 +02:00
|
|
|
$header = yield self::HEADER_LENGTH;
|
|
|
|
$data = \unpack("Cprefix/Llength", $header);
|
2017-06-08 06:33:13 +02:00
|
|
|
|
|
|
|
if ($data["prefix"] !== 0) {
|
2018-01-22 23:12:55 +01:00
|
|
|
$data = $header . yield;
|
2019-02-21 00:22:48 +01:00
|
|
|
throw new ChannelException("Invalid packet received: " . self::encodeUnprintableChars($data));
|
2017-06-08 06:33:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$data = yield $data["length"];
|
|
|
|
|
|
|
|
// Attempt to unserialize the received data.
|
|
|
|
try {
|
2019-02-21 00:22:48 +01:00
|
|
|
$result = \unserialize($data);
|
|
|
|
|
|
|
|
if ($result === false && $data !== \serialize(false)) {
|
|
|
|
throw new ChannelException("Received invalid data: " . self::encodeUnprintableChars($data));
|
|
|
|
}
|
2017-06-08 06:33:13 +02:00
|
|
|
} catch (\Throwable $exception) {
|
2018-10-25 05:49:01 +02:00
|
|
|
throw new SerializationException("Exception thrown when unserializing data", 0, $exception);
|
2017-06-08 06:33:13 +02:00
|
|
|
}
|
|
|
|
|
2019-02-21 00:22:48 +01:00
|
|
|
$push($result);
|
2017-06-08 06:33:13 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-21 00:22:48 +01:00
|
|
|
/**
|
|
|
|
* @param string $data Binary data.
|
|
|
|
*
|
|
|
|
* @return string Unprintable characters encoded as \x##.
|
|
|
|
*/
|
|
|
|
private static function encodeUnprintableChars(string $data): string
|
2018-10-07 16:50:45 +02:00
|
|
|
{
|
2019-08-28 01:14:56 +02:00
|
|
|
return \preg_replace_callback("/[^\x20-\x7e]/", function (array $matches): string {
|
2019-02-21 00:22:48 +01:00
|
|
|
return "\\x" . \dechex(\ord($matches[0]));
|
|
|
|
}, $data);
|
2017-06-08 06:33:13 +02:00
|
|
|
}
|
|
|
|
}
|