Skip to content

Commit

Permalink
Convert overflowed integers to floats by default, drop UnpackOptions:…
Browse files Browse the repository at this point in the history
…:BIGINT_AS_EXCEPTION
  • Loading branch information
rybakit committed Jan 8, 2020
1 parent 342a747 commit 4ed09e6
Show file tree
Hide file tree
Showing 10 changed files with 60 additions and 171 deletions.
21 changes: 10 additions & 11 deletions README.md
Expand Up @@ -245,16 +245,16 @@ $unpacker->unpackExt(); // PHP MessagePack\Ext class
The `BufferUnpacker` object supports a number of bitmask-based options for fine-tuning the unpacking process (defaults
are in bold):

| Name | Description |
| ------------------- | ---------------------------------------------------------- |
| BIGINT_AS_EXCEPTION | Throws an exception on integer overflow <sup>[1]</sup> |
| BIGINT_AS_GMP | Converts overflowed integers to GMP objects <sup>[2]</sup> |
| **BIGINT_AS_STR** | Converts overflowed integers to strings |
| Name | Description |
| -------------------- | ---------------------------------------------------------- |
| **BIGINT_AS_FLOAT** | Converts overflowed integers to floats <sup>[1]</sup> |
| BIGINT_AS_STR | Converts overflowed integers to strings |
| BIGINT_AS_GMP | Converts overflowed integers to GMP objects <sup>[2]</sup> |

