netkitty
Version:
Network tools kit
332 lines (324 loc) • 15.4 kB
text/typescript
import {ProtocolJSONSchema} from '../../schema/ProtocolJSONSchema'
import {BaseHeader} from '../abstracts/BaseHeader'
import {UInt16ToHex} from '../../helper/NumberToHex'
import {CodecModule} from '../types/CodecModule'
import {StringContentEncodingEnum} from '../lib/StringContentEncodingEnum'
import {BufferToUInt16, BufferToUInt8} from '../../helper/BufferToNumber'
import {UInt16ToBuffer, UInt8ToBuffer} from '../../helper/NumberToBuffer'
import {BufferToHex} from '../../helper/BufferToHex'
import {IPv4ToBuffer} from '../../helper/IPToBuffer'
import {BufferToIPv4} from '../../helper/BufferToIP'
import {HexToBuffer} from '../../helper/HexToBuffer'
export class IPv4 extends BaseHeader {
/**
* Calculate IPv4 header checksum
* @param headerBuffer
* @protected
*/
protected calculateIPv4Checksum(headerBuffer: Buffer): number {
const header: Uint8Array = Uint8Array.from(headerBuffer)
let sum: number = 0
for (let i: number = 0; i < header.length; i += 2) {
const word = (header[i] << 8) + (header[i + 1] || 0)
sum += word
}
while (sum >>> 16) {
sum = (sum & 0xFFFF) + (sum >>> 16)
}
return (~sum) & 0xFFFF
}
public SCHEMA: ProtocolJSONSchema = {
type: 'object',
properties: {
version: {
type: 'integer',
label: 'Version',
minimum: 0,
maximum: 15,
decode: (): void => {
this.instance.version.setValue(this.readBits(0, 1, 0, 4))
if (this.instance.version.getValue() !== 4) this.recordError(this.instance.version.getPath(), 'IPv4 version should be 4')
},
encode: (): void => {
let version: number = this.instance.version.getValue(4)
version = version > 15 ? 15 : version
version = version < 0 ? 0 : version
if (version !== 4) this.recordError(this.instance.version.getPath(), 'IPv4 version should be 4')
this.instance.version.setValue(version)
this.writeBits(0, 1, 0, 4, version)
}
},
hdrLen: {
type: 'integer',
label: 'Header Length',
minimum: 20,
maximum: 60,
decode: (): void => {
this.instance.hdrLen.setValue(this.readBits(0, 1, 4, 4) * 4)
},
encode: (): void => {
let headerLength: number = this.instance.hdrLen.getValue(0)
if (headerLength) headerLength = headerLength ? Math.floor(headerLength / 4) : 0
this.writeBits(0, 1, 4, 4, headerLength)
if (!headerLength) this.addPostSelfEncodeHandler((): void => {
this.instance.hdrLen.setValue(this.length)
this.writeBits(0, 1, 4, 4, Math.floor(this.length / 4))
}, 10)
}
},
dsfield: {
type: 'object',
label: 'Differentiated Services Field',
properties: {
dscp: {
type: 'integer',
minimum: 0,
maximum: 63,
label: 'Differentiated Services Codepoint',
decode: (): void => {
this.instance.dsfield.dscp.setValue(this.readBits(1, 1, 0, 6))
},
encode: (): void => {
const dscp: number = this.instance.dsfield.dscp.getValue(0)
this.instance.dsfield.dscp.setValue(dscp)
this.writeBits(1, 1, 0, 6, dscp)
}
},
ecn: {
type: 'integer',
label: 'Explicit Congestion Notification',
decode: (): void => {
this.instance.dsfield.ecn.setValue(this.readBits(1, 1, 6, 2))
},
encode: (): void => {
const ecn: number = this.instance.dsfield.ecn.getValue(0)
this.instance.dsfield.ecn.setValue(ecn)
this.writeBits(1, 1, 6, 2, ecn)
}
}
}
},
length: {
type: 'integer',
label: 'Total Length',
minimum: 0,
maximum: 65535,
decode: (): void => {
this.instance.length.setValue(BufferToUInt16(this.readBytes(2, 2)))
},
encode: (): void => {
//This field's real value needs down stream codec invoke recode to fill
const length: number = this.instance.length.getValue(0)
if (length) {
this.writeBytes(2, UInt16ToBuffer(length))
} else {
this.writeBytes(2, UInt16ToBuffer(length))
this.addPostPacketEncodeHandler((): void => {
let startCount: boolean = false
let totalLength: number = 0
this.codecModules.forEach((codecModule: CodecModule): void => {
if (codecModule === this) startCount = true
if (startCount) totalLength += codecModule.length
})
this.instance.length.setValue(totalLength)
this.writeBytes(2, UInt16ToBuffer(totalLength))
}, 1)
}
}
},
id: {
type: 'integer',
label: 'Identification',
minimum: 0,
maximum: 65535,
decode: (): void => {
this.instance.id.setValue(BufferToUInt16(this.readBytes(4, 2)))
},
encode: (): void => {
const id: number = this.instance.id.getValue(0, (nodePath: string): void => this.recordError(nodePath, 'Not Found'))
this.instance.id.setValue(id)
this.writeBytes(4, UInt16ToBuffer(id))
}
},
flags: {
type: 'object',
label: 'Flags',
properties: {
rb: {
type: 'integer',
enum: [0, 1],
label: 'Reserved bit',
decode: (): void => {
this.instance.flags.rb.setValue(this.readBits(6, 1, 0, 1))
},
encode: (): void => {
const rb: number = this.instance.flags.rb.getValue(0, (nodePath: string): void => this.recordError(nodePath, 'Not Found'))
this.instance.flags.rb.setValue(rb)
this.writeBits(6, 1, 0, 1, rb)
}
},
df: {
type: 'integer',
enum: [0, 1],
label: 'Don\'t fragment',
decode: (): void => {
this.instance.flags.df.setValue(this.readBits(6, 1, 1, 1))
},
encode: (): void => {
const df: number = this.instance.flags.df.getValue(1, (nodePath: string): void => this.recordError(nodePath, 'Not Found'))
this.instance.flags.df.setValue(df)
this.writeBits(6, 1, 1, 1, df)
}
},
mf: {
type: 'integer',
enum: [0, 1],
label: 'More fragments',
decode: (): void => {
this.instance.flags.mf.setValue(this.readBits(6, 1, 2, 1))
},
encode: (): void => {
const mf: number = this.instance.flags.mf.getValue(0, (nodePath: string): void => this.recordError(nodePath, 'Not Found'))
this.instance.flags.mf.setValue(mf)
this.writeBits(6, 1, 2, 1, mf)
}
}
}
},
fragOffset: {
type: 'integer',
minimum: 0,
maximum: 8191,
label: 'Fragment Offset',
decode: (): void => {
this.instance.fragOffset.setValue(this.readBits(6, 2, 3, 13))
},
encode: (): void => {
const fragOffset: number = this.instance.fragOffset.getValue(0, (nodePath: string): void => this.recordError(nodePath, 'Not Found'))
this.instance.fragOffset.setValue(fragOffset)
this.writeBits(6, 2, 3, 13, fragOffset)
}
},
ttl: {
type: 'integer',
minimum: 0,
maximum: 255,
label: 'Time to Live',
decode: (): void => {
this.instance.ttl.setValue(BufferToUInt8(this.readBytes(8, 1)))
},
encode: (): void => {
const ttl: number = this.instance.ttl.getValue(0, (nodePath: string): void => this.recordError(nodePath, 'Not Found'))
this.instance.ttl.setValue(ttl)
this.writeBytes(8, UInt8ToBuffer(ttl))
}
},
protocol: {
type: 'integer',
label: 'Protocol',
minimum: 0,
maximum: 255,
decode: (): void => {
this.instance.protocol.setValue(BufferToUInt8(this.readBytes(9, 1)))
},
encode: (): void => {
const protocol: number = this.instance.protocol.getValue(0, (nodePath: string): void => this.recordError(nodePath, 'Not Found'))
this.instance.protocol.setValue(protocol)
this.writeBytes(9, UInt8ToBuffer(protocol))
}
},
checksum: {
type: 'integer',
label: 'Header Checksum',
minimum: 0,
maximum: 65535,
decode: (): void => {
this.instance.checksum.setValue(BufferToUInt16(this.readBytes(10, 2)))
},
encode: (): void => {
let checksum: number = this.instance.checksum.getValue(0)
checksum = checksum > 65535 ? 65535 : checksum
checksum = checksum < 0 ? 0 : checksum
if (checksum) {
this.instance.checksum.setValue(checksum)
this.writeBytes(10, UInt16ToBuffer(checksum))
} else {
this.instance.checksum.setValue(checksum)
this.writeBytes(10, UInt16ToBuffer(checksum))
this.addPostPacketEncodeHandler((): void => {
const calcChecksum: number = this.calculateIPv4Checksum(this.packet.subarray(this.startPos, this.endPos))
this.instance.checksum.setValue(calcChecksum)
this.writeBytes(10, UInt16ToBuffer(calcChecksum))
}, 2)
}
}
},
sip: {
type: 'string',
label: 'Source Address',
minLength: 7,
maxLength: 15,
contentEncoding: StringContentEncodingEnum.IPv4,
decode: (): void => {
const sipBuffer: Buffer = this.readBytes(12, 4)
this.instance.sip.setValue(BufferToIPv4(sipBuffer))
},
encode: (): void => {
const sipStr: string = this.instance.sip.getValue('0.0.0.0', (nodePath: string): void => this.recordError(nodePath, 'Not Found'))
this.instance.sip.setValue(sipStr)
this.writeBytes(12, IPv4ToBuffer(sipStr))
}
},
dip: {
type: 'string',
minLength: 7,
maxLength: 15,
label: 'Destination Address',
contentEncoding: StringContentEncodingEnum.IPv4,
decode: (): void => {
const dipBuffer: Buffer = this.readBytes(16, 4)
this.instance.dip.setValue(BufferToIPv4(dipBuffer))
},
encode: (): void => {
const dipStr: string = this.instance.dip.getValue('0.0.0.0', (nodePath: string): void => this.recordError(nodePath, 'Not Found'))
this.instance.dip.setValue(dipStr)
this.writeBytes(16, IPv4ToBuffer(dipStr))
}
},
options: {
type: 'string',
label: 'Options',
minLength: 0,
maxLength: 40 * 2,
contentEncoding: StringContentEncodingEnum.HEX,
decode: (): void => {
if (this.length < (this.instance.hdrLen.getValue())) {
this.instance.options.setValue(BufferToHex(this.readBytes(this.length, (this.instance.hdrLen.getValue()) - this.length)))
}
},
encode: (): void => {
if (!this.instance.options.isUndefined()) {
let optionsBuffer: Buffer = HexToBuffer(this.instance.options.getValue(''))
if (optionsBuffer.length > 40) optionsBuffer = optionsBuffer.subarray(0, 40)
const estimateHdrLen: number = this.length + optionsBuffer.length
if (estimateHdrLen % 4) {
/**
* IPv4 Header should have a length that a multiple of 32 bits
* @see https://learningnetwork.cisco.com/s/question/0D53i00000Kt6hHCAR/padding-field-on-ipv4-header
*/
optionsBuffer = Buffer.concat([optionsBuffer, Buffer.from([0x00])])
}
this.writeBytes(this.length, optionsBuffer)
}
}
}
}
}
public readonly id: string = 'ipv4'
public readonly name: string = 'Internet Protocol Version 4'
public readonly nickname: string = 'IPv4'
public match(): boolean {
if (!this.prevCodecModule) return false
return this.prevCodecModule.instance.etherType.getValue() === UInt16ToHex(0x0800)
}
}