dataLength = strlen($binaryData); } /** * Returns the length of the ByteBuffer data. * * @return int */ public function getDataLength(): int { return $this->dataLength; } /** * Check if the length of the data is greater than zero. * * @return bool */ public function hasLength(): bool { return (bool) $this->dataLength; } /** * Check if the length of the data is zero. * * @return bool */ public function hasNoLength(): bool { return !$this->hasLength(); } /** * Returns the binary string verbatim. * * @return string */ public function getBinaryString(): string { return $this->binaryData; } /** * Check if both Byte Buffers are equal using `hash_equals`. * * @param \Laragear\WebAuthn\ByteBuffer|string $buffer * @return bool */ public function hashEqual(self|string $buffer): bool { if ($buffer instanceof static) { $buffer = $buffer->getBinaryString(); } return hash_equals($this->binaryData, $buffer); } /** * Check if both Byte Buffers are not equal using `hash_equals`. * * @param \Laragear\WebAuthn\ByteBuffer|string $buffer * @return bool */ public function hashNotEqual(self|string $buffer): bool { return ! $this->hashEqual($buffer); } /** * Returns a certain portion of these bytes. * * @param int $offset * @param int|null $length * @return string */ public function getBytes(int $offset = 0, int $length = null): string { $length ??= $this->dataLength; if ($offset < 0 || $length < 0 || ($offset + $length > $this->dataLength)) { throw new InvalidArgumentException('ByteBuffer: Invalid offset or length.'); } return substr($this->binaryData, $offset, $length); } /** * Returns the value of a single byte. * * @param int $offset * @return int */ public function getByteVal(int $offset = 0): int { if (!$byte = $this->binaryData[$offset] ?? null) { throw new InvalidArgumentException('ByteBuffer: Invalid offset'); } return ord($byte); } /** * Returns the value of a single unsigned 16-bit integer. * * @param int $offset * @return int */ public function getUint16Val(int $offset = 0): int { if ($offset < 0 || ($offset + 2) > $this->dataLength) { throw new InvalidArgumentException('ByteBuffer: Invalid offset'); } return unpack('n', $this->binaryData, $offset)[1]; } /** * Returns the value of a single unsigned 32-bit integer. * * @param int $offset * @return int */ public function getUint32Val(int $offset = 0): int { if ($offset < 0 || ($offset + 4) > $this->dataLength) { throw new InvalidArgumentException('ByteBuffer: Invalid offset'); } $val = unpack('N', $this->binaryData, $offset)[1]; // Signed integer overflow causes signed negative numbers if ($val < 0) { throw new OutOfBoundsException('ByteBuffer: Value out of integer range.'); } return $val; } /** * Returns the value of a single unsigned 64-bit integer. * * @param int $offset * @return int */ public function getUint64Val(int $offset): int { if (PHP_INT_SIZE < 8) { throw new OutOfBoundsException('ByteBuffer: 64-bit values not supported by this system'); } if ($offset < 0 || ($offset + 8) > $this->dataLength) { throw new InvalidArgumentException('ByteBuffer: Invalid offset'); } $val = unpack('J', $this->binaryData, $offset)[1]; // Signed integer overflow causes signed negative numbers if ($val < 0) { throw new OutOfBoundsException('ByteBuffer: Value out of integer range.'); } return $val; } /** * Returns the value of a single 16-bit float. * * @param int $offset * @return float */ public function getHalfFloatVal(int $offset = 0): float { // FROM spec pseudo decode_half(unsigned char *halfp) $half = $this->getUint16Val($offset); $exp = ($half >> 10) & 0x1f; $mant = $half & 0x3ff; if ($exp === 0) { $val = $mant * (2 ** -24); } elseif ($exp !== 31) { $val = ($mant + 1024) * (2 ** ($exp - 25)); } else { $val = ($mant === 0) ? INF : NAN; } return ($half & 0x8000) ? -$val : $val; } /** * Returns the value of a single 32-bit float. * * @param int $offset * @return float */ public function getFloatVal(int $offset = 0): float { if ($offset < 0 || ($offset + 4) > $this->dataLength) { throw new InvalidArgumentException('ByteBuffer: Invalid offset'); } return unpack('G', $this->binaryData, $offset)[1]; } /** * Returns the value of a single 64-bit float. * * @param int $offset * @return float */ public function getDoubleVal(int $offset = 0): float { if ($offset < 0 || ($offset + 8) > $this->dataLength) { throw new InvalidArgumentException('ByteBuffer: Invalid offset'); } return unpack('E', $this->binaryData, $offset)[1]; } /** * Transforms the ByteBuffer JSON into a generic Object. * * @param int $jsonFlags * @return object * @throws \JsonException */ public function toObject(int $jsonFlags = 0): object { return json_decode($this->binaryData, null, 512, JSON_THROW_ON_ERROR | $jsonFlags); } /** * Returns a Base64 URL representation of the byte buffer. * * @return string */ public function toBase64Url(): string { return static::encodeBase64Url($this->binaryData); } /** * Specify data which should be serialized to JSON. * * @return string */ public function jsonSerialize(): string { return $this->toBase64Url(); } /** * Returns a hexadecimal representation of the ByteBuffer. * * @return string */ public function toHex(): string { return bin2hex($this->binaryData); } /** * object to string * * @return string */ public function __toString(): string { return $this->toHex(); } /** * Convert the object to its JSON representation. * * @param int $options * @return string */ public function toJson($options = 0): string { return $this->jsonSerialize(); } /** * Returns an array of data for serialization. * * @return array */ #[ArrayShape(['binaryData' => "string"])] public function __serialize(): array { return ['binaryData' => static::encodeBase64Url($this->binaryData)]; } /** * Serializable-Interface * * @param array $data */ public function __unserialize(array $data): void { $this->binaryData = static::decodeBase64Url($data['binaryData']); $this->dataLength = strlen($this->binaryData); } /** * Create a ByteBuffer from a BASE64 URL encoded string. * * @param string $base64url * @return static */ public static function fromBase64Url(string $base64url): static { if (false === $bin = self::decodeBase64Url($base64url)) { throw new InvalidArgumentException('ByteBuffer: Invalid base64 url string'); } return new static($bin); } /** * Create a ByteBuffer from a BASE64 encoded string. * * @param string $base64 * @return static */ public static function fromBase64(string $base64): static { /** @var string|false $bin */ $bin = base64_decode($base64); if (false === $bin) { throw new InvalidArgumentException('ByteBuffer: Invalid base64 string'); } return new static($bin); } /** * Create a ByteBuffer from a hexadecimal string. * * @param string $hex * @return static */ public static function fromHex(string $hex): static { if (false === $bin = hex2bin($hex)) { throw new InvalidArgumentException('ByteBuffer: Invalid hex string'); } return new static($bin); } /** * Create a random ByteBuffer * * @param int $length * @return static */ public static function makeRandom(int $length): static { return new static(random_bytes($length)); } /** * Decodes a BASE64 URL string. * * @param string $data * @return string|false */ protected static function decodeBase64Url(string $data): string|false { return base64_decode(strtr($data, '-_', '+/').str_repeat('=', 3 - (3 + strlen($data)) % 4)); } /** * Encodes a BASE64 URL string. * * @param string $data * @return string|false */ protected static function encodeBase64Url(string $data): string|false { return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); } }