UNPKG

netkitty

Version:
171 lines (147 loc) 6.4 kB
import EventEmitter from 'events' import {createReadStream, ReadStream} from 'node:fs' import {format} from 'node:util' import {IPcapPacketInfo} from './interfaces/IPcapPacketInfo' const GLOBAL_HEADER_LENGTH = 24 //bytes const PACKET_HEADER_LENGTH = 16 //bytes export class PcapParser extends EventEmitter { protected stream: ReadStream protected buffer: Buffer | null protected errored: boolean = false protected endianness: 'BE' | 'LE' | null protected index: number = 0 protected state: () => boolean protected offset: number = 0 protected currentPacketHeader: { timestampSeconds: number timestampMicroseconds: number capturedLength: number originalLength: number } public static parse(input: string | ReadStream): PcapParser { return new PcapParser(input) } constructor(input: string | ReadStream) { super() this.stream = (typeof input === 'string') ? createReadStream(input) : input this.stream.pause() this.stream.on('data', (data: string | Buffer): void => this.onData(data as Buffer)) this.stream.on('error', (err: Error): void => this.onError(err)) this.stream.on('end', (): void => this.onEnd()) this.buffer = null this.state = () => this.parseGlobalHeader() this.endianness = null process.nextTick(this.stream.resume.bind(this.stream)) } protected updateBuffer(data: Buffer): void { if (data === null || data === undefined) return if (this.buffer === null) { this.buffer = data } else { let extendedBuffer: Buffer = Buffer.alloc(this.buffer.length + data.length) this.buffer.copy(extendedBuffer) data.copy(extendedBuffer, this.buffer.length) this.buffer = extendedBuffer } } protected parseGlobalHeader(): boolean { const buffer: Buffer = this.buffer! if (buffer.length >= GLOBAL_HEADER_LENGTH) { let msg: string const magicNumber: string = buffer.toString('hex', 0, 4) // determine pcap endianness if (magicNumber == 'a1b2c3d4') { this.endianness = 'BE' } else if (magicNumber == 'd4c3b2a1' || magicNumber == '4d3cb2a1') { this.endianness = 'LE' } else { this.errored = true this.stream.pause() msg = format('unknown magic number: %s', magicNumber) this.emit('error', new Error(msg)) this.onEnd() return false } const header = { magicNumber: buffer['readUInt32' + this.endianness](0, true), majorVersion: buffer['readUInt16' + this.endianness](4, true), minorVersion: buffer['readUInt16' + this.endianness](6, true), gmtOffset: buffer['readInt32' + this.endianness](8, true), timestampAccuracy: buffer['readUInt32' + this.endianness](12, true), snapshotLength: buffer['readUInt32' + this.endianness](16, true), linkLayerType: buffer['readUInt32' + this.endianness](20, true) } if (header.majorVersion != 2 && header.minorVersion != 4) { this.errored = true this.stream.pause() msg = format('unsupported version %d.%d. pcap-parser only parses libpcap file format 2.4', header.majorVersion, header.minorVersion) this.emit('error', new Error(msg)) this.onEnd() } else { this.emit('globalHeader', header) this.buffer = buffer.subarray(GLOBAL_HEADER_LENGTH) this.state = (): boolean => this.parsePacketHeader() this.offset = GLOBAL_HEADER_LENGTH return true } } return false } protected parsePacketHeader(): boolean { const buffer: Buffer = this.buffer! if (buffer.length >= PACKET_HEADER_LENGTH) { const header = { timestampSeconds: buffer['readUInt32' + this.endianness](0, true), timestampMicroseconds: buffer['readUInt32' + this.endianness](4, true), capturedLength: buffer['readUInt32' + this.endianness](8, true), originalLength: buffer['readUInt32' + this.endianness](12, true) } this.currentPacketHeader = header this.emit('packetHeader', header) this.buffer = buffer.subarray(PACKET_HEADER_LENGTH) this.state = (): boolean => this.parsePacketBody() this.index += 1 return true } return false } protected parsePacketBody(): boolean { const buffer: Buffer = this.buffer! if (buffer.length >= this.currentPacketHeader.capturedLength) { const data: Buffer = buffer.subarray(0, this.currentPacketHeader!.capturedLength) const packetLength: number = data.length const recordLength: number = PACKET_HEADER_LENGTH + packetLength const pcapPacketInfo: IPcapPacketInfo = { index: this.index, offset: this.offset, length: recordLength, recordHeaderOffset: this.offset, recordHeaderLength: PACKET_HEADER_LENGTH, packetOffset: this.offset + PACKET_HEADER_LENGTH, packetLength: data.length, seconds: this.currentPacketHeader.timestampSeconds, microseconds: this.currentPacketHeader.timestampMicroseconds, packet: data.toString('base64') } this.emit('packetData', data) this.emit('packet', pcapPacketInfo) this.buffer = buffer.subarray(this.currentPacketHeader.capturedLength) this.state = (): boolean => this.parsePacketHeader() this.offset += recordLength return true } return false } protected onData(data: Buffer): void { if (this.errored) return this.updateBuffer(data) while (this.state()) { } } protected onError(err: Error): void { this.emit('error', err) } protected onEnd(): void { this.emit('end') } }