diablo2-web/src/netproto/bncs/framing.ts

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)
}
}