Simplify serialisation.

This commit is contained in:
flash 2023-07-21 21:47:41 +00:00
parent 64d6bdca1f
commit 4aae77900b
16 changed files with 105 additions and 386 deletions

View file

@ -1 +1 @@
0.2307.212010
0.2307.212147

View file

@ -1,14 +1,14 @@
<?php
// BencodedContent.php
// Created: 2022-02-10
// Updated: 2022-02-27
// Updated: 2023-07-21
namespace Index\Http\Content;
use Stringable;
use Index\IO\Stream;
use Index\IO\FileStream;
use Index\Serialisation\Serialiser;
use Index\Serialisation\Bencode;
use Index\Serialisation\IBencodeSerialisable;
class BencodedContent implements Stringable, IHttpContent, IBencodeSerialisable {
@ -27,7 +27,7 @@ class BencodedContent implements Stringable, IHttpContent, IBencodeSerialisable
}
public function encode(): string {
return Serialiser::bencode()->serialise($this->content);
return Bencode::encode($this->content);
}
public function __toString(): string {
@ -35,7 +35,7 @@ class BencodedContent implements Stringable, IHttpContent, IBencodeSerialisable
}
public static function fromEncoded(Stream|string $encoded): BencodedContent {
return new BencodedContent(Serialiser::bencode()->deserialise($encoded));
return new BencodedContent(Bencode::decode($encoded));
}
public static function fromFile(string $path): BencodedContent {

View file

@ -1,7 +1,7 @@
<?php
// JsonContent.php
// Created: 2022-02-10
// Updated: 2022-02-27
// Updated: 2023-07-21
namespace Index\Http\Content;
@ -9,7 +9,6 @@ use JsonSerializable;
use Stringable;
use Index\IO\Stream;
use Index\IO\FileStream;
use Index\Serialisation\Serialiser;
class JsonContent implements Stringable, IHttpContent, JsonSerializable {
private mixed $content;
@ -27,7 +26,7 @@ class JsonContent implements Stringable, IHttpContent, JsonSerializable {
}
public function encode(): string {
return Serialiser::json()->serialise($this->content);
return json_encode($this->content);
}
public function __toString(): string {
@ -35,7 +34,7 @@ class JsonContent implements Stringable, IHttpContent, JsonSerializable {
}
public static function fromEncoded(Stream|string $encoded): JsonContent {
return new JsonContent(Serialiser::json()->deserialise($encoded));
return new JsonContent(json_decode($encoded));
}
public static function fromFile(string $path): JsonContent {

View file

@ -5,7 +5,7 @@
namespace Index\Security;
use Index\Serialisation\Serialiser;
use Index\Serialisation\UriBase64;
/**
* Contains a mechanism for validating requests.
@ -60,7 +60,7 @@ class CSRFP {
$nonce = random_bytes(self::NONCE_LENGTH);
$hash = $this->createHash($time, $nonce);
return Serialiser::uriBase64()->serialise(
return UriBase64::encode(
pack('V', $time) . $nonce . $hash
);
}
@ -79,7 +79,7 @@ class CSRFP {
if($tolerance < 0)
$tolerance = $this->tolerance;
$token = Serialiser::uriBase64()->deserialise($token);
$token = UriBase64::decode($token);
if(empty($token))
return false;

View file

@ -1,7 +1,7 @@
<?php
// Base32Serialiser.php
// Base32.php
// Created: 2022-01-13
// Updated: 2023-01-01
// Updated: 2023-07-21
namespace Index\Serialisation;
@ -10,16 +10,16 @@ use Index\IO\Stream;
/**
* Provides a Base32 serialiser.
*/
class Base32Serialiser extends Serialiser {
final class Base32 {
private const CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
/**
* Encodes binary data as a Base32 string.
*
* @param mixed $input Input binary data.
* @param Stream|string $input Input binary data.
* @return string Base32 string representing the binary data.
*/
public function serialise(mixed $input): string {
public static function encode(Stream|string $input): string {
$input = (string)$input;
$length = strlen($input);
$bin = '';
@ -44,7 +44,7 @@ class Base32Serialiser extends Serialiser {
* @param Stream|string $input Input Base32 string.
* @return string Binary data.
*/
public function deserialise(Stream|string $input): mixed {
public static function decode(Stream|string $input): string {
$input = (string)$input;
$length = strlen($input);
$char = $shift = 0;

View file

@ -1,7 +1,7 @@
<?php
// Base62Serialiser.php
// Base62.php
// Created: 2022-01-28
// Updated: 2023-01-01
// Updated: 2023-07-21
namespace Index\Serialisation;
@ -10,7 +10,7 @@ use Index\IO\Stream;
/**
* Provides a Base62 serialiser.
*/
class Base62Serialiser extends Serialiser {
final class Base62 {
private const CHARS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
/**
@ -19,8 +19,7 @@ class Base62Serialiser extends Serialiser {
* @param int $input Integer.
* @return string Base64 string representing the integer.
*/
public function serialise(mixed $input): string {
$input = (int)$input;
public static function encode(int $input): string {
$output = '';
for($i = floor(log10($input) / log10(62)); $i >= 0; --$i) {
@ -38,7 +37,7 @@ class Base62Serialiser extends Serialiser {
* @param Stream|string $input Input Base62 string.
* @return int Integer.
*/
public function deserialise(Stream|string $input): mixed {
public static function decode(Stream|string $input): int {
$input = (string)$input;
$output = 0;
$length = strlen($input) - 1;

View file

@ -1,7 +1,7 @@
<?php
// Base64Serialiser.php
// Base64.php
// Created: 2022-01-13
// Updated: 2022-02-27
// Updated: 2023-07-21
namespace Index\Serialisation;
@ -10,14 +10,14 @@ use Index\IO\Stream;
/**
* Provides a Base64 serialiser.
*/
class Base64Serialiser extends Serialiser {
final class Base64 {
/**
* Encodes binary data as a Base64 string.
*
* @param mixed $input Input binary data.
* @param Stream|string $input Input binary data.
* @return string Base64 string representing the binary data.
*/
public function serialise(mixed $input): string {
public static function encode(Stream|string $input): string {
return base64_encode((string)$input);
}
@ -25,9 +25,9 @@ class Base64Serialiser extends Serialiser {
* Decodes a Base64 string back to binary data.
*
* @param Stream|string $input Input Base64 string.
* @return string Binary data.
* @return string|false Binary data.
*/
public function deserialise(Stream|string $input): mixed {
public static function decode(Stream|string $input): string|bool {
return base64_decode((string)$input);
}
}

View file

@ -1,7 +1,7 @@
<?php
// BencodeSerialiser.php
// Bencode.php
// Created: 2022-01-13
// Updated: 2023-01-01
// Updated: 2023-07-21
namespace Index\Serialisation;
@ -12,29 +12,10 @@ use Index\IO\TempFileStream;
/**
* Provides a Bencode serialiser.
*/
class BencodeSerialiser extends Serialiser {
private BencodeSerialiserSettings $settings;
final class Bencode {
public const DEFAULT_DEPTH = 512;
/**
* Creates a new Bencode serialiser.
*
* @param BencodeSerialiserSettings|null $settings Settings for the serialisation behaviour.
*/
public function __construct(?BencodeSerialiserSettings $settings = null) {
$this->settings = $settings ?? new BencodeSerialiserSettings;
}
public function serialise(mixed $input): string {
return self::encode((string)$input, $this->settings->getMaxDepth());
}
public function deserialise(Stream|string $input): mixed {
if(!($input instanceof Stream))
$input = TempFileStream::fromString($input);
return $this->decode($input, $this->settings->getMaxDepth());
}
private static function encode(mixed $input, int $depth): string {
public static function encode(mixed $input, int $depth = self::DEFAULT_DEPTH): string {
if($depth < 1)
throw new RuntimeException('Maximum depth reached, structure is too dense.');
@ -79,7 +60,12 @@ class BencodeSerialiser extends Serialiser {
}
}
private function decode(Stream $input, int $depth): mixed {
public static function decode(Stream|string $input, int $depth = self::DEFAULT_DEPTH, bool $dictAsObject = false): mixed {
if(!($input instanceof Stream)) {
$input = TempFileStream::fromString($input);
$input->seek(0);
}
if($depth < 1)
throw new RuntimeException('Maximum depth reached, structure is too dense.');
@ -120,7 +106,7 @@ class BencodeSerialiser extends Serialiser {
if($char === 'e')
break;
$input->seek(-1, Stream::CURRENT);
$list[] = $this->decode($input, $depth - 1);
$list[] = self::decode($input, $depth - 1, $dictAsObject);
}
return $list;
@ -137,10 +123,10 @@ class BencodeSerialiser extends Serialiser {
if(!ctype_digit($char))
throw new RuntimeException('Unexpected dictionary key type, expected a string.');
$input->seek(-1, Stream::CURRENT);
$dict[$this->decode($input, $depth - 1)] = $this->decode($input, $depth - 1);
$dict[self::decode($input, $depth - 1, $dictAsObject)] = self::decode($input, $depth - 1, $dictAsObject);
}
if($this->settings->shouldDecodeDictAsObject())
if($dictAsObject)
$dict = (object)$dict;
return $dict;

View file

@ -1,48 +0,0 @@
<?php
// BencodeSerialiserSettings.php
// Created: 2022-01-13
// Updated: 2022-02-02
namespace Index\Serialisation;
/**
* Provides settings for Bencode serialisation.
*/
class BencodeSerialiserSettings {
public const DEFAULT_DEPTH = 512;
private int $maxDepth;
private bool $dictAsObject;
/**
* Creates a new BencodeSerialiserSettings instance.
*
* @param int $maxDepth Maximum parsing depth before the serialisation/deserialisation should stop.
* @param bool $dictAsObject Whether Dictionaries should be decoded as stdClass instances rather than associative arrays.
*/
public function __construct(
int $maxDepth = self::DEFAULT_DEPTH,
bool $dictAsObject = false
) {
$this->maxDepth = $maxDepth;
$this->dictAsObject = $dictAsObject;
}
/**
* Gets the maximum recursion depth.
*
* @return int Maximum recursion depth.
*/
public function getMaxDepth(): int {
return $this->maxDepth;
}
/**
* Gets whether dictionaries should be decoded as objects (instances of stdClass).
*
* @return bool True if dictionaries should be objects, false if they should be associative arrays.
*/
public function shouldDecodeDictAsObject(): bool {
return $this->dictAsObject;
}
}

View file

@ -1,38 +0,0 @@
<?php
// JsonSerialiser.php
// Created: 2022-01-13
// Updated: 2022-02-27
namespace Index\Serialisation;
use Index\WString;
use Index\IO\Stream;
/**
* Provides a JSON serialiser.
*/
class JsonSerialiser extends Serialiser {
private JsonSerialiserSettings $settings;
/**
* Creates a new JSON serialiser instance.
*
* @param JsonSerialiserSettings|null $settings Settings to be used with this serialiser.
*/
public function __construct(?JsonSerialiserSettings $settings = null) {
$this->settings = $settings ?? new JsonSerialiserSettings;
}
public function serialise(mixed $input): string {
$output = json_encode($input, $this->settings->getFlags(), $this->settings->getMaxDepth());
if($output === false)
$output = '';
return $output;
}
public function deserialise(Stream|string $input): mixed {
//if($input instanceof WString)
// $input = $input->convertEncoding('utf-8');
return json_decode((string)$input, false, $this->settings->getMaxDepth(), $this->settings->getFlags());
}
}

View file

@ -1,116 +0,0 @@
<?php
// JsonSerialiserSettings.php
// Created: 2022-01-13
// Updated: 2022-02-02
namespace Index\Serialisation;
/**
* Provides settings for altering JSON serialisation behaviour.
*/
class JsonSerialiserSettings {
public const DEFAULT_DEPTH = 512;
private int $flags;
private int $maxDepth;
/**
* Creates a new instance of JsonSerialiserSettings.
*
* @param int $maxDepth Maximum parsing depth before the serialisation/deserialisation should stop.
* @param bool $throwOnError Whether the serialiser should throw an exeception upon errors.
* @param bool $preserveZeroFraction Ensure that float values are never encoded as integers.
* @param bool $prettyPrint Whether the output should be formatted.
* @param bool $forceObject Output an object (stdClass instance) instead of an associative array.
* @param bool $encodeQuotes Converts " to \u0022.
* @param bool $convertTags Converts < and > to \u003C and \u003E respectively.
* @param bool $convertAmpersand Converts & to \u0026.
* @param bool $convertApostrophe Converts ' to \u0027.
* @param bool $ignoreInvalidUtf8 Ignores invalid UTF-8 characters.
* @param bool $substituteInvalidUtf8 Converts invalid UTF-8 characters to \0xfffd (Unicode Character 'REPLACEMENT CHARACTER').
* @param bool $numericCheck Encodes numeric strings as numbers.
* @param bool $partialOutputOnError Substitute some unencodable values instead of failing.
* @param bool $unescapedLineTerminators The line terminators are kept unescaped when $unescapedUnicode is true.
* @param bool $unescapedSlashes Don't escape /.
* @param bool $unescapedUnicode Encode multibyte Unicode characters literally (default is to escape as \uXXXX).
* @param bool $bigIntAsString Decodes large integers as their original string value.
* @param bool $objectAsArray Decodes JSON objects as PHP array.
*/
public function __construct(
int $maxDepth = self::DEFAULT_DEPTH,
bool $throwOnError = true,
bool $preserveZeroFraction = true,
bool $prettyPrint = false,
bool $forceObject = false,
bool $encodeQuotes = false,
bool $convertTags = false,
bool $convertAmpersand = false,
bool $convertApostrophe = false,
bool $ignoreInvalidUtf8 = false,
bool $substituteInvalidUtf8 = false,
bool $numericCheck = false,
bool $partialOutputOnError = false,
bool $unescapedLineTerminators = false,
bool $unescapedSlashes = false,
bool $unescapedUnicode = false,
bool $bigIntAsString = false,
bool $objectAsArray = false
) {
$this->maxDepth = $maxDepth;
$flags = 0;
if($throwOnError)
$flags |= JSON_THROW_ON_ERROR;
if($preserveZeroFraction)
$flags |= JSON_PRESERVE_ZERO_FRACTION;
if($prettyPrint)
$flags |= JSON_PRETTY_PRINT;
if($forceObject)
$flags |= JSON_FORCE_OBJECT;
if($encodeQuotes)
$flags |= JSON_HEX_QUOT;
if($convertTags)
$flags |= JSON_HEX_TAG;
if($convertAmpersand)
$flags |= JSON_HEX_AMP;
if($convertApostrophe)
$flags |= JSON_HEX_APOS;
if($ignoreInvalidUtf8)
$flags |= JSON_INVALID_UTF8_IGNORE;
if($substituteInvalidUtf8)
$flags |= JSON_INVALID_UTF8_SUBSTITUTE;
if($numericCheck)
$flags |= JSON_NUMERIC_CHECK;
if($partialOutputOnError)
$flags |= JSON_PARTIAL_OUTPUT_ON_ERROR;
if($unescapedLineTerminators)
$flags |= JSON_UNESCAPED_LINE_TERMINATORS;
if($unescapedSlashes)
$flags |= JSON_UNESCAPED_SLASHES;
if($unescapedUnicode)
$flags |= JSON_UNESCAPED_UNICODE;
if($bigIntAsString)
$flags |= JSON_BIGINT_AS_STRING;
if($objectAsArray)
$flags |= JSON_OBJECT_AS_ARRAY;
$this->flags = $flags;
}
/**
* Gets the maximum recursion depth.
*
* @return int Maximum recursion depth.
*/
public function getMaxDepth(): int {
return $this->maxDepth;
}
/**
* Gets the flagset constructed by the constructor arguments.
*
* @return int JSON flagset.
*/
public function getFlags(): int {
return $this->flags;
}
}

View file

@ -1,111 +0,0 @@
<?php
// Serialiser.php
// Created: 2022-01-13
// Updated: 2022-02-27
namespace Index\Serialisation;
use Index\IO\Stream;
/**
* Base class for Serialisers.
*/
abstract class Serialiser {
/**
* Returns a global Base32Serialiser instance with default settings.
*
* @return Base32Serialiser
*/
public static function base32(): Base32Serialiser {
static $instance = null;
if($instance === null)
$instance = new Base32Serialiser;
return $instance;
}
/**
* Returns a global Base62Serialiser instance with default settings.
*
* @return Base62Serialiser
*/
public static function base62(): Base62Serialiser {
static $instance = null;
if($instance === null)
$instance = new Base62Serialiser;
return $instance;
}
/**
* Returns a global Base64Serialiser instance with default settings.
*
* @return Base64Serialiser
*/
public static function base64(): Base64Serialiser {
static $instance = null;
if($instance === null)
$instance = new Base64Serialiser;
return $instance;
}
/**
* Returns a global UriBase64Serialiser instance with default settings.
*
* @return UriBase64Serialiser
*/
public static function uriBase64(): UriBase64Serialiser {
static $instance = null;
if($instance === null)
$instance = new UriBase64Serialiser;
return $instance;
}
/**
* Returns a global JsonSerialiser instance with default settings.
*
* @return JsonSerialiser
*/
public static function json(): JsonSerialiser {
static $instance = null;
if($instance === null)
$instance = new JsonSerialiser;
return $instance;
}
/**
* Returns a global BencodeSerialiser instance with default settings.
*
* @return BencodeSerialiser
*/
public static function bencode(): BencodeSerialiser {
static $instance = null;
if($instance === null)
$instance = new BencodeSerialiser;
return $instance;
}
/**
* Serialises data to a stringable format.
*
* @param mixed $input Data to be serialised.
* @return string Serialised representation of the input data.
*/
abstract public function serialise(mixed $input): string;
/**
* Deserialises a stringable format into data.
*
* @param Stream|string $input String or stream to be deserialised.
* @return mixed Deserialised data of the input string.
*/
abstract public function deserialise(Stream|string $input): mixed;
/**
* Serialises data to a stringable format into stream.
*
* @param mixed $input Data to be serialised.
* @param Stream $output Target stream.
*/
public function serialiseToStream(mixed $input, Stream $output): void {
$output->write($this->serialise($input));
}
}

View file

@ -1,7 +1,7 @@
<?php
// UriBase64Serialiser.php
// UriBase64.php
// Created: 2022-01-13
// Updated: 2022-02-27
// Updated: 2023-07-21
namespace Index\Serialisation;
@ -10,14 +10,14 @@ use Index\IO\Stream;
/**
* Provides URL-safe Base64 encoding.
*/
class UriBase64Serialiser extends Serialiser {
final class UriBase64 {
/**
* Encodes binary data as a Base64 string.
*
* @param string $input Input binary data.
* @return string Base64 string representing the binary data.
*/
public function serialise(mixed $input): string {
public static function encode(mixed $input): string {
return rtrim(strtr(base64_encode((string)$input), '+/', '-_'), '=');
}
@ -27,7 +27,7 @@ class UriBase64Serialiser extends Serialiser {
* @param Stream|string $input Input Base64 string.
* @return string Binary data.
*/
public function deserialise(Stream|string $input): mixed {
public static function decode(Stream|string $input): mixed {
$input = (string)$input;
return base64_decode(str_pad(strtr($input, '-_', '+/'), strlen($input) % 4, '=', STR_PAD_RIGHT));
}

View file

@ -1,12 +1,12 @@
<?php
// Base32Test.php
// Created: 2021-04-28
// Updated: 2021-04-28
// Updated: 2023-07-21
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
use Index\Serialisation\Serialiser;
use Index\Serialisation\Base32;
/**
* @covers Base32
@ -22,11 +22,11 @@ final class Base32Test extends TestCase {
public function testDecode(): void {
foreach(self::TESTS as $value => $expected)
$this->assertEquals($expected, (string)Serialiser::base32()->deserialise($value));
$this->assertEquals($expected, Base32::decode($value));
}
public function testEncode(): void {
foreach(self::TESTS as $expected => $value)
$this->assertEquals($expected, (string)Serialiser::base32()->serialise($value));
$this->assertEquals($expected, Base32::encode($value));
}
}

View file

@ -1,15 +1,15 @@
<?php
// Base62Test.php
// Created: 2022-01-28
// Updated: 2022-02-03
// Updated: 2023-07-21
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
use Index\Serialisation\Serialiser;
use Index\Serialisation\Base62;
/**
* @covers Base62Serialiser
* @covers Base62
*/
final class Base62Test extends TestCase {
public const TESTS = [
@ -26,11 +26,11 @@ final class Base62Test extends TestCase {
public function testDecode(): void {
foreach(self::TESTS as $test)
$this->assertEquals($test[1], Serialiser::base62()->deserialise($test[0]));
$this->assertEquals($test[1], Base62::decode($test[0]));
}
public function testEncode(): void {
foreach(self::TESTS as $test)
$this->assertEquals($test[0], (string)Serialiser::base62()->serialise($test[1]));
$this->assertEquals($test[0], Base62::encode($test[1]));
}
}

48
tests/BencodeTest.php Normal file
View file

@ -0,0 +1,48 @@
<?php
// BencodeTest.php
// Created: 2023-07-21
// Updated: 2023-07-21
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
use Index\Serialisation\Bencode;
/**
* @covers Bencode
*/
final class BencodeTest extends TestCase {
public const TORRENT = 'ZDg6YW5ub3VuY2U0NTpodHRwczovL3RyYWNrZXIuZmxhc2hpaS5uZXQvYW5ub3VuY2UucGhwL21lb3c3OmNvbW1lbnQyNjp0aGlzIGlzIHRoZSBjb21tZW50cyBmaWVsZDEwOmNyZWF0ZWQgYnkxODpxQml0dG9ycmVudCB2NC41LjQxMzpjcmVhdGlvbiBkYXRlaTE2ODk5NzM2NjRlNDppbmZvZDY6bGVuZ3RoaTM5NDg4ZTQ6bmFtZTEwOmF1dGlzbS5qcGcxMjpwaWVjZSBsZW5ndGhpMTYzODRlNjpwaWVjZXM2MDpJCQyONUjTseqtXs9g1xIC+yWtTGjuWTho+hSXmOUkSAiKnD4vs5E9QewQ1NrXvVeg2xCRHYD/HL9RNRc3OnByaXZhdGVpMWVlZQ';
public function testDecode(): void {
$decoded = Bencode::decode(base64_decode(self::TORRENT));
$this->assertEquals(5, count($decoded));
$this->assertArrayHasKey('announce', $decoded);
$this->assertEquals('https://tracker.flashii.net/announce.php/meow', $decoded['announce']);
$this->assertArrayHasKey('creation date', $decoded);
$this->assertEquals(1689973664, $decoded['creation date']);
$this->assertArrayHasKey('info', $decoded);
$this->assertArrayHasKey('private', $decoded['info']);
$this->assertEquals(1, $decoded['info']['private']);
$decoded = Bencode::decode(base64_decode(self::TORRENT), dictAsObject: true);
$this->assertEquals('https://tracker.flashii.net/announce.php/meow', $decoded->announce);
$this->assertEquals(1689973664, $decoded->{'creation date'});
$this->assertEquals('this is the comments field', $decoded->comment);
$this->assertEquals(1, $decoded->info->private);
}
public function testEncode(): void {
$original = base64_decode(self::TORRENT);
$decoded = Bencode::decode($original);
$encoded = Bencode::encode($decoded);
$this->assertEquals($original, $encoded);
}
}