[Encoding] introduce encoding component

This commit is contained in:
azjezz 2020-09-27 11:24:24 +01:00
parent 883141b2a4
commit fa55601439
13 changed files with 243 additions and 0 deletions

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Psl\Encoding\Base64;
use Psl\Encoding\Exception;
use function base64_decode;
use function preg_match;
/**
* Decode a base64-encoded string into raw binary.
*
* Base64 character set:
* [A-Z] [a-z] [0-9] + /
* 0x41-0x5a, 0x61-0x7a, 0x30-0x39, 0x2b, 0x2f
*
* @psalm-pure
*
* @throws Exception\RangeException If the encoded string contains characters outside
* the base64 characters range.
*/
function decode(string $base64): string
{
if (!preg_match('%^[a-zA-Z0-9/+]*={0,2}$%', $base64)) {
throw new Exception\RangeException(
'The given base64 string contains characters outside the base64 range.'
);
}
return (string) @base64_decode($base64, true);
}

View File

@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace Psl\Encoding\Base64;
use Psl\Str;
use function base64_encode;
use function pack;
use function unpack;
/**
* Convert a binary string into a base64-encoded string.
*
* Base64 character set:
* [A-Z] [a-z] [0-9] + /
* 0x41-0x5a, 0x61-0x7a, 0x30-0x39, 0x2b, 0x2f
*
* @psalm-pure
*/
function encode(string $binary): string
{
return base64_encode($binary);
}

View File

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Psl\Encoding\Exception;
use Psl\Exception;
interface ExceptionInterface extends Exception\ExceptionInterface
{
}

View File

@ -0,0 +1,9 @@
<?php
namespace Psl\Encoding\Exception;
use Psl\Exception;
final class IncorrectPaddingException extends Exception\InvalidArgumentException implements ExceptionInterface
{
}

View File

@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Psl\Encoding\Exception;
final class RangeException extends \RangeException implements ExceptionInterface
{
}

View File

@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Psl\Encoding\Hex;
use Psl\Encoding\Exception;
use Psl\Str;
/**
* Convert a hexadecimal string into a binary string.
*
* Hex ( Base16 ) character set:
* [0-9] [a-f] [A-F]
* 0x30-0x39, 0x61-0x66, 0x41-0x46
*
* @psalm-pure
*
* @throws Exception\RangeException If the hexadecimal string contains characters outside the base16 range,
* or an odd number of characters.
*/
function decode(string $hexadecimal): string
{
if (!ctype_xdigit($hexadecimal)) {
throw new Exception\RangeException(
'The given hexadecimal string contains characters outside the base16 range.'
);
}
/** @psalm-suppress MissingThrowsDocblock */
$hex_len = Str\length($hexadecimal, '8bit');
if (($hex_len & 1) !== 0) {
throw new Exception\RangeException(
'Expected an even number of hexadecimal characters.',
);
}
return hex2bin($hexadecimal);
}

View File

@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace Psl\Encoding\Hex;
/**
* Convert a binary string into a hexadecimal string.
*
* Hex ( Base16 ) character set:
* [0-9] [a-f]
* 0x30-0x39, 0x61-0x66
*
* @psalm-pure
*/
function encode(string $binary): string
{
return bin2hex($binary);
}

View File

@ -323,6 +323,10 @@ final class Loader
'Psl\Password\hash', 'Psl\Password\hash',
'Psl\Password\needs_rehash', 'Psl\Password\needs_rehash',
'Psl\Password\verify', 'Psl\Password\verify',
'Psl\Encoding\Base64\encode',
'Psl\Encoding\Base64\decode',
'Psl\Encoding\Hex\encode',
'Psl\Encoding\Hex\decode',
]; ];
public const INTERFACES = [ public const INTERFACES = [
@ -340,6 +344,7 @@ final class Loader
'Psl\Observer\SubjectInterface', 'Psl\Observer\SubjectInterface',
'Psl\Observer\ObserverInterface', 'Psl\Observer\ObserverInterface',
'Psl\Result\ResultInterface', 'Psl\Result\ResultInterface',
'Psl\Encoding\Exception\ExceptionInterface',
]; ];
public const TRAITS = [ public const TRAITS = [
@ -379,6 +384,8 @@ final class Loader
'Psl\Type\Type', 'Psl\Type\Type',
'Psl\Json\Exception\DecodeException', 'Psl\Json\Exception\DecodeException',
'Psl\Json\Exception\EncodeException', 'Psl\Json\Exception\EncodeException',
'Psl\Encoding\Exception\IncorrectPaddingException',
'Psl\Encoding\Exception\RangeException',
]; ];
private const TYPE_CONSTANTS = 1; private const TYPE_CONSTANTS = 1;

