blob: 781a73073d0d1578fc0f66c3adf51b5e4cf20d5b [file]
<?php
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Apache\Ignite\Internal\Binary;
use Ds\Map;
use Ds\Set;
use Brick\Math\BigDecimal;
use Brick\Math\BigInteger;
use Apache\Ignite\Internal\Connection\ClientFailoverSocket;
use Apache\Ignite\Internal\Utils\ArgumentChecker;
use Apache\Ignite\Type\ObjectType;
use Apache\Ignite\Type\CollectionObjectType;
use Apache\Ignite\Type\ComplexObjectType;
use Apache\Ignite\Type\MapObjectType;
use Apache\Ignite\Data\BinaryObject;
use Apache\Ignite\Data\Date;
use Apache\Ignite\Data\EnumItem;
use Apache\Ignite\Data\Time;
use Apache\Ignite\Data\Timestamp;
use Apache\Ignite\Exception\ClientException;
class BinaryCommunicator
{
private $socket;
private $typeStorage;
public function __construct(ClientFailoverSocket $socket)
{
$this->socket = $socket;
$this->typeStorage = new BinaryTypeStorage($this);
}
public function send(int $opCode, ?callable $payloadWriter, callable $payloadReader = null): void
{
$this->socket->send($opCode, $payloadWriter, $payloadReader);
}
public function getTypeStorage(): BinaryTypeStorage
{
return $this->typeStorage;
}
public static function readString(MessageBuffer $buffer): ?string
{
$typeCode = $buffer->readByte();
BinaryUtils::checkTypesCompatibility(ObjectType::STRING, $typeCode);
if ($typeCode === ObjectType::NULL) {
return null;
}
return $buffer->readString();
}
public static function writeString(MessageBuffer $buffer, ?string $str): void
{
if ($str === null) {
$buffer->writeByte(ObjectType::NULL);
} else {
$buffer->writeByte(ObjectType::STRING);
$buffer->writeString($str);
}
}
public function readObject(MessageBuffer $buffer, $expectedType = null)
{
$typeCode = $buffer->readByte();
BinaryUtils::checkTypesCompatibility($expectedType, $typeCode);
return $this->readTypedObject($buffer, $typeCode, $expectedType);
}
public function readStringArray(MessageBuffer $buffer): array
{
return $this->readTypedObject($buffer, ObjectType::STRING_ARRAY);
}
public function writeObject(MessageBuffer $buffer, $object, $objectType = null, bool $writeObjectType = true): void
{
BinaryUtils::checkCompatibility($object, $objectType);
if ($object === null) {
$buffer->writeByte(ObjectType::NULL);
return;
}
$objectType = $objectType ? $objectType : BinaryUtils::calcObjectType($object);
$objectTypeCode = BinaryUtils::getTypeCode($objectType);
if ($writeObjectType) {
$buffer->writeByte($objectTypeCode);
}
switch ($objectTypeCode) {
case ObjectType::BYTE:
case ObjectType::SHORT:
case ObjectType::INTEGER:
case ObjectType::LONG:
case ObjectType::FLOAT:
case ObjectType::DOUBLE:
$buffer->writeNumber($object, $objectTypeCode);
break;
case ObjectType::CHAR:
$buffer->writeChar($object);
break;
case ObjectType::BOOLEAN:
$buffer->writeBoolean($object);
break;
case ObjectType::STRING:
$buffer->writeString($object);
break;
case ObjectType::UUID:
$this->writeUUID($buffer, $object);
break;
case ObjectType::DATE:
$this->writeDate($buffer, $object);
break;
case ObjectType::ENUM:
$this->writeEnum($buffer, $object);
break;
case ObjectType::DECIMAL:
$this->writeDecimal($buffer, $object);
break;
case ObjectType::TIME:
$this->writeTime($buffer, $object);
break;
case ObjectType::TIMESTAMP:
$this->writeTimestamp($buffer, $object);
break;
case ObjectType::BYTE_ARRAY:
case ObjectType::SHORT_ARRAY:
case ObjectType::INTEGER_ARRAY:
case ObjectType::LONG_ARRAY:
case ObjectType::FLOAT_ARRAY:
case ObjectType::DOUBLE_ARRAY:
case ObjectType::CHAR_ARRAY:
case ObjectType::BOOLEAN_ARRAY:
case ObjectType::STRING_ARRAY:
case ObjectType::UUID_ARRAY:
case ObjectType::DATE_ARRAY:
case ObjectType::OBJECT_ARRAY:
case ObjectType::ENUM_ARRAY:
case ObjectType::DECIMAL_ARRAY:
case ObjectType::TIMESTAMP_ARRAY:
case ObjectType::TIME_ARRAY:
$this->writeArray($buffer, $object, $objectType, $objectTypeCode);
break;
case ObjectType::COLLECTION:
$this->writeCollection($buffer, $object, $objectType);
break;
case ObjectType::MAP:
$this->writeMap($buffer, $object, $objectType);
break;
case ObjectType::BINARY_OBJECT:
$this->writeBinaryObject($buffer, $object);
break;
case ObjectType::COMPLEX_OBJECT:
$this->writeComplexObject($buffer, $object, $objectType);
break;
default:
BinaryUtils::unsupportedType($objectType);
}
}
public function readTypedObject(MessageBuffer $buffer, int $objectTypeCode, $expectedType = null)
{
switch ($objectTypeCode) {
case ObjectType::BYTE:
case ObjectType::SHORT:
case ObjectType::INTEGER:
case ObjectType::LONG:
case ObjectType::FLOAT:
case ObjectType::DOUBLE:
return $buffer->readNumber($objectTypeCode);
case ObjectType::CHAR:
return $buffer->readChar();
case ObjectType::BOOLEAN:
return $buffer->readBoolean();
case ObjectType::STRING:
return $buffer->readString();
case ObjectType::UUID:
return $this->readUUID($buffer);
case ObjectType::DATE:
return $this->readDate($buffer);
case ObjectType::ENUM:
case ObjectType::BINARY_ENUM:
return $this->readEnum($buffer);
case ObjectType::DECIMAL:
return $this->readDecimal($buffer);
case ObjectType::TIME:
return $this->readTime($buffer);
case ObjectType::TIMESTAMP:
return $this->readTimestamp($buffer);
case ObjectType::BYTE_ARRAY:
case ObjectType::SHORT_ARRAY:
case ObjectType::INTEGER_ARRAY:
case ObjectType::LONG_ARRAY:
case ObjectType::FLOAT_ARRAY:
case ObjectType::DOUBLE_ARRAY:
case ObjectType::CHAR_ARRAY:
case ObjectType::BOOLEAN_ARRAY:
case ObjectType::STRING_ARRAY:
case ObjectType::UUID_ARRAY:
case ObjectType::DATE_ARRAY:
case ObjectType::OBJECT_ARRAY:
case ObjectType::ENUM_ARRAY:
case ObjectType::DECIMAL_ARRAY:
case ObjectType::TIMESTAMP_ARRAY:
case ObjectType::TIME_ARRAY:
return $this->readArray($buffer, $objectTypeCode, $expectedType);
case ObjectType::COLLECTION:
return $this->readCollection($buffer, $expectedType);
case ObjectType::MAP:
return $this->readMap($buffer, $expectedType);
case ObjectType::BINARY_OBJECT:
return $this->readBinaryObject($buffer, $expectedType);
case ObjectType::NULL:
return null;
case ObjectType::COMPLEX_OBJECT:
return $this->readComplexObject($buffer, $expectedType);
default:
BinaryUtils::unsupportedType($objectTypeCode);
}
return null;
}
private function readDate(MessageBuffer $buffer): Date
{
return new Date($buffer->readLong());
}
private function readTime(MessageBuffer $buffer): Time
{
return new Time($buffer->readLong());
}
private function readTimestamp(MessageBuffer $buffer): Timestamp
{
return new Timestamp($buffer->readLong(), $buffer->readInteger());
}
private function readUUID(MessageBuffer $buffer): array
{
$result = [];
for ($i = 0; $i < BinaryUtils::getSize(ObjectType::UUID); $i++) {
array_push($result, $buffer->readByte(false));
}
return $result;
}
private function readEnum(MessageBuffer $buffer): EnumItem
{
$enumItem = new EnumItem($buffer->readInteger());
$ordinal = $buffer->readInteger();
$enumItem->setOrdinal($ordinal);
$type = $this->typeStorage->getType($enumItem->getTypeId());
if (!$type || !$type->isEnum()) {
BinaryUtils::enumSerializationError(false, sprintf('enum type id "%d" is not registered', $enumItem->getTypeId()));
} elseif (!$type->getEnumValues() || count($type->getEnumValues()) <= $ordinal) {
BinaryUtils::enumSerializationError(false, 'type mismatch');
}
$enumValues = $type->getEnumValues();
$enumItem->setName($enumValues[$ordinal][0]);
$enumItem->setValue($enumValues[$ordinal][1]);
return $enumItem;
}
private function readDecimal(MessageBuffer $buffer): BigDecimal
{
$scale = $buffer->readInteger();
$value = $buffer->readString(false);
$isNegative = (ord($value[0]) & 0x80) !== 0;
if ($isNegative) {
$value[0] = chr(ord($value[0]) & 0x7F);
}
$hexValue = '';
for ($i = 0; $i < strlen($value); $i++) {
$hex = dechex(ord($value[$i]));
if (strlen($hex) < 2) {
$hex = str_repeat('0', 2 - strlen($hex)) . $hex;
}
$hexValue .= $hex;
}
$result = BigDecimal::ofUnscaledValue(BigInteger::parse($hexValue, 16), $scale >= 0 ? $scale : 0);
if ($isNegative) {
$result = $result->negated();
}
if ($scale < 0) {
$result = $result->multipliedBy((BigInteger::of(10))->power(-$scale));
}
return $result;
}
private function readArray(MessageBuffer $buffer, int $arrayTypeCode, $arrayType): array
{
if ($arrayTypeCode === ObjectType::OBJECT_ARRAY) {
$buffer->readInteger();
}
$length = $buffer->readInteger();
$elementType = BinaryUtils::getArrayElementType($arrayType ? $arrayType : $arrayTypeCode);
$keepElementType = $elementType === null ? true : TypeInfo::getTypeInfo($arrayTypeCode)->keepElementType();
$result = array();
for ($i = 0; $i < $length; $i++) {
array_push(
$result,
$keepElementType ?
$this->readObject($buffer, $elementType) :
$this->readTypedObject($buffer, $elementType));
}
return $result;
}
private function readCollection(MessageBuffer $buffer, CollectionObjectType $expectedColType = null)
{
$size = $buffer->readInteger();
$subType = $buffer->readByte();
$isSet = CollectionObjectType::isSet($subType);
$result = $isSet ? new Set() : [];
for ($i = 0; $i < $size; $i++) {
$element = $this->readObject($buffer, $expectedColType ? $expectedColType->getElementType() : null);
if ($isSet) {
$result->add($element);
} else {
array_push($result, $element);
}
}
return $result;
}
private function readMap(MessageBuffer $buffer, MapObjectType $expectedMapType = null): Map
{
$size = $buffer->readInteger();
// map sub-type
$buffer->readByte();
$result = new Map();
$result->allocate($size);
for ($i = 0; $i < $size; $i++) {
$key = $this->readObject($buffer, $expectedMapType ? $expectedMapType->getKeyType() : null);
$value = $this->readObject($buffer, $expectedMapType ? $expectedMapType->getValueType() : null);
$result->put($key, $value);
}
return $result;
}
private function readBinaryObject(MessageBuffer $buffer, ?ComplexObjectType $expectedType): object
{
$size = $buffer->readInteger();
$startPos = $buffer->getPosition();
$buffer->setPosition($startPos + $size);
$offset = $buffer->readInteger();
$endPos = $buffer->getPosition();
$buffer->setPosition($startPos + $offset);
$result = $this->readObject($buffer, $expectedType);
$buffer->setPosition($endPos);
return $result;
}
private function readComplexObject(MessageBuffer $buffer, ?ComplexObjectType $expectedType): object
{
$buffer->setPosition($buffer->getPosition() - 1);
$binaryObject = BinaryObject::fromBuffer($this, $buffer);
return $expectedType ? $binaryObject->toObject($expectedType) : $binaryObject;
}
private function writeDate(MessageBuffer $buffer, Date $date): void
{
$buffer->writeLong($date->getMillis());
}
private function writeTime(MessageBuffer $buffer, Time $time): void
{
$buffer->writeLong($time->getMillis());
}
private function writeTimestamp(MessageBuffer $buffer, Timestamp $timestamp): void
{
$buffer->writeLong($timestamp->getMillis());
$buffer->writeInteger($timestamp->getNanos());
}
private function writeUUID(MessageBuffer $buffer, array $value): void
{
for ($i = 0; $i < count($value); $i++) {
$buffer->writeByte($value[$i], false);
}
}
private function writeEnum(MessageBuffer $buffer, EnumItem $enumValue): void
{
$type = $this->typeStorage->getType($enumValue->getTypeId());
if (!$type || !$type->isEnum()) {
BinaryUtils::enumSerializationError(true, sprintf('enum type id "%d" is not registered', $enumValue->getTypeId()));
}
$buffer->writeInteger($enumValue->getTypeId());
if ($enumValue->getOrdinal() !== null) {
$buffer->writeInteger($enumValue->getOrdinal());
return;
} elseif ($enumValue->getName() !== null || $enumValue->getValue() !== null) {
$enumValues = $type->getEnumValues();
if ($enumValues) {
for ($i = 0; $i < count($enumValues); $i++) {
if ($enumValue->getName() === $enumValues[$i][0] ||
$enumValue->getValue() === $enumValues[$i][1]) {
$buffer->writeInteger($i);
return;
}
}
}
}
ArgumentChecker::illegalArgument('Proper ordinal, name or value must be specified for EnumItem');
}
private function writeDecimal(MessageBuffer $buffer, BigDecimal $decimal): void
{
$scale = $decimal->getScale();
$isNegative = $decimal->isNegative();
$hexValue = $decimal->getUnscaledValue()->abs()->toBase(16);
$hexValue = ((strlen($hexValue) % 2 !== 0) ? '000' : '00') . $hexValue;
if ($isNegative) {
$hexValue[0] = '8';
}
$value = '';
for ($i = 0; $i < strlen($hexValue); $i += 2) {
$value .= chr(hexdec(substr($hexValue, $i, 2)));
}
$buffer->writeInteger($scale);
$buffer->writeString($value, false);
}
private function writeArray(MessageBuffer $buffer, array $array, $arrayType, int $arrayTypeCode): void
{
$elementType = BinaryUtils::getArrayElementType($arrayType);
$keepElementType = !$elementType || TypeInfo::getTypeInfo($arrayTypeCode)->keepElementType();
if ($arrayTypeCode === ObjectType::OBJECT_ARRAY) {
$typeId = -1;
if ($elementType instanceof ComplexObjectType) {
$typeName = BinaryTypeBuilder::calcTypeName($elementType, count($array) > 0 ? $array[0] : null);
if ($typeName) {
$typeId = BinaryType::calculateId($typeName);
}
}
$buffer->writeInteger($typeId);
}
$buffer->writeInteger(count($array));
foreach ($array as $elem) {
$this->writeObject($buffer, $elem, $elementType, $keepElementType);
}
}
private function writeCollection(MessageBuffer $buffer, $collection, CollectionObjectType $collectionType): void
{
$buffer->writeInteger($collection instanceof Set ? $collection->count() : count($collection));
$buffer->writeByte($collectionType->getSubType());
foreach ($collection as $element) {
$this->writeObject($buffer, $element, $collectionType->getElementType());
}
}
private function writeMap(MessageBuffer $buffer, $map, MapObjectType $mapType): void
{
if (!($map instanceof Map)) {
$map = new Map($map);
}
$buffer->writeInteger($map->count());
$buffer->writeByte($mapType->getSubType());
foreach ($map->pairs() as $pair) {
$this->writeObject($buffer, $pair->key, $mapType->getKeyType());
$this->writeObject($buffer, $pair->value, $mapType->getValueType());
}
}
private function writeBinaryObject(MessageBuffer $buffer, BinaryObject $binaryObject): void
{
$buffer->setPosition($buffer->getPosition() - 1);
$binaryObject->write($this, $buffer);
}
private function writeComplexObject(MessageBuffer $buffer, object $object, ?ComplexObjectType $objectType): void
{
$this->writeBinaryObject($buffer, BinaryObject::fromObject($object, $objectType));
}
}