base = new GzipNative($level); } /** * Serialize a persistable object and return the data. * * @internal * * @param Persistable $persistable * @return Encoding */ public function serialize(Persistable $persistable) : Encoding { $encoding = $this->base->serialize($persistable); $hash = hash(self::CHECKSUM_HASH_TYPE, $encoding); $header = JSON::encode([ 'library' => [ 'version' => LIBRARY_VERSION, ], 'class' => [ 'name' => get_class($persistable), 'revision' => $persistable->revision(), ], 'data' => [ 'checksum' => [ 'type' => self::CHECKSUM_HASH_TYPE, 'hash' => $hash, ], 'length' => $encoding->bytes(), ], ]); $hash = hash(self::CHECKSUM_HASH_TYPE, $header); $checksum = self::CHECKSUM_HASH_TYPE . ':' . $hash; $data = self::IDENTIFIER_STRING; $data .= self::VERSION . self::EOL; $data .= $checksum . self::EOL; $data .= $header . self::EOL; $data .= $encoding; return new Encoding($data); } /** * Deserialize a persistable object and return it. * * @internal * * @param Encoding $encoding * @throws RuntimeException * @return Persistable */ public function deserialize(Encoding $encoding) : Persistable { if (strpos($encoding, self::IDENTIFIER_STRING) !== 0) { throw new RuntimeException('Unrecognized message format.'); } $data = substr($encoding, strlen(self::IDENTIFIER_STRING)); [$version, $checksum, $header, $payload] = array_pad(explode(self::EOL, $data, 4), 4, null); if (!$version or !$checksum or !$header or !$payload) { throw new RuntimeException('Invalid message format.'); } if ($version != self::VERSION) { throw new RuntimeException("Incompatible with RBX version $version."); } [$type, $hash] = array_pad(explode(':', $checksum, 2), 2, null); if ($hash !== hash($type, $header)) { throw new RuntimeException('Header checksum verification failed.'); } $header = JSON::decode($header); if (strlen($payload) !== $header['data']['length']) { throw new RuntimeException('Data is corrupted.'); } $hash = hash($header['data']['checksum']['type'], $payload); if ($header['data']['checksum']['hash'] !== $hash) { throw new RuntimeException('Data checksum verification failed.'); } $persistable = $this->base->deserialize(new Encoding($payload)); if (get_class($persistable) !== $header['class']['name']) { throw new RuntimeException('Class name mismatch.'); } if ($persistable->revision() !== $header['class']['revision']) { throw new ClassRevisionMismatch($header['library']['version']); } return $persistable; } /** * Return the string representation of the object. * * @internal * * @return string */ public function __toString() : string { return "RBX (level: {$this->base->level()})"; } }