93 lines
2.4 KiB
TypeScript
93 lines
2.4 KiB
TypeScript
/**
|
|
* BNCS (Battle.net Chat Server, Port 6112) stream framing (`0xFF | u8 id | u16 len`).
|
|
* Client sends 1-byte protocol greeting `0x01` upon connection.
|
|
*/
|
|
|
|
import { ProtocolError } from '../core/errors.ts'
|
|
|
|
export const BNCS_GREETING_BYTE = 0x01
|
|
export const BNCS_HEADER_MAGIC = 0xff
|
|
|
|
export interface BncsRawPacket {
|
|
readonly id: number
|
|
readonly payload: Uint8Array
|
|
readonly raw: Uint8Array
|
|
}
|
|
|
|
export function encodeBncsFrame(packetId: number, payload: Uint8Array): Uint8Array {
|
|
const totalLen = 4 + payload.length
|
|
if (totalLen > 0xffff) {
|
|
throw new ProtocolError('bnet', packetId, 0, `BNCS packet too large (${totalLen}B)`)
|
|
}
|
|
const out = new Uint8Array(totalLen)
|
|
out[0] = BNCS_HEADER_MAGIC
|
|
out[1] = packetId & 0xff
|
|
out[2] = totalLen & 0xff
|
|
out[3] = (totalLen >>> 8) & 0xff
|
|
out.set(payload, 4)
|
|
return out
|
|
}
|
|
|
|
export class BncsFramer {
|
|
private buf: Uint8Array = new Uint8Array(0)
|
|
|
|
push(chunk: Uint8Array): BncsRawPacket[] {
|
|
if (chunk.length === 0) return []
|
|
if (this.buf.length === 0) {
|
|
this.buf = chunk.slice()
|
|
} else {
|
|
const merged = new Uint8Array(this.buf.length + chunk.length)
|
|
merged.set(this.buf, 0)
|
|
merged.set(chunk, this.buf.length)
|
|
this.buf = merged
|
|
}
|
|
|
|
const packets: BncsRawPacket[] = []
|
|
let offset = 0
|
|
|
|
while (this.buf.length - offset >= 4) {
|
|
const magic = this.buf[offset]!
|
|
if (magic !== BNCS_HEADER_MAGIC) {
|
|
throw new ProtocolError(
|
|
'bnet',
|
|
null,
|
|
offset,
|
|
`Invalid BNCS frame magic 0x${magic.toString(16).padStart(2, '0')} (expected 0xFF)`,
|
|
this.buf.slice(offset),
|
|
)
|
|
}
|
|
const id = this.buf[offset + 1]!
|
|
const totalLen = this.buf[offset + 2]! | (this.buf[offset + 3]! << 8)
|
|
if (totalLen < 4) {
|
|
throw new ProtocolError(
|
|
'bnet',
|
|
id,
|
|
offset,
|
|
`Invalid BNCS frame length ${totalLen} (< 4)`,
|
|
this.buf.slice(offset),
|
|
)
|
|
}
|
|
if (this.buf.length - offset < totalLen) {
|
|
break
|
|
}
|
|
const raw = this.buf.slice(offset, offset + totalLen)
|
|
const payload = raw.subarray(4)
|
|
packets.push({ id, payload, raw })
|
|
offset += totalLen
|
|
}
|
|
|
|
if (offset > 0) {
|
|
this.buf = this.buf.slice(offset)
|
|
}
|
|
return packets
|
|
}
|
|
|
|
get bufferedBytes(): number {
|
|
return this.buf.length
|
|
}
|
|
|
|
reset(): void {
|
|
this.buf = new Uint8Array(0)
|
|
}
|
|
}
|