174 lines
4.0 KiB
TypeScript
174 lines
4.0 KiB
TypeScript
![]() |
/**
|
||
|
* Buffer manipulation utilities for protocol detection
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* BufferAccumulator class for handling fragmented data
|
||
|
*/
|
||
|
export class BufferAccumulator {
|
||
|
private chunks: Buffer[] = [];
|
||
|
private totalLength = 0;
|
||
|
|
||
|
/**
|
||
|
* Append data to the accumulator
|
||
|
*/
|
||
|
append(data: Buffer): void {
|
||
|
this.chunks.push(data);
|
||
|
this.totalLength += data.length;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the accumulated buffer
|
||
|
*/
|
||
|
getBuffer(): Buffer {
|
||
|
if (this.chunks.length === 0) {
|
||
|
return Buffer.alloc(0);
|
||
|
}
|
||
|
if (this.chunks.length === 1) {
|
||
|
return this.chunks[0];
|
||
|
}
|
||
|
return Buffer.concat(this.chunks, this.totalLength);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get current buffer length
|
||
|
*/
|
||
|
length(): number {
|
||
|
return this.totalLength;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Clear all accumulated data
|
||
|
*/
|
||
|
clear(): void {
|
||
|
this.chunks = [];
|
||
|
this.totalLength = 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Check if accumulator has minimum bytes
|
||
|
*/
|
||
|
hasMinimumBytes(minBytes: number): boolean {
|
||
|
return this.totalLength >= minBytes;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Read a big-endian 16-bit integer from buffer
|
||
|
*/
|
||
|
export function readUInt16BE(buffer: Buffer, offset: number): number {
|
||
|
if (offset + 2 > buffer.length) {
|
||
|
throw new Error('Buffer too short for UInt16BE read');
|
||
|
}
|
||
|
return (buffer[offset] << 8) | buffer[offset + 1];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Read a big-endian 24-bit integer from buffer
|
||
|
*/
|
||
|
export function readUInt24BE(buffer: Buffer, offset: number): number {
|
||
|
if (offset + 3 > buffer.length) {
|
||
|
throw new Error('Buffer too short for UInt24BE read');
|
||
|
}
|
||
|
return (buffer[offset] << 16) | (buffer[offset + 1] << 8) | buffer[offset + 2];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Find a byte sequence in a buffer
|
||
|
*/
|
||
|
export function findSequence(buffer: Buffer, sequence: Buffer, startOffset = 0): number {
|
||
|
if (sequence.length === 0) {
|
||
|
return startOffset;
|
||
|
}
|
||
|
|
||
|
const searchLength = buffer.length - sequence.length + 1;
|
||
|
for (let i = startOffset; i < searchLength; i++) {
|
||
|
let found = true;
|
||
|
for (let j = 0; j < sequence.length; j++) {
|
||
|
if (buffer[i + j] !== sequence[j]) {
|
||
|
found = false;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (found) {
|
||
|
return i;
|
||
|
}
|
||
|
}
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Extract a line from buffer (up to CRLF or LF)
|
||
|
*/
|
||
|
export function extractLine(buffer: Buffer, startOffset = 0): { line: string; nextOffset: number } | null {
|
||
|
let lineEnd = -1;
|
||
|
let skipBytes = 1;
|
||
|
|
||
|
// Look for CRLF first
|
||
|
const crlfPos = findSequence(buffer, Buffer.from('\r\n'), startOffset);
|
||
|
if (crlfPos !== -1) {
|
||
|
lineEnd = crlfPos;
|
||
|
skipBytes = 2;
|
||
|
} else {
|
||
|
// Look for LF only
|
||
|
for (let i = startOffset; i < buffer.length; i++) {
|
||
|
if (buffer[i] === 0x0A) { // LF
|
||
|
lineEnd = i;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (lineEnd === -1) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
const line = buffer.slice(startOffset, lineEnd).toString('utf8');
|
||
|
return {
|
||
|
line,
|
||
|
nextOffset: lineEnd + skipBytes
|
||
|
};
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Check if buffer starts with a string (case-insensitive)
|
||
|
*/
|
||
|
export function startsWithString(buffer: Buffer, str: string, offset = 0): boolean {
|
||
|
if (offset + str.length > buffer.length) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
const bufferStr = buffer.slice(offset, offset + str.length).toString('utf8');
|
||
|
return bufferStr.toLowerCase() === str.toLowerCase();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Safe buffer slice that doesn't throw on out-of-bounds
|
||
|
*/
|
||
|
export function safeSlice(buffer: Buffer, start: number, end?: number): Buffer {
|
||
|
const safeStart = Math.max(0, Math.min(start, buffer.length));
|
||
|
const safeEnd = end === undefined
|
||
|
? buffer.length
|
||
|
: Math.max(safeStart, Math.min(end, buffer.length));
|
||
|
|
||
|
return buffer.slice(safeStart, safeEnd);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Check if buffer contains printable ASCII
|
||
|
*/
|
||
|
export function isPrintableAscii(buffer: Buffer, length?: number): boolean {
|
||
|
const checkLength = length || buffer.length;
|
||
|
|
||
|
for (let i = 0; i < checkLength && i < buffer.length; i++) {
|
||
|
const byte = buffer[i];
|
||
|
// Check if byte is printable ASCII (0x20-0x7E) or tab/newline/carriage return
|
||
|
if (byte < 0x20 || byte > 0x7E) {
|
||
|
if (byte !== 0x09 && byte !== 0x0A && byte !== 0x0D) {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|