Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add VarInt support #47

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,26 @@ Write a null terminated Buffer.

Insert a null terminated Buffer.

## VarInt

[Protobuf VarInt](https://developers.google.com/protocol-buffers/docs/encoding#varints) is a special variant of integer that varies in it's encoded bytes length.
Current implementation doesn't support negative VarInts.

### buff.readVarInt()

Read a VarInt.

### buff.writeVarInt(value[, offset])
- ```value``` *{Number}* The integer value to write.
- ```offset``` *{number}* An optional offset to write the value to. **Default:** ```Auto managed offset```

Write a VarInt.

### buff.insertVarInt(value, offset)
- ```value``` *{Number}* The integer value to write.
- ```offset``` *{number}* The offset to insert the value to.

Insert a VarInt.

## Offsets

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"dependencies": {},
"scripts": {
"prepublish": "npm install -g typescript && npm run build",
"test": "NODE_ENV=test mocha --recursive --require ts-node/register test/**/*.ts",
"test": "mocha --recursive --require ts-node/register test/**/*.ts",
"coverage": "NODE_ENV=test nyc npm test",
"coveralls": "NODE_ENV=test nyc npm test && nyc report --reporter=text-lcov | coveralls",
"lint": "tslint --type-check --project tsconfig.json 'src/**/*.ts'",
Expand Down
109 changes: 109 additions & 0 deletions src/smartbuffer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -998,6 +998,81 @@ class SmartBuffer {
return this;
}

// VarInts

/**
* Inserts VarInt.
*
* @param value { Number } The number to write.
* @param offset { Number } The offset to insert the number to.
*
* @return { Number }
*/
public insertVarInt(value: number, offset: number): SmartBuffer {
checkOffsetValue(offset);

this.insertBuffer(this._intToVarInt(value), offset);

return this;
}

/**
* Writes a VarInt to the current write position.
*
* @param value { Number } The number to write.
* @param offset { Number } The offset to write the number to.
*
* @return { Number }
*/
public writeVarInt(value: number, offset?: number): SmartBuffer {
// Checks for valid numberic value;
if (typeof offset !== 'undefined') {
checkOffsetValue(offset);
}

this.writeBuffer(this._intToVarInt(value), offset);

return this;
}

/**
* Reads a VarInt from the internal read position.
*
* @return { Number }
*/
public readVarInt(): number {
const MSB = 0x80;
const REST = 0x7F;

let res = 0;
let shift = 0;
let counter = 0;
let byte = 0;

do {
const readOffset = this._readOffset + counter;

if (readOffset > this.length - 1) {
throw new RangeError('Could not read full VarInt - end of buffer.');
}

if (shift > 49) {
throw new RangeError('Could not decode VarInt');
}

byte = this._buff[readOffset];
counter += 1;
res += shift < 28
? (byte & REST) << shift
: (byte & REST) * Math.pow(2, shift);
shift += 7;
} while (byte >= MSB);

this._readOffset += counter;

return res;
}

/**
* Clears the SmartBuffer instance to its original empty state.
*/
Expand Down Expand Up @@ -1411,6 +1486,40 @@ class SmartBuffer {

return this;
}

private _intToVarInt(value: number): Buffer {
if (value < 0) {
throw new Error('Cannot insert VarInt smaller than 0');
}

const INT = Math.pow(2, 31);
const MSB = 0x80;
const REST = 0x7F;
const MSBALL = ~REST;

if (value > Number.MAX_SAFE_INTEGER) {
throw new RangeError('Could not encode varint');
}

const out: number[] = [];
let outOffset = 0;

while (value >= INT) {
out[outOffset] = (value & 0xFF) | MSB;
outOffset += 1;
value /= 128;
}

while (value & MSBALL) {
out[outOffset] = (value & 0xFF) | MSB;
outOffset += 1;
value >>>= 7;
}

out[outOffset] = value | 0;

return Buffer.from(out);
}
}

export { SmartBufferOptions, SmartBuffer };
34 changes: 34 additions & 0 deletions test/smartbuffer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,40 @@ describe('Reading/Writing To/From SmartBuffer', () => {
});
});
});

describe('VarInt values', () => {
let reader = new SmartBuffer();

reader.writeVarInt(0);
reader.writeVarInt(128);
reader.writeVarInt(25565);
reader.insertVarInt(0, 1); // writeVarInt(0) will write 1 byte, insert after it

it('should equal the correct values that were written above', () => {
assert.strictEqual(reader.readVarInt(), 0);
assert.strictEqual(reader.readVarInt(), 0);
assert.strictEqual(reader.readVarInt(), 128);
assert.strictEqual(reader.readVarInt(), 25565);
});

it('should throw an exception if attempting to read VarInt from a buffer with not enough data left', () => {
assert.throws(() => {
reader.readVarInt();
});
});

it('should throw an exception if attempting to write VarInt smaller than 0', () => {
assert.throws(() => {
reader.writeVarInt(-1);
});
});

it('should throw an exception if attempting to write VarInt to a negative offset.', () => {
assert.throws(() => {
reader.writeVarInt(20, -5);
});
});
});
});

describe('Skipping around data', () => {
Expand Down