HEX
Server: LiteSpeed
System: Linux d8 4.18.0-553.121.1.lve.el8.x86_64 #1 SMP Thu Apr 30 16:40:41 UTC 2026 x86_64
User: wbwebdes (3015)
PHP: 8.1.31
Disabled: exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname
Upload Files
File: /home/wbwebdes/domains/files.wb-cloud.nl/private_html/apps/text/lib/YjsMessage.php
<?php

/**
 * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
 * SPDX-License-Identifier: AGPL-3.0-or-later
 */

namespace OCA\Text;

use InvalidArgumentException;

/**
 * Steps are base64 encoded messages of the yjs protocols
 * https://github.com/yjs/y-protocols
 *
 * This class is a simple representation of a message containing some methods
 * to decode parts of it for what we need on the backend
 *
 * Relevant resources:
 * https://github.com/yjs/y-protocols/blob/master/PROTOCOL.md
 * https://github.com/yjs/y-websocket/blob/master/src/y-websocket.js#L19-L22
 * https://github.com/yjs/y-protocols/blob/master/sync.js#L38-L40
 * https://github.com/dmonad/lib0/blob/master/decoding.js
 */
class YjsMessage {

	public const YJS_MESSAGE_SYNC = 0;
	public const YJS_MESSAGE_AWARENESS = 1;
	public const YJS_MESSAGE_AWARENESS_QUERY = 3;

	public const YJS_MESSAGE_SYNC_STEP1 = 0;
	public const YJS_MESSAGE_SYNC_STEP2 = 1;
	public const YJS_MESSAGE_SYNC_UPDATE = 2;

	private int $pos = 0;

	public function __construct(
		private string $data = '',
	) {
	}

	public static function fromBase64(string $data = ''): self {
		return new self(base64_decode($data));
	}

	/**
	 * https://github.com/dmonad/lib0/blob/bd69ab4dc701d77e808f2bab08d96d63acd297da/decoding.js#L242
	 */
	public function readVarUint(): int {
		$values = unpack('C*', $this->data);
		$bytes = array_values($values !== false ? $values : []);
		$num = 0;
		$mult = 1;
		$len = count($bytes);
		while ($this->pos < $len) {
			$r = $bytes[$this->pos++];
			// num = num | ((r & binary.BITS7) << len)
			$num = $num + ($r & 0b1111111) * $mult;
			$mult *= 128;
			if ($r <= 0b1111111) {
				return $num;
			}
			// Number.MAX_SAFE_INTEGER in JS
			if ($num > 9007199254740990) {
				throw new \OutOfBoundsException();
			}
		}
		throw new InvalidArgumentException();
	}

	public function getYjsMessageType(): int {
		$oldPos = $this->pos;
		$this->pos = 0;
		$messageType = $this->readVarUint();
		$this->pos = $oldPos;
		return $messageType;
	}

	public function getYjsSyncType(): int {
		$oldPos = $this->pos;
		$this->pos = 0;
		$messageType = $this->readVarUint();
		if ($messageType !== self::YJS_MESSAGE_SYNC) {
			throw new \ValueError('Message is not a sync message');
		}
		$syncType = $this->readVarUint();
		$this->pos = $oldPos;
		return $syncType;
	}

	/**
	 * Based on https://github.com/yjs/y-protocols/blob/master/PROTOCOL.md#handling-read-only-users
	 */
	public function isUpdate(): bool {
		if ($this->getYjsMessageType() === self::YJS_MESSAGE_SYNC) {
			if (in_array($this->getYjsSyncType(), [self::YJS_MESSAGE_SYNC_STEP2, self::YJS_MESSAGE_SYNC_UPDATE])) {
				return true;
			}
		}

		return false;
	}

}