Skip to content
Advertisement

How this function should be properly ported from PHP to Javascript

I am working on a project where I need an encoding that I can only get from a PHP project:

public static function writeVarLong(int $v) : string{
    return self::writeUnsignedVarLong(($v << 1) ^ ($v >> 63));
}

public static function writeUnsignedVarLong(int $value) : string{
    $buf = "";
    for($i = 0; $i < 10; ++$i){
        if(($value >> 7) !== 0){
            $buf .= chr($value | 0x80); //Let chr() take the last byte of this, it's faster than adding another & 0x7f.
        }else{
            $buf .= chr($value & 0x7f);
            return $buf;
        }

        $value = (($value >> 7) & (PHP_INT_MAX >> 6)); //PHP really needs a logical right-shift operator
    }

    throw new InvalidArgumentException("Value too large to be encoded as a VarLong");
}

Here is my Javascript implementation:

writeVarLong(v) {
   return this.writeUnsignedVarLong((v << 1) ^ (v >> 63))
}

writeUnsignedVarLong(v) {
   for (let i = 0; i < 10; i++) {
     if ((v >> 7) !== 0) {
        this.writeByte(v | 0x80)
     } else {
        this.writeByte(v & 0x7f)
        break
     }
     v >>= 7
  }
}

The problem comes when I run a simple test, I instantiate the two binary streams and I wrote a value on them, the problem is that output is different.

Here is the PHP test & output:

$stream = new NetworkBinaryStream();
$stream->putVarLong(422212465606656);
var_dump(bin2hex($stream->buffer));
// Output: 8080c2808080c001

Here is the Javascript test & output:

let stream = new PacketBinaryStream()
stream.writeVarLong(422212465606656)
console.log(stream.buffer)
// Output: 80 80 42

As we can see outputs are different

Complete files:

Advertisement

Answer

These methods are hitting an issue where the bitwise operators are only working in 32-bit, so the 64-bit numbers are being truncated before your serialisation.

You can work around this with BigInt.

const BinaryStream = require('jsbinaryutils')

class PacketBinaryStream extends BinaryStream {
    writeVarLong(v) {
        let bi = BigInt(v);
        return this.writeUnsignedVarLong((bi << 1n) ^ (bi >> 63n))
    }

    writeUnsignedVarLong(v) {
        let bi = BigInt(v);
        for (let i = 0; i < 10; i++) {
            if ((bi >> 7n) !== 0n) {
                this.writeByte(Number((bi | 0x80n)));
            } else {
                this.writeByte(Number((bi & 0x7fn)));
                break
            }
            bi >>= 7n
        }
    }
}

let toWrite = 422212465606656;
let stream = new PacketBinaryStream();
stream.writeVarLong(toWrite);
console.log(stream.buffer);

// Output: <Buffer 80 80 c2 80 80 80 c0 01>
User contributions licensed under: CC BY-SA
10 People found this is helpful
Advertisement