Implement smart buffer
continuous-integration/drone/push Build is failing Details

This commit is contained in:
Jack Hadrill 2022-02-02 01:58:55 +00:00
parent a93bfc182f
commit 7d1a0991e4
12 changed files with 1717 additions and 152 deletions

1239
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -18,8 +18,11 @@
"@types/jest": "^27.4.0", "@types/jest": "^27.4.0",
"jest": "^27.4.7", "jest": "^27.4.7",
"ts-jest": "^27.1.3", "ts-jest": "^27.1.3",
"ts-loader": "^9.2.6",
"ts-standard": "^11.0.0", "ts-standard": "^11.0.0",
"typescript": "^4.5.5" "typescript": "^4.5.5",
"webpack": "^5.68.0",
"webpack-cli": "^4.9.2"
}, },
"jest": { "jest": {
"verbose": true, "verbose": true,

View File

@ -1,17 +1,23 @@
import { IMessageEvent } from 'websocket' import { IMessageEvent } from 'websocket'
import { IncomingPacket } from './structures/server' import { IncomingPacket } from './structures/server'
// import { SubscribeMessage } from './structures/subscribe'
export function connect (url: string): void { export function connect (url: string): void {
const webSocket = new WebSocket(url) const webSocket = new WebSocket(url)
webSocket.binaryType = 'arraybuffer' webSocket.binaryType = 'arraybuffer'
webSocket.onopen = onOpen
webSocket.onmessage = onMessage webSocket.onmessage = onMessage
}
function onOpen (): void { webSocket.onopen = () => {
console.log('Connected!') console.log('Connected!')
// const subscribeMessage = new SubscribeMessage(0x0001)
const tmp = Uint8Array.from([0x00, 0x00, 0x00, 0x02, 0x00, 0x01])
console.log(tmp)
webSocket.send(tmp)
}
} }
function onMessage (event: IMessageEvent): void { function onMessage (event: IMessageEvent): void {
console.log(new IncomingPacket(event.data as Buffer)) console.log('New message.')
console.log(event.data)
console.log(new IncomingPacket(event.data as ArrayBuffer))
} }

View File

@ -1,37 +0,0 @@
import { ByteLength } from './common'
export class BufferReader {
private readonly _data: Buffer
private _cursor: number
constructor (data: Buffer = Buffer.from([])) {
this._data = data
this._cursor = 0
}
get cursor (): number {
return this._cursor
}
set cursor (position: number) {
if (position < 0 || position > this._data.length) {
throw RangeError(`Cannot seek to ${this.cursor} of ${this._data.length} bytes.`)
}
this._cursor = position
}
readUInt16 (): number {
this.cursor += ByteLength.UInt16
return this._data.slice(this.cursor - ByteLength.UInt16, this.cursor).readInt16BE()
}
readUInt32 (): number {
this.cursor += ByteLength.UInt32
return this._data.slice(this.cursor - ByteLength.UInt32, this.cursor).readInt32BE()
}
readBuffer (len: number): Buffer {
this.cursor += len
return this._data.slice(this.cursor - len, this.cursor)
}
}

View File

@ -1,3 +1,5 @@
export const MAX_DATA_LENGTH = 1000
export enum ByteLength { export enum ByteLength {
UInt16 = 2, UInt16 = 2,
UInt32 = 4 UInt32 = 4

View File

@ -1 +1,3 @@
export { connect } from './bennc' import { connect } from './bennc'
connect('wss://chat.3t.network/BENNC')

158
src/smart-buffer.ts Normal file
View File

@ -0,0 +1,158 @@
import { ByteLength } from './common'
export class SmartBuffer {
private _data: number[]
private _cursor: number
/**
* Wrap a buffer to track position and provide useful read / write functionality.
* @param data Buffer to wrap (optional).
*/
constructor (data?: Uint8Array) {
this._data = data === undefined ? [] : [...data]
this._cursor = 0
}
/**
* Return a regular buffer.
*/
get data (): Uint8Array {
return Uint8Array.from(this._data)
}
/**
* Update the smart buffer to wrap new data.
*/
set data (data: Uint8Array) {
this._data = [...data]
this.cursor = 0
}
/**
* Get the length of the smart buffer data.
*/
get length (): number {
return this._data.length
}
/**
* Set the length of the smart buffer data.
*/
set length (length: number) {
this._data.length = length
}
/**
* Get the current cursor position of the smart buffer.
*/
get cursor (): number {
return this._cursor
}
/**
* Set the cursor position of the smart buffer.
*/
set cursor (position: number) {
if (position < 0) {
throw RangeError(`Cannot seek to ${this.cursor} of ${this.length} bytes.`)
}
this._cursor = position
}
/**
* Pads bytes to the end of the smart buffer.
* @param length The number of bytes to pad.
*/
pad (length: number): void {
this._data.push(...new Uint8Array(length))
}
/**
* Return the data from the specified range.
* @param start The start position.
* @param end The end position.
* @returns A new buffer containing data from the specified range.
*/
slice (start: number, end: number): Uint8Array {
return Uint8Array.from(this._data.slice(start, end))
}
/**
* Splice bytes into the smart buffer's data.
* @param start The position at which to insert the new data.
* @param deleteCount The number of items to remove before inserting new data.
* @param items The items to insert at the specified position.
*/
splice (start: number, deleteCount: number, ...items: number[]): void {
if (this.length < start) {
this.pad(start)
}
this._data.splice(this.cursor, ByteLength.UInt16, ...items)
}
/**
* Read a UInt16 number from the smart buffer at the current cursor position, and increment the cursor.
* @returns A number represented by the bytes at the current cursor position.
*/
readUInt16 (): number {
this.cursor += ByteLength.UInt16
const bytes = this.slice(this.cursor - ByteLength.UInt16, this.cursor)
return (bytes[0] << 8) | bytes[1]
}
/**
* Read a UInt32 number from the smart buffer at the current cursor position, and increment the cursor.
* @returns A number represented by the bytes at the current cursor position.
*/
readUInt32 (): number {
this.cursor += ByteLength.UInt32
const bytes = this.slice(this.cursor - ByteLength.UInt32, this.cursor)
return (((bytes[0] << 24) | (bytes[1] << 16)) | (bytes[2] << 8) | bytes[3])
}
/**
* Read the specified number of bytes from the smart buffer at the current cursor position, and increment the cursor.
* @returns A buffer containing the bytes read from the current cursor position.
*/
readBytes (length: number): Uint8Array {
this.cursor += length
return this.slice(this.cursor - length, this.cursor)
}
/**
* Write a UInt16 number to the smart buffer at the current cursor position, and increment the cursor.
* @param number The number to write in UInt16 format at the current cursor position.
*/
writeUInt16 (number: number): void {
const bytes = [
(number & 0xff00) >> 8,
(number & 0x00ff)
]
this.splice(this.cursor, ByteLength.UInt16, ...bytes)
this.cursor += ByteLength.UInt16
}
/**
* Write a UInt32 number to the smart buffer at the current cursor position, and increment the cursor.
* @param number The number to write in UInt32 format at the current cursor position.
*/
writeUInt32 (number: number): void {
const bytes = [
(number & 0xff000000) >> 24,
(number & 0x00ff0000) >> 16,
(number & 0x0000ff00) >> 8,
(number & 0x000000ff)
]
this.splice(this.cursor, ByteLength.UInt32, ...bytes)
this.cursor += ByteLength.UInt32
}
/**
* Write the specified bytes to the smart buffer at the current cursor position, and increment the cursor.
* @param buffer The bytes to write at the current cursor position.
*/
writeBytes (buffer: Uint8Array): void {
this.splice(this.cursor, buffer.length, ...buffer)
this.cursor += buffer.length
}
}

View File

@ -1,16 +1,35 @@
import { BufferReader } from '../buffer-reader' import { MAX_DATA_LENGTH } from '../common'
import { SmartBuffer } from '../smart-buffer'
export class IncomingPacket extends BufferReader { export class IncomingPacket extends SmartBuffer {
messageType: number fields: {
senderId: number messageType: number
length: number senderId: number
data: Buffer length: number
data: Uint8Array
}
constructor (data: Buffer) { constructor (data: ArrayBuffer) {
super() super()
this.messageType = this.readUInt16() this.data = new Uint8Array(data)
this.senderId = this.readUInt32() this.fields = {
this.length = this.readUInt16() messageType: this.readUInt16(),
this.data = this.readBuffer(this.length) senderId: this.readUInt32(),
length: this.readUInt16(),
data: this.readBytes(this.length)
}
}
}
export class OutgoingPacket extends SmartBuffer {
constructor (messageType: number, data: Uint8Array = new Uint8Array(0)) {
super()
if (data.length > MAX_DATA_LENGTH) {
throw RangeError(`Specified data of length ${data.length} exceeds max data length ${MAX_DATA_LENGTH}.`)
}
this.writeUInt16(messageType)
this.writeUInt16(data.length)
this.writeBytes(data)
} }
} }

View File

@ -0,0 +1,22 @@
import { SmartBuffer } from '../smart-buffer'
import { OutgoingPacket } from './server'
export class SubscribeMessage extends SmartBuffer {
static readonly messageType = 0x0000
constructor (id: number) {
super()
const outgoingPacket = new OutgoingPacket(SubscribeMessage.messageType)
this.data = outgoingPacket.data
this.writeUInt16(id)
}
}
export class Unsubscribe extends OutgoingPacket {
static readonly messageType = 0xffff
constructor (id: number) {
super(Unsubscribe.messageType)
this.writeUInt16(id)
}
}

View File

@ -1,96 +0,0 @@
import { BufferReader } from '../src/buffer-reader'
test('Read a UInt16.', () => {
// Given
const buffer = Buffer.from([0x30, 0x39])
// When
const smartBuffer = new BufferReader(buffer)
// Then
expect(smartBuffer.readUInt16()).toBe(12345)
})
test('Read a UInt32.', () => {
// Given
const buffer = Buffer.from([0x49, 0x96, 0x02, 0xD2])
// When
const smartBuffer = new BufferReader(buffer)
// Then
expect(smartBuffer.readUInt32()).toBe(1234567890)
})
test('Read a buffer.', () => {
// Given
const buffer = Buffer.from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
// When
const smartBuffer = new BufferReader(buffer)
// Then
expect(smartBuffer.readBuffer(4)).toMatchObject(Buffer.from([0, 1, 2, 3]))
})
test('Cursor is correctly incremented after reading a UInt16.', () => {
// Given
const buffer = Buffer.from([0x30, 0x39, 0x1A, 0x85])
// When
const smartBuffer = new BufferReader(buffer)
// Then
expect(smartBuffer.readUInt16()).toBe(12345)
expect(smartBuffer.readUInt16()).toBe(6789)
})
test('Cursor is correctly incremented after reading a buffer.', () => {
// Given
const buffer = Buffer.from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
// When
const smartBuffer = new BufferReader(buffer)
// Then
expect(smartBuffer.readBuffer(4)).toMatchObject(Buffer.from([0, 1, 2, 3]))
expect(smartBuffer.readBuffer(4)).toMatchObject(Buffer.from([4, 5, 6, 7]))
})
test('Cursor is correctly incremented after reading a UInt32.', () => {
// Given
const buffer = Buffer.from([0x49, 0x96, 0x02, 0xD2, 0x3A, 0xDE, 0x68, 0xB1])
// When
const smartBuffer = new BufferReader(buffer)
// Then
expect(smartBuffer.readUInt32()).toBe(1234567890)
expect(smartBuffer.readUInt32()).toBe(987654321)
})
test('Seek to position below 0 throws range error.', () => {
// Given
const buffer = Buffer.from([])
// When
const smartBuffer = new BufferReader(buffer)
// Then
expect(() => {
smartBuffer.cursor = -1
}).toThrow(RangeError)
})
test('Seek beyond buffer throws range error.', () => {
// Given
const buffer = Buffer.from([0, 1, 2, 3])
// When
const smartBuffer = new BufferReader(buffer)
// Then
expect(() => {
smartBuffer.cursor = 5
}).toThrow(RangeError)
})

229
tests/smart-buffer.test.ts Normal file
View File

@ -0,0 +1,229 @@
import { SmartBuffer } from '../src/smart-buffer'
test('Read a UInt16.', () => {
// Given
const buffer = Uint8Array.from([0x30, 0x39])
// When
const smartBuffer = new SmartBuffer(buffer)
// Then
expect(smartBuffer.readUInt16()).toBe(12345)
})
test('Read a UInt32.', () => {
// Given
const buffer = Uint8Array.from([0x49, 0x96, 0x02, 0xD2])
// When
const smartBuffer = new SmartBuffer(buffer)
// Then
expect(smartBuffer.readUInt32()).toBe(1234567890)
})
test('Read a buffer.', () => {
// Given
const buffer = Uint8Array.from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
// When
const smartBuffer = new SmartBuffer(buffer)
// Then
expect(smartBuffer.readBytes(4)).toMatchObject(Uint8Array.from([0, 1, 2, 3]))
})
test('Read a UInt16 from an offset.', () => {
// Given
const buffer = Uint8Array.from([0x00, 0x00, 0x30, 0x39])
// When
const smartBuffer = new SmartBuffer(buffer)
smartBuffer.cursor = 2
// Then
expect(smartBuffer.readUInt16()).toBe(12345)
})
test('Read a UInt32 from an offset.', () => {
// Given
const buffer = Uint8Array.from([0x00, 0x00, 0x49, 0x96, 0x02, 0xD2])
// When
const smartBuffer = new SmartBuffer(buffer)
smartBuffer.cursor = 2
// Then
expect(smartBuffer.readUInt32()).toBe(1234567890)
})
test('Read a buffer from an offset.', () => {
// Given
const buffer = Uint8Array.from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
// When
const smartBuffer = new SmartBuffer(buffer)
smartBuffer.cursor = 2
// Then
expect(smartBuffer.readBytes(4)).toMatchObject(Uint8Array.from([2, 3, 4, 5]))
})
test('Write a UInt16.', () => {
// Given
const smartBuffer = new SmartBuffer()
// When
smartBuffer.writeUInt16(12345)
// Then
expect(smartBuffer.data).toMatchObject(Uint8Array.from([0x30, 0x39]))
})
test('Write a UInt32.', () => {
// Given
const smartBuffer = new SmartBuffer()
// When
smartBuffer.writeUInt32(1234567890)
// Then
expect(smartBuffer.data).toMatchObject(Uint8Array.from([0x49, 0x96, 0x02, 0xD2]))
})
test('Write a buffer.', () => {
// Given
const smartBuffer = new SmartBuffer()
// When
smartBuffer.writeBytes(Uint8Array.from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]))
// Then
expect(smartBuffer.data).toMatchObject(Uint8Array.from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]))
})
test('Write a UInt16 at an offset.', () => {
// Given
const smartBuffer = new SmartBuffer()
// When
smartBuffer.cursor = 2
smartBuffer.writeUInt16(12345)
// Then
expect(smartBuffer.data).toMatchObject(Uint8Array.from([0x00, 0x00, 0x30, 0x39]))
})
test('Write a UInt32 at an offset.', () => {
// Given
const smartBuffer = new SmartBuffer()
// When
smartBuffer.cursor = 2
smartBuffer.writeUInt32(1234567890)
// Then
expect(smartBuffer.data).toMatchObject(Uint8Array.from([0x00, 0x00, 0x49, 0x96, 0x02, 0xD2]))
})
test('Write a buffer at an offset.', () => {
// Given
const smartBuffer = new SmartBuffer()
// When
smartBuffer.cursor = 2
smartBuffer.writeBytes(Uint8Array.from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]))
// Then
expect(smartBuffer.data).toMatchObject(Uint8Array.from([0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]))
})
test('Cursor is correctly incremented after reading a UInt16.', () => {
// Given
const buffer = new Uint8Array(4)
// When
const smartBuffer = new SmartBuffer(buffer)
// Then
smartBuffer.readUInt16()
expect(smartBuffer.cursor).toBe(2)
})
test('Cursor is correctly incremented after reading a UInt32.', () => {
// Given
const buffer = new Uint8Array(4)
// When
const smartBuffer = new SmartBuffer(buffer)
// Then
smartBuffer.readUInt32()
expect(smartBuffer.cursor).toBe(4)
})
test('Cursor is correctly incremented after reading a buffer.', () => {
// Given
const buffer = new Uint8Array(8)
// When
const smartBuffer = new SmartBuffer(buffer)
// Then
smartBuffer.readBytes(4)
expect(smartBuffer.cursor).toBe(4)
})
test('Cursor is correctly incremented after writing a UInt16.', () => {
// Given
const smartBuffer = new SmartBuffer()
// When
smartBuffer.writeUInt16(12345)
// Then
expect(smartBuffer.cursor).toBe(2)
})
test('Cursor is correctly incremented after writing a UInt32.', () => {
// Given
const smartBuffer = new SmartBuffer()
// When
smartBuffer.writeUInt32(1234567890)
// Then
expect(smartBuffer.cursor).toBe(4)
})
test('Cursor is correctly incremented after writing a buffer.', () => {
// Given
const smartBuffer = new SmartBuffer()
// When
smartBuffer.writeBytes(Uint8Array.from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]))
// Then
expect(smartBuffer.cursor).toBe(10)
})
test('Seek to position below 0 throws range error.', () => {
// When
const smartBuffer = new SmartBuffer()
// Then
expect(() => {
smartBuffer.cursor = -1
}).toThrow(RangeError)
})
test('Pad some data..', () => {
// Given
const smartBuffer = new SmartBuffer()
// When
smartBuffer.pad(10)
// Then
expect(smartBuffer.length).toBe(10)
})

20
webpack.config.js Normal file
View File

@ -0,0 +1,20 @@
const path = require('path')
module.exports = {
entry: './src/index.ts',
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
},
resolve: {
extensions: ['.tsx', '.ts', '.js']
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}
}