Skip to content

Commit

Permalink
wip: pull out packetbuilder helper
Browse files Browse the repository at this point in the history
  • Loading branch information
Julusian committed Dec 1, 2023
1 parent f83e2b1 commit 0282ac9
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 7 deletions.
120 changes: 120 additions & 0 deletions src/lib/__tests__/packetBuilder.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import type { ISerializableCommand } from '../../commands'
import { ProtocolVersion } from '../../enums'
import { PacketBuilder } from '../packetBuilder'

class FakeCommand implements ISerializableCommand {
static readonly rawName: string = 'FAKE'

constructor(public readonly length: number, public readonly value: number = 1) {}

public get lengthWithHeader(): number {
return this.length + 8
}

serialize = jest.fn((_version: ProtocolVersion): Buffer => {
return Buffer.alloc(this.length, this.value)
})
}

describe('PacketBuilder', () => {
it('No commands', () => {
const builder = new PacketBuilder(500, ProtocolVersion.V8_1_1)
expect(builder.getPackets()).toHaveLength(0)
})

it('Single command', () => {
const builder = new PacketBuilder(500, ProtocolVersion.V8_1_1)

const cmd = new FakeCommand(10)
builder.addCommand(cmd)

expect(builder.getPackets()).toHaveLength(1)
expect(builder.getPackets()).toHaveLength(1) // Ensure that calling it twice doesnt affect the output
expect(builder.getPackets()[0]).toHaveLength(cmd.lengthWithHeader)
})

it('Once finished cant add more commands', () => {
const builder = new PacketBuilder(500, ProtocolVersion.V8_1_1)

const cmd = new FakeCommand(10)
builder.addCommand(cmd)

expect(builder.getPackets()).toHaveLength(1)

expect(() => builder.addCommand(cmd)).toThrow('finished')
})

it('Repeated command', () => {
const builder = new PacketBuilder(500, ProtocolVersion.V8_1_1)

const cmd = new FakeCommand(10)
for (let i = 0; i < 5; i++) {
builder.addCommand(cmd)
}

expect(builder.getPackets()).toHaveLength(1)
expect(builder.getPackets()[0]).toHaveLength(cmd.lengthWithHeader * 5)
})

it('Repeated command spanning multiple packets', () => {
const builder = new PacketBuilder(500, ProtocolVersion.V8_1_1)

const cmd = new FakeCommand(10)
for (let i = 0; i < 60; i++) {
builder.addCommand(cmd)
}

expect(cmd.lengthWithHeader).toBe(18)
expect(builder.getPackets()).toHaveLength(3)

expect(builder.getPackets()[0]).toHaveLength(cmd.lengthWithHeader * 27)
expect(builder.getPackets()[1]).toHaveLength(cmd.lengthWithHeader * 27)
expect(builder.getPackets()[2]).toHaveLength(cmd.lengthWithHeader * 6)
})

it('Command too large to fit a packets', () => {
const builder = new PacketBuilder(500, ProtocolVersion.V8_1_1)

const cmd = new FakeCommand(501)
expect(() => builder.addCommand(cmd)).toThrow('too large')
})

it('Command same size as packet', () => {
const builder = new PacketBuilder(500, ProtocolVersion.V8_1_1)

const cmd = new FakeCommand(500 - 8)
expect(cmd.lengthWithHeader).toBe(500)

builder.addCommand(cmd)
expect(builder.getPackets()).toHaveLength(1)
expect(builder.getPackets()[0]).toHaveLength(cmd.lengthWithHeader)
})

it('Commands of mixed sizes', () => {
const builder = new PacketBuilder(500, ProtocolVersion.V8_1_1)

const largeCmd = new FakeCommand(400)
const mediumCmd = new FakeCommand(80)
const smallCmd = new FakeCommand(10)

// packet 0:
builder.addCommand(mediumCmd)
builder.addCommand(smallCmd)

// packet 1:
builder.addCommand(largeCmd)
builder.addCommand(mediumCmd)

// packet 2:
builder.addCommand(smallCmd)
builder.addCommand(smallCmd)
builder.addCommand(largeCmd)

expect(builder.getPackets()).toHaveLength(3)
expect(builder.getPackets()[0]).toHaveLength(mediumCmd.lengthWithHeader + smallCmd.lengthWithHeader)
expect(builder.getPackets()[1]).toHaveLength(largeCmd.lengthWithHeader + mediumCmd.lengthWithHeader)
expect(builder.getPackets()[2]).toHaveLength(
smallCmd.lengthWithHeader + smallCmd.lengthWithHeader + largeCmd.lengthWithHeader
)
})
})
16 changes: 9 additions & 7 deletions src/lib/packetBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export class PacketBuilder {

readonly #completedBuffers: Buffer[] = []

#finished = false
#currentPacketBuffer: Buffer
#currentPacketFilled: number

Expand All @@ -19,16 +20,17 @@ export class PacketBuilder {
}

public addCommand(cmd: ISerializableCommand): void {
if (this.#finished) throw new Error('Packets have been finished')

if (typeof cmd.serialize !== 'function') {
throw new Error(`Command ${cmd.constructor.name} is not serializable`)
}

const payload = cmd.serialize(this.#protocolVersion)

const rawName: string = (cmd.constructor as any).rawName
const payload = cmd.serialize(this.#protocolVersion)

const totalLength = payload.length + 8
if (totalLength >= this.#maxPacketSize) {
if (totalLength > this.#maxPacketSize) {
throw new Error(`Comamnd ${cmd.constructor.name} is too large for a single packet`)
}

Expand All @@ -37,11 +39,9 @@ export class PacketBuilder {
this.#finishBuffer()
}

// Command name
// Add to packet
this.#currentPacketBuffer.writeUInt16BE(payload.length + 8, this.#currentPacketFilled + 0)
this.#currentPacketBuffer.write(rawName, this.#currentPacketFilled + 4, 4)

// Body
payload.copy(this.#currentPacketBuffer, this.#currentPacketFilled + 8)

this.#currentPacketFilled += totalLength
Expand All @@ -50,11 +50,13 @@ export class PacketBuilder {
public getPackets(): Buffer[] {
this.#finishBuffer(true)

this.#finished = true

return this.#completedBuffers
}

#finishBuffer(skipCreateNext?: boolean) {
if (this.#currentPacketFilled === 0) return
if (this.#currentPacketFilled === 0 || this.#finished) return

this.#completedBuffers.push(this.#currentPacketBuffer.subarray(0, this.#currentPacketFilled))

Expand Down

0 comments on commit 0282ac9

Please sign in to comment.