> *1. The binary MessagePack format has unsigned 64-bit as its largest integer data type,
> but PHP does not support such integers, which means that an overflow can occur during unpacking.*
>
> *2. Make sure that the [GMP](http://php.net/manual/en/book.gmp.php) extension is enabled.*
> *2. Make sure the [GMP](http://php.net/manual/en/book.gmp.php) extension is enabled.*

Examples:
Expand All @@ -266,13 +266,13 @@ use MessagePack\UnpackOptions;
$packedUint64 = "\xcf"."\xff\xff\xff\xff"."\xff\xff\xff\xff";

$unpacker = new BufferUnpacker($packedUint64);
var_dump($unpacker->unpack()); // double(1.844674407371E+19)

$unpacker = new BufferUnpacker($packedUint64, UnpackOptions::BIGINT_AS_STR);
var_dump($unpacker->unpack()); // string(20) "18446744073709551615"

$unpacker = new BufferUnpacker($packedUint64, UnpackOptions::BIGINT_AS_GMP);
var_dump($unpacker->unpack()); // object(GMP) {...}

$unpacker = new BufferUnpacker($packedUint64, UnpackOptions::BIGINT_AS_EXCEPTION);
$unpacker->unpack(); // throws MessagePack\Exception\IntegerOverflowException
```


Expand Down Expand Up @@ -411,10 +411,9 @@ $date = $unpacker->reset($packed)->unpack();
If an error occurs during packing/unpacking, a `PackingFailedException` or `UnpackingFailedException` will be thrown,
respectively.

In addition, there are two more exceptions that can be thrown during unpacking:
In addition, there is one more exception that can be thrown during unpacking:

* `InsufficientDataException`
* `IntegerOverflowException`

An `InvalidOptionException` will be thrown in case an invalid option (or a combination of mutually exclusive options)
is used.
Expand Down
3 changes: 2 additions & 1 deletion examples/uint64.php
Expand Up @@ -13,6 +13,7 @@
use App\MessagePack\Uint64Transformer;
use MessagePack\BufferUnpacker;
use MessagePack\Packer;
use MessagePack\UnpackOptions;

require __DIR__.'/autoload.php';

Expand All @@ -27,7 +28,7 @@
$packed = $packer->pack($uint64);

printf("Packed (%s): %s\n", $uint64, bin2hex($packed));
printf("Unpacked: %s\n", (new BufferUnpacker($packed))->unpack());
printf("Unpacked: %s\n", (new BufferUnpacker($packed, UnpackOptions::BIGINT_AS_STR))->unpack());

/* OUTPUT
Packed (18446744073709551615): cfffffffffffffffff
Expand Down
25 changes: 11 additions & 14 deletions src/BufferUnpacker.php
Expand Up @@ -12,7 +12,6 @@
namespace MessagePack;

use MessagePack\Exception\InsufficientDataException;
use MessagePack\Exception\IntegerOverflowException;
use MessagePack\Exception\InvalidOptionException;
use MessagePack\Exception\UnpackingFailedException;
use MessagePack\TypeTransformer\Extension;
Expand Down Expand Up @@ -556,7 +555,17 @@ private function unpackUint64()
$num = \unpack('J', $this->buffer, $this->offset)[1];
$this->offset += 8;

return $num < 0 ? $this->handleIntOverflow($num) : $num;
if ($num >= 0) {
return $num;
}
if ($this->isBigIntAsStr) {
return \sprintf('%u', $num);
}
if ($this->isBigIntAsGmp) {
return \gmp_init(\sprintf('%u', $num));
}

return (float) \sprintf('%u', $num);
}

private function unpackInt8()
Expand Down Expand Up @@ -727,16 +736,4 @@ private function unpackExtData($length)

return new Ext($type, $data);
}

private function handleIntOverflow($value)
{
if ($this->isBigIntAsStr) {
return \sprintf('%u', $value);
}
if ($this->isBigIntAsGmp) {
return \gmp_init(\sprintf('%u', $value));
}

throw new IntegerOverflowException($value);
}
}
29 changes: 0 additions & 29 deletions src/Exception/IntegerOverflowException.php

This file was deleted.

20 changes: 2 additions & 18 deletions src/Exception/PackingFailedException.php
Expand Up @@ -13,26 +13,10 @@

class PackingFailedException extends \RuntimeException
{
private $value;

public function __construct($value, string $message = '', \Throwable $previous = null)
{
parent::__construct($message, 0, $previous);

$this->value = $value;
}

public function getValue()
{
return $this->value;
}

public static function unsupportedType($value) : self
{
$message = \sprintf('Unsupported type: %s.',
return new self(\sprintf('Unsupported type: %s.',
\is_object($value) ? \get_class($value) : \gettype($value)
);

return new self($value, $message);
));
}
}
26 changes: 13 additions & 13 deletions src/UnpackOptions.php
Expand Up @@ -15,9 +15,9 @@

final class UnpackOptions
{
public const BIGINT_AS_STR = 0b001;
public const BIGINT_AS_GMP = 0b010;
public const BIGINT_AS_EXCEPTION = 0b100;
public const BIGINT_AS_FLOAT = 0b001;
public const BIGINT_AS_STR = 0b010;
public const BIGINT_AS_GMP = 0b100;

private $bigIntMode;

Expand All @@ -28,7 +28,7 @@ private function __construct()
public static function fromDefaults() : self
{
$self = new self();
$self->bigIntMode = self::BIGINT_AS_STR;
$self->bigIntMode = self::BIGINT_AS_FLOAT;

return $self;
}
Expand All @@ -38,14 +38,19 @@ public static function fromBitmask(int $bitmask) : self
$self = new self();

$self->bigIntMode = self::getSingleOption('bigint', $bitmask,
self::BIGINT_AS_FLOAT |
self::BIGINT_AS_STR |
self::BIGINT_AS_GMP |
self::BIGINT_AS_EXCEPTION
) ?: self::BIGINT_AS_STR;
self::BIGINT_AS_GMP
) ?: self::BIGINT_AS_FLOAT;

return $self;
}

public function isBigIntAsFloatMode() : bool
{
return self::BIGINT_AS_FLOAT === $this->bigIntMode;
}

public function isBigIntAsStrMode() : bool
{
return self::BIGINT_AS_STR === $this->bigIntMode;
Expand All @@ -56,11 +61,6 @@ public function isBigIntAsGmpMode() : bool
return self::BIGINT_AS_GMP === $this->bigIntMode;
}

public function isBigIntAsExceptionMode() : bool
{
return self::BIGINT_AS_EXCEPTION === $this->bigIntMode;
}

private static function getSingleOption(string $name, int $bitmask, int $validBitmask) : int
{
$option = $bitmask & $validBitmask;
Expand All @@ -69,9 +69,9 @@ private static function getSingleOption(string $name, int $bitmask, int $validBi
}

static $map = [
self::BIGINT_AS_FLOAT => 'BIGINT_AS_FLOAT',
self::BIGINT_AS_STR => 'BIGINT_AS_STR',
self::BIGINT_AS_GMP => 'BIGINT_AS_GMP',
self::BIGINT_AS_EXCEPTION => 'BIGINT_AS_EXCEPTION',
];

$validOptions = [];
Expand Down
30 changes: 13 additions & 17 deletions tests/Unit/BufferUnpackerTest.php
Expand Up @@ -13,7 +13,6 @@

use MessagePack\BufferUnpacker;
use MessagePack\Exception\InsufficientDataException;
use MessagePack\Exception\IntegerOverflowException;
use MessagePack\Exception\InvalidOptionException;
use MessagePack\Exception\UnpackingFailedException;
use MessagePack\Ext;
Expand Down Expand Up @@ -113,17 +112,21 @@ public function testUnpackThrowsExceptionOnUnknownCode() : void
$this->unpacker->unpack();
}

public function testUnpackThrowsExceptionOnBigIntAsExceptionOption() : void
public function testUnpackBigIntDefaultModeFloat() : void
{
$unpacker = new BufferUnpacker("\xcf"."\xff\xff\xff\xff"."\xff\xff\xff\xff");

self::assertSame('1.8446744073709552E+19', var_export($unpacker->unpack(), true));
}

public function testUnpackBigIntAsFloat() : void
{
$unpacker = new BufferUnpacker(
"\xcf"."\xff\xff\xff\xff"."\xff\xff\xff\xff",
UnpackOptions::BIGINT_AS_EXCEPTION
UnpackOptions::BIGINT_AS_FLOAT
);

$this->expectException(IntegerOverflowException::class);
$this->expectExceptionMessage('The value is too big: 18446744073709551615.');

$unpacker->unpack();
self::assertSame('1.8446744073709552E+19', var_export($unpacker->unpack(), true));
}

public function testUnpackBigIntAsString() : void
Expand All @@ -136,13 +139,6 @@ public function testUnpackBigIntAsString() : void
self::assertSame('18446744073709551615', $unpacker->unpack());
}

public function testUnpackBigIntDefaultModeString() : void
{
$unpacker = new BufferUnpacker("\xcf"."\xff\xff\xff\xff"."\xff\xff\xff\xff");

self::assertSame('18446744073709551615', $unpacker->unpack());
}

/**
* @requires extension gmp
*/
Expand Down Expand Up @@ -343,9 +339,9 @@ public function provideInvalidOptionsData() : iterable
{
return [
[UnpackOptions::BIGINT_AS_STR | UnpackOptions::BIGINT_AS_GMP],
[UnpackOptions::BIGINT_AS_STR | UnpackOptions::BIGINT_AS_EXCEPTION],
[UnpackOptions::BIGINT_AS_GMP | UnpackOptions::BIGINT_AS_EXCEPTION],
[UnpackOptions::BIGINT_AS_STR | UnpackOptions::BIGINT_AS_GMP | UnpackOptions::BIGINT_AS_EXCEPTION],
[UnpackOptions::BIGINT_AS_STR | UnpackOptions::BIGINT_AS_FLOAT],
[UnpackOptions::BIGINT_AS_GMP | UnpackOptions::BIGINT_AS_FLOAT],
[UnpackOptions::BIGINT_AS_STR | UnpackOptions::BIGINT_AS_GMP | UnpackOptions::BIGINT_AS_FLOAT],
];
}

Expand Down
27 changes: 0 additions & 27 deletions tests/Unit/Exception/IntegerOverflowExceptionTest.php

This file was deleted.

32 changes: 0 additions & 32 deletions tests/Unit/Exception/PackingFailedExceptionTest.php

This file was deleted.

0 comments on commit 4ed09e6

Please sign in to comment.