View File

@ -13,6 +13,8 @@ use Psl\Iter;
* @psalm-param (callable(): \Generator<Tk, Tv, mixed, mixed>) $fun * @psalm-param (callable(): \Generator<Tk, Tv, mixed, mixed>) $fun
* *
* @psalm-return Iter\Iterator<Tk, Tv> * @psalm-return Iter\Iterator<Tk, Tv>
*
* @internal
*/ */
function lazy_iterator(callable $fun): Iter\Iterator function lazy_iterator(callable $fun): Iter\Iterator
{ {

View File

@ -15,6 +15,8 @@ use Psl;
* @codeCoverageIgnore * @codeCoverageIgnore
* *
* @throws Psl\Exception\InvariantViolationException If the offset is out-of-bounds. * @throws Psl\Exception\InvariantViolationException If the offset is out-of-bounds.
*
* @internal
*/ */
function validate_offset(int $offset, int $length): int function validate_offset(int $offset, int $length): int
{ {

View File

@ -13,6 +13,8 @@ use Psl;
* @psalm-pure * @psalm-pure
* *
* @throws Psl\Exception\InvariantViolationException If $offset is out-of-bounds. * @throws Psl\Exception\InvariantViolationException If $offset is out-of-bounds.
*
* @internal
*/ */
function validate_offset_lower_bound(int $offset, int $length): int function validate_offset_lower_bound(int $offset, int $length): int
{ {

View File

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Psl\Tests\Encoding;
use PHPUnit\Framework\TestCase;
use Psl\Encoding\Base64;
use Psl\Encoding\Exception;
use Psl\SecureRandom;
final class Base64Test extends TestCase
{
/**
* @dataProvider provideRandomBytes
*/
public function testEncodeAndDecode(string $random): void
{
$encoded = Base64\encode($random);
self::assertSame($random, Base64\decode($encoded));
}
public function testDecodeThrowsForCharactersOutsideTheBase64Range(): void
{
$this->expectException(Exception\RangeException::class);
Base64\decode('@~==');
}
public function provideRandomBytes(): iterable
{
for ($i = 1; $i < 128; ++$i) {
yield [SecureRandom\bytes($i)];
}
}
}

View File

@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace Psl\Tests\Encoding;
use PHPUnit\Framework\TestCase;
use Psl\Encoding\Hex;
use Psl\Encoding\Exception;
use Psl\SecureRandom;
use Psl\Str;
use function bin2hex;
final class HexTest extends TestCase
{
/**
* @dataProvider provideRandomBytes
*/
public function testRandom(string $random): void
{
$enc = Hex\encode($random);
self::assertSame($random, Hex\decode($enc));
self::assertSame(bin2hex($random), $enc);
$enc = Hex\encode($random);
self::assertSame($random, Hex\decode($enc));
}
public function testDecodeThrowsForCharactersOutsideTheHexRange(): void
{
$this->expectException(Exception\RangeException::class);
Hex\decode('gf');
}
public function testDecodeThrowsForAnOddNumberOfCharacters(): void
{
$this->expectException(Exception\RangeException::class);
Hex\decode('f');
}
public function provideRandomBytes(): iterable
{
for ($i = 1; $i < 128; ++$i) {
yield [SecureRandom\bytes($i)];
}
}
}