This commit is contained in:
parent
e8dcf31461
commit
69094524d1
@ -4,4 +4,5 @@ pipeline:
|
||||
commands:
|
||||
- node --version && npm --version
|
||||
- npm install
|
||||
- npm run build
|
||||
- npm test
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
node_modules
|
||||
*.pcapng
|
||||
*.bin
|
||||
lib/
|
4
lib/index.d.ts
vendored
4
lib/index.d.ts
vendored
@ -1,4 +0,0 @@
|
||||
import * as Types from "./types";
|
||||
import * as Request from "./request";
|
||||
declare const _default: typeof Types & typeof Request;
|
||||
export = _default;
|
@ -1,5 +0,0 @@
|
||||
"use strict";
|
||||
const Types = require("./types");
|
||||
const Request = require("./request");
|
||||
module.exports = Object.assign({}, Types, Request);
|
||||
//# sourceMappingURL=index.js.map
|
@ -1 +0,0 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,iCAAiC;AACjC,qCAAoC;AAEpC,iBAAU,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC"}
|
64
lib/request.d.ts
vendored
64
lib/request.d.ts
vendored
@ -1,64 +0,0 @@
|
||||
/// <reference types="node" />
|
||||
import { IMessage, IMessageHeader, IMessageQuestion, MessageRecourceRecord, ErrorCodes } from "./types";
|
||||
export declare function parseHeader(data: Buffer): IMessageHeader;
|
||||
export declare function parseQuestions(count: number, packet: Buffer): IMessageQuestion[];
|
||||
export declare function serializeName(name: string): Buffer;
|
||||
export declare class Request implements IMessage {
|
||||
private sendCallback;
|
||||
private max_size;
|
||||
private _header;
|
||||
readonly header: Header;
|
||||
private _questions;
|
||||
readonly questions: Question[];
|
||||
answers: RecourceRecord[];
|
||||
authorities: RecourceRecord[];
|
||||
additionals: RecourceRecord[];
|
||||
constructor(packet: Buffer, sendCallback: (packet: Buffer) => any, max_size?: number);
|
||||
error(error: ErrorCodes): void;
|
||||
noRecursion(): void;
|
||||
send(): void;
|
||||
serialize(): Buffer;
|
||||
}
|
||||
export declare class Header implements IMessageHeader {
|
||||
ID: number;
|
||||
QR: 0 | 1;
|
||||
OPCODE: number;
|
||||
AA: 0 | 1;
|
||||
TC: 0 | 1;
|
||||
RD: 0 | 1;
|
||||
RA: 0 | 1;
|
||||
Z: 0 | 1;
|
||||
AD: 0 | 1;
|
||||
CD: 0 | 1;
|
||||
RCODE: ErrorCodes;
|
||||
QDCOUNT: number;
|
||||
ANCOUNT: number;
|
||||
NSCOUNT: number;
|
||||
ARCOUNT: number;
|
||||
constructor(header: IMessageHeader);
|
||||
serialize(): Buffer;
|
||||
}
|
||||
export declare class Question implements IMessageQuestion {
|
||||
QNAME: string;
|
||||
QTYPE: number;
|
||||
QCLASS: number;
|
||||
constructor(question: IMessageQuestion);
|
||||
serialize(): Buffer;
|
||||
}
|
||||
export declare class RecourceRecord implements MessageRecourceRecord {
|
||||
constructor(data?: Partial<MessageRecourceRecord>);
|
||||
/**
|
||||
* This value can be set to identify if specific record is already set
|
||||
*/
|
||||
Identifier: string;
|
||||
NAME: string;
|
||||
private _TYPE;
|
||||
TYPE: number;
|
||||
private _CLASS;
|
||||
CLASS: number;
|
||||
private _TTL;
|
||||
TTL: number;
|
||||
RDATA: Buffer;
|
||||
readonly RDLENGTH: number;
|
||||
serialize(): Buffer;
|
||||
}
|
262
lib/request.js
262
lib/request.js
@ -1,262 +0,0 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const binary_parser_1 = require("binary-parser");
|
||||
const types_1 = require("./types");
|
||||
const headerParser = new binary_parser_1.Parser()
|
||||
.endianess("big")
|
||||
.uint16("ID")
|
||||
.bit1("QR")
|
||||
.bit4("OPCODE")
|
||||
.bit1("AA")
|
||||
.bit1("TC")
|
||||
.bit1("RD")
|
||||
.bit1("RA")
|
||||
.bit1("Z")
|
||||
.bit1("AD")
|
||||
.bit1("CD")
|
||||
.bit4("RCODE")
|
||||
.uint16("QDCOUNT")
|
||||
.uint16("ANCOUNT")
|
||||
.uint16("NSCOUNT")
|
||||
.uint16("ARCOUNT");
|
||||
function parseHeader(data) {
|
||||
try {
|
||||
return headerParser.parse(data);
|
||||
}
|
||||
catch (e) {
|
||||
throw new Error("Header parsing failed" + e.message);
|
||||
}
|
||||
}
|
||||
exports.parseHeader = parseHeader;
|
||||
const labelParser = new binary_parser_1.Parser()
|
||||
.endianess("big")
|
||||
.uint8("dataLength")
|
||||
.string("name", {
|
||||
length: "dataLength",
|
||||
encoding: "ascii"
|
||||
});
|
||||
const questionParser = new binary_parser_1.Parser()
|
||||
.endianess("big")
|
||||
.array("QNAME", {
|
||||
type: labelParser,
|
||||
readUntil: (item, buffer) => {
|
||||
if (item.dataLength <= 0)
|
||||
return true;
|
||||
},
|
||||
formatter: (value) => {
|
||||
return value.map(e => e.name).join(".").slice(0, -1);
|
||||
}
|
||||
})
|
||||
.uint16("QTYPE")
|
||||
.uint16("QCLASS");
|
||||
function parseQuestions(count, packet) {
|
||||
try {
|
||||
return new binary_parser_1.Parser()
|
||||
.endianess("big")
|
||||
.array("questions", {
|
||||
type: questionParser,
|
||||
length: count
|
||||
}).parse(packet).questions;
|
||||
}
|
||||
catch (e) {
|
||||
throw new Error("Question parsing failed" + e.message);
|
||||
}
|
||||
}
|
||||
exports.parseQuestions = parseQuestions;
|
||||
const MAX_LABEL_SIZE = 63;
|
||||
function serializeName(name) {
|
||||
let length = 0;
|
||||
let parts = name.split(".");
|
||||
parts.forEach(e => {
|
||||
// Length of part and byte that holds the length information
|
||||
if (e.length > MAX_LABEL_SIZE)
|
||||
throw new Error("Label to large");
|
||||
length += e.length + 1;
|
||||
});
|
||||
length += 1; //Adding last 0 length octet
|
||||
let data = Buffer.alloc(length);
|
||||
let offset = 0;
|
||||
parts.forEach(e => {
|
||||
data.writeUInt8(e.length, offset);
|
||||
offset++;
|
||||
data.write(e, offset, e.length);
|
||||
offset += e.length;
|
||||
});
|
||||
data.writeUInt8(0, offset);
|
||||
return data;
|
||||
}
|
||||
exports.serializeName = serializeName;
|
||||
class Request {
|
||||
constructor(packet, sendCallback, max_size = 512) {
|
||||
this.sendCallback = sendCallback;
|
||||
this.max_size = max_size;
|
||||
this.answers = [];
|
||||
this.authorities = [];
|
||||
this.additionals = [];
|
||||
let headerData = Buffer.alloc(12);
|
||||
packet.copy(headerData, 0, 0, 12);
|
||||
let bodyData = Buffer.alloc(packet.length - 12);
|
||||
packet.copy(bodyData, 0, 12, packet.length);
|
||||
this._header = new Header(parseHeader(headerData));
|
||||
this._header.AD = 0;
|
||||
this._header.RCODE = types_1.ErrorCodes.NoError;
|
||||
this._header.RA = this._header.RD;
|
||||
this._questions = parseQuestions(this._header.QDCOUNT, bodyData).map(e => new Question(e));
|
||||
}
|
||||
get header() {
|
||||
return Object.assign({}, this._header);
|
||||
}
|
||||
get questions() {
|
||||
return this._questions.map(e => Object.assign({}, e));
|
||||
}
|
||||
error(error) {
|
||||
if (this._header.RCODE === types_1.ErrorCodes.NoError)
|
||||
this._header.RCODE = error;
|
||||
}
|
||||
noRecursion() {
|
||||
this._header.RA = 0;
|
||||
}
|
||||
send() {
|
||||
this.sendCallback(this.serialize());
|
||||
}
|
||||
serialize() {
|
||||
this._header.AA = 1;
|
||||
this._header.ANCOUNT = this.answers.length;
|
||||
this._header.ARCOUNT = this.additionals.length;
|
||||
this._header.NSCOUNT = this.authorities.length;
|
||||
this._header.QR = 1;
|
||||
let questions = this._questions.map(e => e.serialize());
|
||||
let answers = this.answers.map(e => e.serialize());
|
||||
let authority = this.authorities.map(e => e.serialize());
|
||||
let additional = this.additionals.map(e => e.serialize());
|
||||
let length = 12;
|
||||
questions.forEach(e => length += e.length);
|
||||
answers.forEach(e => length += e.length);
|
||||
authority.forEach(e => length += e.length);
|
||||
additional.forEach(e => length += e.length);
|
||||
if (length > this.max_size) {
|
||||
this._header.TC = 1;
|
||||
//Will ignore data, that exceeds length
|
||||
length = this.max_size;
|
||||
}
|
||||
let header = this._header.serialize();
|
||||
let data = Buffer.alloc(length);
|
||||
let offset = 0;
|
||||
let append = (buffer) => {
|
||||
if (offset <= length) {
|
||||
buffer.copy(data, offset, 0, buffer.length);
|
||||
offset += buffer.length;
|
||||
}
|
||||
};
|
||||
append(header);
|
||||
questions.forEach(append);
|
||||
answers.forEach(append);
|
||||
authority.forEach(append);
|
||||
additional.forEach(append);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
exports.Request = Request;
|
||||
class Header {
|
||||
constructor(header) {
|
||||
for (let k in header) {
|
||||
this[k] = header[k];
|
||||
}
|
||||
}
|
||||
serialize() {
|
||||
let data = Buffer.alloc(12);
|
||||
data.writeUInt16BE(this.ID, 0);
|
||||
var f = 0x0000;
|
||||
f = f | (this.QR << 15);
|
||||
f = f | (this.OPCODE << 11);
|
||||
f = f | (this.AA << 10);
|
||||
f = f | (this.TC << 9);
|
||||
f = f | (this.RD << 8);
|
||||
f = f | (this.RA << 7);
|
||||
f = f | (this.Z << 6);
|
||||
f = f | (this.AD << 5);
|
||||
f = f | (this.CD << 4);
|
||||
f = f | this.RCODE;
|
||||
data.writeUInt16BE(f, 2);
|
||||
data.writeUInt16BE(this.QDCOUNT, 4);
|
||||
data.writeUInt16BE(this.ANCOUNT, 6);
|
||||
data.writeUInt16BE(this.NSCOUNT, 8);
|
||||
data.writeUInt16BE(this.ARCOUNT, 10);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
exports.Header = Header;
|
||||
class Question {
|
||||
constructor(question) {
|
||||
for (let k in question) {
|
||||
this[k] = question[k];
|
||||
}
|
||||
}
|
||||
serialize() {
|
||||
let qname = serializeName(this.QNAME);
|
||||
let data = Buffer.alloc(qname.length + 4);
|
||||
qname.copy(data, 0, 0, qname.length);
|
||||
let offset = qname.length;
|
||||
data.writeUInt16BE(this.QTYPE, offset);
|
||||
offset += 2;
|
||||
data.writeUInt16BE(this.QCLASS, offset);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
exports.Question = Question;
|
||||
class RecourceRecord {
|
||||
constructor(data) {
|
||||
if (data) {
|
||||
for (let key in data) {
|
||||
this[key] = data[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
set TYPE(value) {
|
||||
if (value < 0 || value > 65535)
|
||||
throw new TypeError("TYPE Range: 0 - 65.535");
|
||||
this._TYPE = value;
|
||||
}
|
||||
get TYPE() {
|
||||
return this._TYPE;
|
||||
}
|
||||
set CLASS(value) {
|
||||
if (value < 0 || value > 65535)
|
||||
throw new TypeError("CLASS Range: 0 - 65.535");
|
||||
this._CLASS = value;
|
||||
}
|
||||
get CLASS() {
|
||||
return this._CLASS;
|
||||
}
|
||||
set TTL(value) {
|
||||
if (value < 0 || value > 4294967295)
|
||||
throw new TypeError("TTL Range: 0 - 4.294.967.295");
|
||||
this._TTL = value;
|
||||
}
|
||||
get TTL() {
|
||||
return this._TTL;
|
||||
}
|
||||
get RDLENGTH() {
|
||||
return this.RDATA.length;
|
||||
}
|
||||
serialize() {
|
||||
// TODO: Implement compression
|
||||
let name = serializeName(this.NAME);
|
||||
let rdata = this.RDATA;
|
||||
let data = Buffer.alloc(name.length + 10 + rdata.length); // For TYPE, CLASS, TTL, RLENGTH
|
||||
name.copy(data, 0, 0, name.length);
|
||||
let offset = name.length;
|
||||
data.writeUInt16BE(this.TYPE, offset);
|
||||
offset += 2;
|
||||
data.writeUInt16BE(this.CLASS, offset);
|
||||
offset += 2;
|
||||
data.writeUInt32BE(this._TTL, offset);
|
||||
offset += 4;
|
||||
data.writeUInt16BE(rdata.length, offset);
|
||||
offset += 2;
|
||||
rdata.copy(data, offset, 0, rdata.length);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
exports.RecourceRecord = RecourceRecord;
|
||||
//# sourceMappingURL=request.js.map
|
File diff suppressed because one or more lines are too long
1
lib/test.d.ts
vendored
1
lib/test.d.ts
vendored
@ -1 +0,0 @@
|
||||
export {};
|
361
lib/test.js
361
lib/test.js
@ -1,361 +0,0 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const chai_1 = require("chai");
|
||||
const request_1 = require("./request");
|
||||
const types_1 = require("./types");
|
||||
function fromHex(data) {
|
||||
return Buffer.from(data.replace(/\s/g, ""), "hex");
|
||||
}
|
||||
describe("parser", function () {
|
||||
describe("header", function () {
|
||||
describe("header parser", function () {
|
||||
let should_templ = {
|
||||
ID: 0,
|
||||
QR: 0,
|
||||
OPCODE: 0,
|
||||
AA: 0,
|
||||
TC: 0,
|
||||
RD: 0,
|
||||
RA: 0,
|
||||
Z: 0,
|
||||
AD: 0,
|
||||
CD: 0,
|
||||
RCODE: 0,
|
||||
QDCOUNT: 0,
|
||||
ANCOUNT: 0,
|
||||
ARCOUNT: 0,
|
||||
NSCOUNT: 0
|
||||
};
|
||||
let tests = [
|
||||
{
|
||||
name: "Testing ID field",
|
||||
data: "0001 0000 0000 0000 0000 0000",
|
||||
fields: {
|
||||
ID: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Testing ID field with max value",
|
||||
data: "FFFF 0000 0000 0000 0000 0000",
|
||||
fields: {
|
||||
ID: 65535
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Testing QR field",
|
||||
data: "0000 8000 0000 0000 0000 0000",
|
||||
fields: {
|
||||
QR: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Testing OPCODE field value 2",
|
||||
data: "0000 1000 0000 0000 0000 0000",
|
||||
fields: {
|
||||
OPCODE: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Testing OPCODE field value 1",
|
||||
data: "0000 0800 0000 0000 0000 0000",
|
||||
fields: {
|
||||
OPCODE: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Testing AA field",
|
||||
data: "0000 0400 0000 0000 0000 0000",
|
||||
fields: {
|
||||
AA: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Testing TC field",
|
||||
data: "0000 0200 0000 0000 0000 0000",
|
||||
fields: {
|
||||
TC: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Testing RD field",
|
||||
data: "0000 0100 0000 0000 0000 0000",
|
||||
fields: {
|
||||
RD: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Testing RCODE field",
|
||||
data: "0000 0002 0000 0000 0000 0000",
|
||||
fields: {
|
||||
RCODE: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Testing QDCOUNT field max value",
|
||||
data: "0000 0000 FFFF 0000 0000 0000",
|
||||
fields: {
|
||||
QDCOUNT: 65535
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Testing ANCOUNT field max value",
|
||||
data: "0000 0000 0000 FFFF 0000 0000",
|
||||
fields: {
|
||||
ANCOUNT: 65535
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Testing NSCOUNT field max value",
|
||||
data: "0000 0000 0000 0000 FFFF 0000",
|
||||
fields: {
|
||||
NSCOUNT: 65535
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Testing ARCOUNT field max value",
|
||||
data: "0000 0000 0000 0000 0000 FFFF",
|
||||
fields: {
|
||||
ARCOUNT: 65535
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Testing all Flags and Values max",
|
||||
data: "FFFF FFFF FFFF FFFF FFFF FFFF",
|
||||
fields: {
|
||||
ID: 65535,
|
||||
QR: 1,
|
||||
OPCODE: 15,
|
||||
AA: 1,
|
||||
TC: 1,
|
||||
RD: 1,
|
||||
RA: 1,
|
||||
Z: 1,
|
||||
AD: 1,
|
||||
CD: 1,
|
||||
RCODE: 15,
|
||||
QDCOUNT: 65535,
|
||||
ANCOUNT: 65535,
|
||||
NSCOUNT: 65535,
|
||||
ARCOUNT: 65535
|
||||
}
|
||||
}
|
||||
];
|
||||
tests.forEach(function (e) {
|
||||
it(e.name, function () {
|
||||
let testdata = fromHex(e.data);
|
||||
let should = Object.assign({}, should_templ, e.fields); // Build in "clone" function
|
||||
let header = request_1.parseHeader(testdata);
|
||||
chai_1.assert.hasAllKeys(header, Object.keys(should), "Parsed header is missing some fields");
|
||||
chai_1.assert.deepEqual(header, should, "Parsed header has not expected values!");
|
||||
});
|
||||
});
|
||||
});
|
||||
describe("header serializer", function () {
|
||||
let empty_header = {
|
||||
ID: 0,
|
||||
QR: 0,
|
||||
OPCODE: 0,
|
||||
AA: 0,
|
||||
TC: 0,
|
||||
RD: 0,
|
||||
RA: 0,
|
||||
Z: 0,
|
||||
AD: 0,
|
||||
CD: 0,
|
||||
RCODE: 0,
|
||||
QDCOUNT: 0,
|
||||
ANCOUNT: 0,
|
||||
ARCOUNT: 0,
|
||||
NSCOUNT: 0
|
||||
};
|
||||
let tests = [
|
||||
{
|
||||
name: "Fill header with 0s",
|
||||
result: "0000 0000 0000 0000 0000 0000",
|
||||
values: {}
|
||||
},
|
||||
{
|
||||
name: "Set header id to 5",
|
||||
result: "0005 0000 0000 0000 0000 0000",
|
||||
values: {
|
||||
ID: 5
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Set QR",
|
||||
result: "0000 8000 0000 0000 0000 0000",
|
||||
values: {
|
||||
QR: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Set OPCODE",
|
||||
result: "0000 7800 0000 0000 0000 0000",
|
||||
values: {
|
||||
OPCODE: 15
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Set AA",
|
||||
result: "0000 0400 0000 0000 0000 0000",
|
||||
values: {
|
||||
AA: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Set TC",
|
||||
result: "0000 0200 0000 0000 0000 0000",
|
||||
values: {
|
||||
TC: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Set RD",
|
||||
result: "0000 0100 0000 0000 0000 0000",
|
||||
values: {
|
||||
RD: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Set RA",
|
||||
result: "0000 0080 0000 0000 0000 0000",
|
||||
values: {
|
||||
RA: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Set Z",
|
||||
result: "0000 0040 0000 0000 0000 0000",
|
||||
values: {
|
||||
Z: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Set AD",
|
||||
result: "0000 0020 0000 0000 0000 0000",
|
||||
values: {
|
||||
AD: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Set CD",
|
||||
result: "0000 0010 0000 0000 0000 0000",
|
||||
values: {
|
||||
CD: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Set RCODE",
|
||||
result: "0000 000F 0000 0000 0000 0000",
|
||||
values: {
|
||||
RCODE: 15
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Set QDCOUNT",
|
||||
result: "0000 0000 FFFF 0000 0000 0000",
|
||||
values: {
|
||||
QDCOUNT: 65535
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Set ANCOUNT",
|
||||
result: "0000 0000 0000 FFFF 0000 0000",
|
||||
values: {
|
||||
ANCOUNT: 65535
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Set NSCOUNT",
|
||||
result: "0000 0000 0000 0000 FFFF 0000",
|
||||
values: {
|
||||
NSCOUNT: 65535
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Set ARCOUNT",
|
||||
result: "0000 0000 0000 0000 0000 FFFF",
|
||||
values: {
|
||||
ARCOUNT: 65535
|
||||
}
|
||||
},
|
||||
];
|
||||
tests.forEach(function (e) {
|
||||
it(e.name, function () {
|
||||
let header = Object.assign({}, empty_header, e.values);
|
||||
let serialized = new request_1.Header(header).serialize();
|
||||
chai_1.assert.equal(serialized.toString("hex"), e.result.replace(/\s/g, "").toLowerCase(), "Header serialization failed");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe("question", function () {
|
||||
let questionData = fromHex("0474 6573 7407 6578 616d 706c 6503 636f 6d00 0001 0001");
|
||||
let questionObj = {
|
||||
QNAME: "test.example.com",
|
||||
QTYPE: types_1.QueryTypes.A,
|
||||
QCLASS: 1
|
||||
};
|
||||
it("check question parser with one question", function () {
|
||||
let res = request_1.parseQuestions(1, questionData);
|
||||
let should = [questionObj];
|
||||
chai_1.assert.deepEqual(res, should, "Question parser does not parse input correctly");
|
||||
});
|
||||
it("check question serialization", function () {
|
||||
let res = new request_1.Question(questionObj).serialize();
|
||||
chai_1.assert.equal(res.toString("hex"), questionData.toString("hex"), "Query serializer does not serialite correctly");
|
||||
});
|
||||
});
|
||||
it("recource record serialization", function () {
|
||||
let should = "07 6578616D706C65 03 636f6D 00 0001 0001 00000640 0004 0A000001";
|
||||
let rr = new request_1.RecourceRecord();
|
||||
rr.CLASS = 1;
|
||||
rr.NAME = "example.com";
|
||||
rr.TTL = 1600;
|
||||
rr.TYPE = 1;
|
||||
rr.RDATA = fromHex("0A 00 00 01");
|
||||
let res = rr.serialize().toString("hex");
|
||||
chai_1.assert.equal(res, should.replace(/\s/g, "").toLowerCase(), "Serialization not working properly");
|
||||
});
|
||||
it("recource record constructor value assign", function () {
|
||||
let should = "07 6578616D706C65 03 636f6D 00 0001 0001 00000640 0004 0A000001";
|
||||
let rr = new request_1.RecourceRecord({
|
||||
CLASS: 1,
|
||||
NAME: "example.com",
|
||||
TTL: 1600,
|
||||
TYPE: 1,
|
||||
RDATA: fromHex("0A 00 00 01")
|
||||
});
|
||||
let res = rr.serialize().toString("hex");
|
||||
chai_1.assert.equal(res, should.replace(/\s/g, "").toLowerCase(), "Serialization not working properly");
|
||||
});
|
||||
it("full response serialization", function () {
|
||||
let reqData = fromHex("E835 0100 0001 0000 0000 0000 07 6578616D706c65 03636F6D 00 0001 0001");
|
||||
let should = "E835 8580 0001000100000000 076578616D706C6503636F6D0000010001 07 6578616D706C65 03 636F6D 00 0001 0001 0000 0640 0004 0A000001";
|
||||
let request = new request_1.Request(reqData, () => null);
|
||||
let rr = new request_1.RecourceRecord();
|
||||
rr.CLASS = 1;
|
||||
rr.NAME = "example.com";
|
||||
rr.TTL = 1600;
|
||||
rr.TYPE = 1;
|
||||
rr.RDATA = fromHex("0A 00 00 01");
|
||||
request.answers.push(rr);
|
||||
let data = request.serialize();
|
||||
chai_1.assert.equal(data.toString("hex"), should.replace(/\s/g, "").toLowerCase(), "Whole packet serialization failed");
|
||||
});
|
||||
// it("full response serialization benchmark", function () {
|
||||
// let reqData = fromHex("E835 0100 0001 0000 0000 0000 07 6578616D706c65 03636F6D 00 0001 0001");
|
||||
// for (let i = 0; i < 100; i++) {
|
||||
// let request = new Request(reqData, () => null)
|
||||
// let rr = new RecourceRecord()
|
||||
// rr.CLASS = 1
|
||||
// rr.NAME = "example.com"
|
||||
// rr.TTL = 1600
|
||||
// rr.TYPE = 1
|
||||
// rr.RDATA = fromHex("0A 00 00 01")
|
||||
// request.answers.push(rr)
|
||||
// request.serialize()
|
||||
// }
|
||||
// })
|
||||
});
|
||||
//# sourceMappingURL=test.js.map
|
File diff suppressed because one or more lines are too long
268
lib/types.d.ts
vendored
268
lib/types.d.ts
vendored
@ -1,268 +0,0 @@
|
||||
/// <reference types="node" />
|
||||
export declare enum ErrorCodes {
|
||||
/**
|
||||
* No error
|
||||
*/
|
||||
NoError = 0,
|
||||
/**
|
||||
* Format error - unable to interpret query
|
||||
*/
|
||||
FormatError = 1,
|
||||
/**
|
||||
* Server failure - internal problem
|
||||
*/
|
||||
ServerFailure = 2,
|
||||
/**
|
||||
* Name error - Only for authorative name server, domain name of query does not exist
|
||||
*/
|
||||
NameError = 3,
|
||||
/**
|
||||
* Not implemented - Request not supported
|
||||
*/
|
||||
NotImplemented = 4,
|
||||
/**
|
||||
* Refused - Nameserver refuses request
|
||||
*/
|
||||
Refused = 5,
|
||||
}
|
||||
export declare enum QueryTypes {
|
||||
/**
|
||||
* IPv4 address
|
||||
*/
|
||||
A = 1,
|
||||
/**
|
||||
* Nameserver
|
||||
*/
|
||||
NS = 2,
|
||||
/**
|
||||
* Obsolete
|
||||
*/
|
||||
MD = 3,
|
||||
/**
|
||||
* Obsolete
|
||||
*/
|
||||
MF = 4,
|
||||
/**
|
||||
* Alias
|
||||
*/
|
||||
CNAME = 5,
|
||||
/**
|
||||
* Start of authority
|
||||
*/
|
||||
SOA = 6,
|
||||
/**
|
||||
* Experimental
|
||||
*/
|
||||
MB = 7,
|
||||
/**
|
||||
* Experimental
|
||||
*/
|
||||
MG = 8,
|
||||
/**
|
||||
* Experimental
|
||||
*/
|
||||
MR = 9,
|
||||
/**
|
||||
* Experimental
|
||||
*/
|
||||
NULL = 10,
|
||||
/**
|
||||
* Service description
|
||||
*/
|
||||
WKS = 11,
|
||||
/**
|
||||
* Reverse entry (inaddr.arpa)
|
||||
*/
|
||||
PTR = 12,
|
||||
/**
|
||||
* Host information
|
||||
*/
|
||||
HINFO = 13,
|
||||
/**
|
||||
* Mailbox / Mail-list information
|
||||
*/
|
||||
MINFO = 14,
|
||||
/**
|
||||
* Mail exchange
|
||||
*/
|
||||
MX = 15,
|
||||
/**
|
||||
* Text strings
|
||||
*/
|
||||
TXT = 16,
|
||||
/**
|
||||
* IPv6 address
|
||||
*/
|
||||
AAAA = 28,
|
||||
/**
|
||||
* SRV records
|
||||
*/
|
||||
SRV = 33,
|
||||
/**
|
||||
* Request to transfer entire zone
|
||||
*/
|
||||
AXFR = 252,
|
||||
/**
|
||||
* Request for mailbox related records
|
||||
*/
|
||||
MAILA = 254,
|
||||
/**
|
||||
* Request for mail agend RRs
|
||||
*/
|
||||
MAILB = 253,
|
||||
/**
|
||||
* Any class
|
||||
*/
|
||||
ANY = 255,
|
||||
}
|
||||
export interface IMessageHeader {
|
||||
/**
|
||||
* A 16 bit identifier assigned by the program that
|
||||
* generates any kind of query. This identifier is copied
|
||||
* the corresponding reply and can be used by the requester
|
||||
* to match up replies to outstanding queries.
|
||||
*/
|
||||
ID: number;
|
||||
/**
|
||||
* Defines if query or response
|
||||
*/
|
||||
QR: 0 | 1;
|
||||
/**
|
||||
* 4 Bit code, that defines type of query.
|
||||
* 0 Standard query
|
||||
* 1 Inverse query
|
||||
* 2 Server status request
|
||||
* 3-15 reserved for future use
|
||||
*/
|
||||
OPCODE: number;
|
||||
/**
|
||||
* Authorative Answer - only valid in responses and
|
||||
* specifies that the responding name server is an
|
||||
* authority for the domain name in question section
|
||||
*/
|
||||
AA: 0 | 1;
|
||||
/**
|
||||
* Truncation - specifies that his message was truncated doe to
|
||||
* length grater than permitted on the transaction channel
|
||||
*
|
||||
* WARNING: NOT IMPLEMENTED IN THIS APPLICATION
|
||||
*/
|
||||
TC: 0 | 1;
|
||||
/**
|
||||
* Recursion Desired - set in query and copied to response
|
||||
* if is set, directs name server to pursue the query recursively
|
||||
*
|
||||
* WARNING: NOT IMPLEMENTED IN THIS APPLICATION
|
||||
*/
|
||||
RD: 0 | 1;
|
||||
/**
|
||||
* Recursion Available - will be cleared in response to
|
||||
* show the client that recursion is not available
|
||||
*/
|
||||
RA: 0 | 1;
|
||||
/**
|
||||
* Reserved for future usage, must be 0 in all queries
|
||||
*/
|
||||
Z: 0 | 1;
|
||||
AD: 0 | 1;
|
||||
CD: 0 | 1;
|
||||
/**
|
||||
* Response Code - 4 bit field is part of response
|
||||
*
|
||||
* 0 No error condition
|
||||
* 1 Format error - unable to interpret query
|
||||
* 2 Server failure - internal problem
|
||||
* 3 Name error - Only for authorative name server, domain name of query does not exist
|
||||
* 4 Not implemented - Request not supported
|
||||
* 5 Refused - Nameserver refuses request
|
||||
* 6-15 Reserved for future usage
|
||||
*/
|
||||
RCODE: ErrorCodes;
|
||||
/**
|
||||
* Number of entries in question section
|
||||
* uint16
|
||||
*/
|
||||
QDCOUNT: number;
|
||||
/**
|
||||
* Number of entries in answer section
|
||||
* uint16
|
||||
*/
|
||||
ANCOUNT: number;
|
||||
/**
|
||||
* Number of resource records in authority records section
|
||||
* uint16
|
||||
*/
|
||||
NSCOUNT: number;
|
||||
/**
|
||||
* Number of resource records in additional records section
|
||||
* uint16
|
||||
*/
|
||||
ARCOUNT: number;
|
||||
}
|
||||
export interface IMessageQuestion {
|
||||
/**
|
||||
* Domain name represented as sequence of labels
|
||||
* Each label consists of a length octed followed
|
||||
* by that number of octeds
|
||||
*/
|
||||
QNAME: string;
|
||||
/**
|
||||
* Two octed code wich specifies the type of the query.
|
||||
*/
|
||||
QTYPE: number;
|
||||
/**
|
||||
* Two octed code that specifies the class of the Query
|
||||
* IS for internet
|
||||
* WARNING: ONLY IN IS SUPPORTED BY THIS APPLICATION
|
||||
*/
|
||||
QCLASS: number;
|
||||
}
|
||||
export interface MessageRecourceRecord {
|
||||
/**
|
||||
* Domain name to wich resource record pertains
|
||||
*/
|
||||
NAME: string;
|
||||
/**
|
||||
* Two octets containing TT type code.
|
||||
* Specifies meaning of data in RDATA field
|
||||
*
|
||||
* uint16
|
||||
*/
|
||||
TYPE: number;
|
||||
/**
|
||||
* Two octets specifying class of RDATA field
|
||||
*
|
||||
* uint16
|
||||
*/
|
||||
CLASS: number;
|
||||
/**
|
||||
* Specifies Time Interval (in seconds) that the record
|
||||
* may be cached before discarded.
|
||||
* Zero values are not cached
|
||||
*
|
||||
* uint32
|
||||
*/
|
||||
TTL: number;
|
||||
/**
|
||||
* Length of RDATA field
|
||||
*
|
||||
* uint16
|
||||
*/
|
||||
RDLENGTH: number;
|
||||
/**
|
||||
* a variable length string of ectets taht describes
|
||||
* the resource. The format is defined by TYPE and CLASS
|
||||
* field.
|
||||
*
|
||||
* If TYPE is A and CLASS is IN, RDATA is a 4 octet
|
||||
* ARPA internet address.
|
||||
*/
|
||||
RDATA: Buffer;
|
||||
}
|
||||
export interface IMessage {
|
||||
header: IMessageHeader;
|
||||
questions: IMessageQuestion[];
|
||||
answers: MessageRecourceRecord[];
|
||||
authorities: MessageRecourceRecord[];
|
||||
additionals: MessageRecourceRecord[];
|
||||
}
|
121
lib/types.js
121
lib/types.js
@ -1,121 +0,0 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
var ErrorCodes;
|
||||
(function (ErrorCodes) {
|
||||
/**
|
||||
* No error
|
||||
*/
|
||||
ErrorCodes[ErrorCodes["NoError"] = 0] = "NoError";
|
||||
/**
|
||||
* Format error - unable to interpret query
|
||||
*/
|
||||
ErrorCodes[ErrorCodes["FormatError"] = 1] = "FormatError";
|
||||
/**
|
||||
* Server failure - internal problem
|
||||
*/
|
||||
ErrorCodes[ErrorCodes["ServerFailure"] = 2] = "ServerFailure";
|
||||
/**
|
||||
* Name error - Only for authorative name server, domain name of query does not exist
|
||||
*/
|
||||
ErrorCodes[ErrorCodes["NameError"] = 3] = "NameError";
|
||||
/**
|
||||
* Not implemented - Request not supported
|
||||
*/
|
||||
ErrorCodes[ErrorCodes["NotImplemented"] = 4] = "NotImplemented";
|
||||
/**
|
||||
* Refused - Nameserver refuses request
|
||||
*/
|
||||
ErrorCodes[ErrorCodes["Refused"] = 5] = "Refused";
|
||||
})(ErrorCodes = exports.ErrorCodes || (exports.ErrorCodes = {}));
|
||||
var QueryTypes;
|
||||
(function (QueryTypes) {
|
||||
/**
|
||||
* IPv4 address
|
||||
*/
|
||||
QueryTypes[QueryTypes["A"] = 1] = "A";
|
||||
/**
|
||||
* Nameserver
|
||||
*/
|
||||
QueryTypes[QueryTypes["NS"] = 2] = "NS";
|
||||
/**
|
||||
* Obsolete
|
||||
*/
|
||||
QueryTypes[QueryTypes["MD"] = 3] = "MD";
|
||||
/**
|
||||
* Obsolete
|
||||
*/
|
||||
QueryTypes[QueryTypes["MF"] = 4] = "MF";
|
||||
/**
|
||||
* Alias
|
||||
*/
|
||||
QueryTypes[QueryTypes["CNAME"] = 5] = "CNAME";
|
||||
/**
|
||||
* Start of authority
|
||||
*/
|
||||
QueryTypes[QueryTypes["SOA"] = 6] = "SOA";
|
||||
/**
|
||||
* Experimental
|
||||
*/
|
||||
QueryTypes[QueryTypes["MB"] = 7] = "MB";
|
||||
/**
|
||||
* Experimental
|
||||
*/
|
||||
QueryTypes[QueryTypes["MG"] = 8] = "MG";
|
||||
/**
|
||||
* Experimental
|
||||
*/
|
||||
QueryTypes[QueryTypes["MR"] = 9] = "MR";
|
||||
/**
|
||||
* Experimental
|
||||
*/
|
||||
QueryTypes[QueryTypes["NULL"] = 10] = "NULL";
|
||||
/**
|
||||
* Service description
|
||||
*/
|
||||
QueryTypes[QueryTypes["WKS"] = 11] = "WKS";
|
||||
/**
|
||||
* Reverse entry (inaddr.arpa)
|
||||
*/
|
||||
QueryTypes[QueryTypes["PTR"] = 12] = "PTR";
|
||||
/**
|
||||
* Host information
|
||||
*/
|
||||
QueryTypes[QueryTypes["HINFO"] = 13] = "HINFO";
|
||||
/**
|
||||
* Mailbox / Mail-list information
|
||||
*/
|
||||
QueryTypes[QueryTypes["MINFO"] = 14] = "MINFO";
|
||||
/**
|
||||
* Mail exchange
|
||||
*/
|
||||
QueryTypes[QueryTypes["MX"] = 15] = "MX";
|
||||
/**
|
||||
* Text strings
|
||||
*/
|
||||
QueryTypes[QueryTypes["TXT"] = 16] = "TXT";
|
||||
/**
|
||||
* IPv6 address
|
||||
*/
|
||||
QueryTypes[QueryTypes["AAAA"] = 28] = "AAAA";
|
||||
/**
|
||||
* SRV records
|
||||
*/
|
||||
QueryTypes[QueryTypes["SRV"] = 33] = "SRV";
|
||||
/**
|
||||
* Request to transfer entire zone
|
||||
*/
|
||||
QueryTypes[QueryTypes["AXFR"] = 252] = "AXFR";
|
||||
/**
|
||||
* Request for mailbox related records
|
||||
*/
|
||||
QueryTypes[QueryTypes["MAILA"] = 254] = "MAILA";
|
||||
/**
|
||||
* Request for mail agend RRs
|
||||
*/
|
||||
QueryTypes[QueryTypes["MAILB"] = 253] = "MAILB";
|
||||
/**
|
||||
* Any class
|
||||
*/
|
||||
QueryTypes[QueryTypes["ANY"] = 255] = "ANY";
|
||||
})(QueryTypes = exports.QueryTypes || (exports.QueryTypes = {}));
|
||||
//# sourceMappingURL=types.js.map
|
@ -1 +0,0 @@
|
||||
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";;AAAA,IAAY,UA8BX;AA9BD,WAAY,UAAU;IACnB;;OAEG;IACH,iDAAW,CAAA;IAEX;;OAEG;IACH,yDAAW,CAAA;IAEX;;OAEG;IACH,6DAAa,CAAA;IAEb;;OAEG;IACH,qDAAS,CAAA;IAET;;OAEG;IACH,+DAAc,CAAA;IAEd;;OAEG;IACH,iDAAO,CAAA;AACV,CAAC,EA9BW,UAAU,GAAV,kBAAU,KAAV,kBAAU,QA8BrB;AAED,IAAY,UA8GX;AA9GD,WAAY,UAAU;IACnB;;OAEG;IACH,qCAAQ,CAAA;IAER;;OAEG;IACH,uCAAS,CAAA;IAET;;OAEG;IACH,uCAAS,CAAA;IAET;;OAEG;IACH,uCAAS,CAAA;IAET;;OAEG;IACH,6CAAY,CAAA;IAEZ;;OAEG;IACH,yCAAU,CAAA;IAEV;;OAEG;IACH,uCAAS,CAAA;IAET;;OAEG;IACH,uCAAS,CAAA;IAET;;OAEG;IACH,uCAAS,CAAA;IAET;;OAEG;IACH,4CAAW,CAAA;IAEX;;OAEG;IACH,0CAAU,CAAA;IAEV;;OAEG;IACH,0CAAU,CAAA;IAEV;;OAEG;IACH,8CAAY,CAAA;IAEZ;;OAEG;IACH,8CAAY,CAAA;IAEZ;;OAEG;IACH,wCAAS,CAAA;IAET;;OAEG;IACH,0CAAU,CAAA;IAEV;;OAEG;IACH,4CAAW,CAAA;IAEX;;OAEG;IACH,0CAAU,CAAA;IAEV;;OAEG;IACH,6CAAW,CAAA;IAEX;;OAEG;IACH,+CAAY,CAAA;IAEZ;;OAEG;IACH,+CAAY,CAAA;IAEZ;;OAEG;IACH,2CAAU,CAAA;AACb,CAAC,EA9GW,UAAU,GAAV,kBAAU,KAAV,kBAAU,QA8GrB"}
|
2814
package-lock.json
generated
Normal file
2814
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
21
package.json
21
package.json
@ -8,21 +8,24 @@
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"watch": "tsc --watch",
|
||||
"prepublish": "tsc",
|
||||
"test": "mocha src/test.ts --require ts-node/register",
|
||||
"testwatch": "mocha src/test.ts --require ts-node/register --watch --watch-extensions ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/binary-parser": "^1.3.0",
|
||||
"@types/chai": "^4.1.3",
|
||||
"@types/mocha": "^5.2.0",
|
||||
"@types/node": "^10.0.8",
|
||||
"chai": "^4.1.2",
|
||||
"mocha": "^5.1.1",
|
||||
"nodemon": "^1.17.4",
|
||||
"typescript": "^2.8.3",
|
||||
"ts-node": "^6.0.3"
|
||||
"@types/chai": "^4.1.7",
|
||||
"@types/lru-cache": "^4.1.1",
|
||||
"@types/mocha": "^5.2.5",
|
||||
"@types/node": "^10.12.11",
|
||||
"chai": "^4.2.0",
|
||||
"mocha": "^5.2.0",
|
||||
"nodemon": "^1.18.7",
|
||||
"ts-node": "^7.0.1",
|
||||
"typescript": "^3.2.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"binary-parser": "^1.3.2"
|
||||
"binary-parser": "^1.3.2",
|
||||
"lru-cache": "^5.1.1"
|
||||
}
|
||||
}
|
75
src/header.ts
Normal file
75
src/header.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import { IMessageHeader, ErrorCodes } from "./types";
|
||||
import { Serializeable } from "./serializeable";
|
||||
|
||||
export class Header implements IMessageHeader, Serializeable {
|
||||
ID: number;
|
||||
QR: 0 | 1;
|
||||
OPCODE: number
|
||||
AA: 0 | 1;
|
||||
TC: 0 | 1;
|
||||
RD: 0 | 1;
|
||||
RA: 0 | 1;
|
||||
Z: 0 | 1;
|
||||
AD: 0 | 1;
|
||||
CD: 0 | 1;
|
||||
RCODE: ErrorCodes;
|
||||
QDCOUNT: number;
|
||||
ANCOUNT: number;
|
||||
NSCOUNT: number;
|
||||
ARCOUNT: number;
|
||||
|
||||
constructor(header: IMessageHeader) {
|
||||
for (let k in header) {
|
||||
this[k] = header[k];
|
||||
}
|
||||
}
|
||||
|
||||
lock() {
|
||||
var f = 0x0000;
|
||||
f = f | (this.QR << 15);
|
||||
f = f | (this.OPCODE << 11);
|
||||
f = f | (this.AA << 10);
|
||||
f = f | (this.TC << 9);
|
||||
f = f | (this.RD << 8);
|
||||
f = f | (this.RA << 7);
|
||||
f = f | (this.Z << 6);
|
||||
f = f | (this.AD << 5);
|
||||
f = f | (this.CD << 4);
|
||||
f = f | this.RCODE;
|
||||
|
||||
let id = this.ID;
|
||||
let qdc = this.QDCOUNT;
|
||||
let anc = this.ANCOUNT;
|
||||
let nsc = this.NSCOUNT;
|
||||
let arc = this.ARCOUNT;
|
||||
return {
|
||||
length: 12,
|
||||
serialize: (buffer: Buffer, offset: number) => {
|
||||
buffer.writeUInt16BE(id, offset);
|
||||
offset += 2;
|
||||
buffer.writeUInt16BE(f, offset);
|
||||
offset += 2;
|
||||
buffer.writeUInt16BE(qdc, offset);
|
||||
offset += 2;
|
||||
buffer.writeUInt16BE(anc, offset);
|
||||
offset += 2;
|
||||
buffer.writeUInt16BE(nsc, offset);
|
||||
offset += 2;
|
||||
buffer.writeUInt16BE(arc, offset);
|
||||
return offset + 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// serialize() {
|
||||
// let data = Buffer.alloc(12);
|
||||
// data.writeUInt16BE(this.ID, 0);
|
||||
|
||||
// data.writeUInt16BE(f, 2);
|
||||
// data.writeUInt16BE(this.QDCOUNT, 4)
|
||||
// data.writeUInt16BE(this.ANCOUNT, 6)
|
||||
// data.writeUInt16BE(this.NSCOUNT, 8)
|
||||
// data.writeUInt16BE(this.ARCOUNT, 10)
|
||||
// return data;
|
||||
// }
|
||||
}
|
@ -1,4 +1,7 @@
|
||||
import * as Types from "./types";
|
||||
import * as Request from "./request"
|
||||
|
||||
export = Object.assign({}, Types, Request);
|
||||
export = {
|
||||
types: Types,
|
||||
request: Request
|
||||
}
|
||||
|
60
src/label.ts
Normal file
60
src/label.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import { Serializeable } from "./serializeable";
|
||||
|
||||
const MAX_LABEL_SIZE = 63;
|
||||
|
||||
export class Label implements Serializeable {
|
||||
constructor(public name?: string) { }
|
||||
|
||||
public lock() {
|
||||
let parts = this.name.split(".");
|
||||
let length = 0;
|
||||
parts.forEach(e => {
|
||||
if (e.length > MAX_LABEL_SIZE) throw new Error("Label to large");
|
||||
length += e.length + 1;
|
||||
})
|
||||
|
||||
length += 1;
|
||||
return {
|
||||
length: length,
|
||||
serialize: (buffer: Buffer, offset: number) => {
|
||||
parts.forEach(e => {
|
||||
buffer.writeUInt8(e.length, offset)
|
||||
offset++
|
||||
buffer.write(e, offset, e.length)
|
||||
offset += e.length
|
||||
})
|
||||
buffer.writeUInt8(0, offset);
|
||||
offset += 1;
|
||||
return offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set name(name: string) {
|
||||
// if (name != this._name) {
|
||||
// let length = 0;
|
||||
// let parts = name.split(".")
|
||||
// parts.forEach(e => {
|
||||
// if (e.length > MAX_LABEL_SIZE) throw new Error("Label to large");
|
||||
// length += e.length + 1;
|
||||
// })
|
||||
|
||||
// length += 1; //Adding last 0 length octet
|
||||
// this._length = length;
|
||||
// this._name_parts = parts;
|
||||
// this._name = name;
|
||||
// }
|
||||
// }
|
||||
|
||||
// get name() {
|
||||
// return this._name;
|
||||
// }
|
||||
|
||||
// get length() {
|
||||
// return this._length;
|
||||
// }
|
||||
|
||||
// serialize(buffer: Buffer, offset: number) {
|
||||
|
||||
// }
|
||||
}
|
0
src/lock.ts
Normal file
0
src/lock.ts
Normal file
37
src/question.ts
Normal file
37
src/question.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { IMessageQuestion } from "./types";
|
||||
import { Label } from "./label";
|
||||
import { Serializeable } from "./serializeable";
|
||||
|
||||
export class Question implements IMessageQuestion, Serializeable {
|
||||
private _QNAME = new Label();
|
||||
get QNAME() { return this._QNAME.name };
|
||||
|
||||
_QTYPE: number;
|
||||
get QTYPE() { return this._QTYPE };
|
||||
|
||||
_QCLASS: number;
|
||||
get QCLASS() { return this._QCLASS };
|
||||
|
||||
constructor(question: IMessageQuestion) {
|
||||
this._QCLASS = question.QCLASS;
|
||||
this._QTYPE = question.QTYPE;
|
||||
this._QNAME.name = question.QNAME;
|
||||
}
|
||||
|
||||
lock() {
|
||||
let name = this._QNAME.lock();
|
||||
let type = this._QTYPE;
|
||||
let cl = this._QCLASS;
|
||||
return {
|
||||
length: name.length + 4,
|
||||
serialize: (buffer: Buffer, offset: number) => {
|
||||
offset = name.serialize(buffer, offset);
|
||||
buffer.writeUInt16BE(type, offset);
|
||||
offset += 2;
|
||||
buffer.writeUInt16BE(cl, offset);
|
||||
offset += 2;
|
||||
return offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
92
src/record.ts
Normal file
92
src/record.ts
Normal file
@ -0,0 +1,92 @@
|
||||
import { Serializeable, Serializer } from "./serializeable";
|
||||
import { MessageRecourceRecord } from "./types";
|
||||
import { Label } from "./label";
|
||||
|
||||
export class RecourceRecord implements Partial<MessageRecourceRecord>, Serializeable {
|
||||
/**
|
||||
* This value can be set to identify if specific record is already set
|
||||
*/
|
||||
public Identifier: string;
|
||||
|
||||
private _NAME = new Label();
|
||||
public get NAME() { return this._NAME.name };
|
||||
public set NAME(name) { this._NAME.name = name };
|
||||
|
||||
private _TYPE: number;
|
||||
public get TYPE() { return this._TYPE }
|
||||
public set TYPE(value) {
|
||||
if (value < 0 || value > 65535) throw new TypeError("TYPE Range: 0 - 65.535")
|
||||
this._TYPE = value;
|
||||
}
|
||||
|
||||
private _CLASS: number = 1;
|
||||
public get CLASS() { return this._CLASS }
|
||||
public set CLASS(value) {
|
||||
if (value < 0 || value > 65535) throw new TypeError("CLASS Range: 0 - 65.535")
|
||||
this._CLASS = value;
|
||||
}
|
||||
|
||||
private _TTL: number;
|
||||
public get TTL() { return this._TTL }
|
||||
public set TTL(value) {
|
||||
if (value < 0 || value > 4294967295) throw new TypeError("TTL Range: 0 - 4.294.967.295")
|
||||
this._TTL = value;
|
||||
}
|
||||
|
||||
constructor(data?: Partial<MessageRecourceRecord>) {
|
||||
if (data) {
|
||||
for (let key in data) {
|
||||
this[key] = data[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected dataLock?: () => Serializer;
|
||||
|
||||
public lock() {
|
||||
if (!this.dataLock) return {
|
||||
length: 0,
|
||||
serialize: (buffer: Buffer, offset: number) => offset
|
||||
}
|
||||
|
||||
let name = this._NAME.lock();
|
||||
let type = this.TYPE;
|
||||
let cl = this.CLASS;
|
||||
let ttl = this.TTL;
|
||||
let data = this.dataLock();
|
||||
let length = name.length + 2 + 2 + 4 + 2 + data.length;
|
||||
return {
|
||||
length: length,
|
||||
serialize: (buffer: Buffer, offset: number) => {
|
||||
offset = name.serialize(buffer, offset);
|
||||
buffer.writeUInt16BE(type, offset)
|
||||
offset += 2
|
||||
buffer.writeUInt16BE(cl, offset)
|
||||
offset += 2
|
||||
buffer.writeUInt32BE(ttl, offset)
|
||||
offset += 4
|
||||
buffer.writeUInt16BE(data.length, offset)
|
||||
offset += 2
|
||||
return data.serialize(buffer, offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class RDATARecord extends RecourceRecord {
|
||||
public RDATA: Buffer;
|
||||
|
||||
get RDLENGTH() {
|
||||
return this.RDATA.length;
|
||||
}
|
||||
|
||||
dataLock = () => {
|
||||
return {
|
||||
length: this.RDATA.length,
|
||||
serialize: (buffer: Buffer, offset: number) => {
|
||||
this.RDATA.copy(buffer, offset);
|
||||
return offset + this.RDATA.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
250
src/request.ts
250
src/request.ts
@ -1,7 +1,11 @@
|
||||
import { Parser } from "binary-parser"
|
||||
import { IMessage, IMessageHeader, IMessageQuestion, MessageRecourceRecord, ErrorCodes } from "./types"
|
||||
import { IMessageHeader, IMessageQuestion, MessageRecourceRecord, ErrorCodes } from "./types"
|
||||
import { Serializer } from "./serializeable";
|
||||
import { Question } from "./question";
|
||||
import { RecourceRecord } from "./record";
|
||||
import { Header } from "./header";
|
||||
|
||||
const headerParser: Parser = new Parser()
|
||||
export const headerParser: Parser = new Parser()
|
||||
.endianess("big")
|
||||
.uint16("ID")
|
||||
.bit1("QR")
|
||||
@ -19,15 +23,7 @@ const headerParser: Parser = new Parser()
|
||||
.uint16("NSCOUNT")
|
||||
.uint16("ARCOUNT")
|
||||
|
||||
export function parseHeader(data: Buffer): IMessageHeader {
|
||||
try {
|
||||
return <any>headerParser.parse(data);
|
||||
} catch (e) {
|
||||
throw new Error("Header parsing failed" + e.message)
|
||||
}
|
||||
}
|
||||
|
||||
const labelParser = new Parser()
|
||||
export const labelParser = new Parser()
|
||||
.endianess("big")
|
||||
.uint8("dataLength")
|
||||
.string("name", {
|
||||
@ -35,7 +31,7 @@ const labelParser = new Parser()
|
||||
encoding: "ascii"
|
||||
})
|
||||
|
||||
const questionParser = new Parser()
|
||||
export const questionParser = new Parser()
|
||||
.endianess("big")
|
||||
.array("QNAME", {
|
||||
type: labelParser,
|
||||
@ -50,47 +46,24 @@ const questionParser = new Parser()
|
||||
.uint16("QTYPE")
|
||||
.uint16("QCLASS")
|
||||
|
||||
export function parseQuestions(count: number, packet: Buffer): IMessageQuestion[] {
|
||||
try {
|
||||
return <any>new Parser()
|
||||
|
||||
const packetParser = new Parser()
|
||||
.endianess("big")
|
||||
.nest("header", { type: headerParser })
|
||||
.array("questions", {
|
||||
type: questionParser,
|
||||
length: count
|
||||
}).parse(packet).questions;
|
||||
} catch (e) {
|
||||
throw new Error("Question parsing failed" + e.message)
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_LABEL_SIZE = 63;
|
||||
|
||||
export function serializeName(name: string) {
|
||||
let length = 0;
|
||||
let parts = name.split(".");
|
||||
parts.forEach(e => {
|
||||
// Length of part and byte that holds the length information
|
||||
if (e.length > MAX_LABEL_SIZE) throw new Error("Label to large");
|
||||
length += e.length + 1;
|
||||
length: "header.QDCOUNT"
|
||||
})
|
||||
|
||||
length += 1; //Adding last 0 length octet
|
||||
let data = Buffer.alloc(length);
|
||||
let offset = 0;
|
||||
parts.forEach(e => {
|
||||
data.writeUInt8(e.length, offset)
|
||||
offset++
|
||||
data.write(e, offset, e.length)
|
||||
offset += e.length
|
||||
})
|
||||
data.writeUInt8(0, offset);
|
||||
return data;
|
||||
export function parseInput(data: Buffer): { header: IMessageHeader, questions: IMessageQuestion[] } {
|
||||
let res = packetParser.parse(data);
|
||||
return <any>res;
|
||||
}
|
||||
|
||||
export class Request implements IMessage {
|
||||
export class Request {
|
||||
private _header: Header;
|
||||
get header() {
|
||||
return Object.assign({}, this._header);
|
||||
return { ...this._header };
|
||||
}
|
||||
|
||||
private _questions: Question[];
|
||||
@ -98,21 +71,18 @@ export class Request implements IMessage {
|
||||
return this._questions.map(e => Object.assign({}, e));
|
||||
}
|
||||
|
||||
answers: RecourceRecord[] = [];
|
||||
authorities: RecourceRecord[] = [];
|
||||
additionals: RecourceRecord[] = [];
|
||||
public _answers: RecourceRecord[] = [];
|
||||
public _authorities: RecourceRecord[] = [];
|
||||
public _additionals: RecourceRecord[] = [];
|
||||
|
||||
constructor(packet: Buffer, private sendCallback: (packet: Buffer) => any, private max_size = 512) {
|
||||
let headerData = Buffer.alloc(12);
|
||||
packet.copy(headerData, 0, 0, 12);
|
||||
let bodyData = Buffer.alloc(packet.length - 12);
|
||||
packet.copy(bodyData, 0, 12, packet.length);
|
||||
constructor(packet: Buffer, public sender: string, private max_size = 0) {
|
||||
let parsed = parseInput(packet);
|
||||
|
||||
this._header = new Header(parseHeader(headerData));
|
||||
this._header = new Header(parsed.header);
|
||||
this._header.AD = 0;
|
||||
this._header.RCODE = ErrorCodes.NoError;
|
||||
this._header.RA = this._header.RD;
|
||||
this._questions = parseQuestions(this._header.QDCOUNT, bodyData).map(e => new Question(e));
|
||||
this._questions = parsed.questions.map(e => new Question(e));
|
||||
}
|
||||
|
||||
error(error: ErrorCodes) {
|
||||
@ -124,42 +94,39 @@ export class Request implements IMessage {
|
||||
this._header.RA = 0;
|
||||
}
|
||||
|
||||
send() {
|
||||
this.sendCallback(this.serialize());
|
||||
}
|
||||
|
||||
serialize() {
|
||||
this._header.AA = 1;
|
||||
this._header.ANCOUNT = this.answers.length;
|
||||
this._header.ARCOUNT = this.additionals.length;
|
||||
this._header.NSCOUNT = this.authorities.length;
|
||||
this._header.ANCOUNT = this._answers.length;
|
||||
this._header.ARCOUNT = this._additionals.length;
|
||||
this._header.NSCOUNT = this._authorities.length;
|
||||
this._header.QR = 1;
|
||||
let questions = this._questions.map(e => e.serialize())
|
||||
let answers = this.answers.map(e => e.serialize())
|
||||
let authority = this.authorities.map(e => e.serialize())
|
||||
let additional = this.additionals.map(e => e.serialize())
|
||||
|
||||
let length = 12;
|
||||
let header = this._header.lock();
|
||||
let questions = this._questions.map(e => e.lock())
|
||||
let answers = this._answers.map(e => e.lock())
|
||||
let authority = this._authorities.map(e => e.lock())
|
||||
let additional = this._additionals.map(e => e.lock())
|
||||
|
||||
let length = header.length;
|
||||
questions.forEach(e => length += e.length);
|
||||
answers.forEach(e => length += e.length)
|
||||
authority.forEach(e => length += e.length)
|
||||
additional.forEach(e => length += e.length)
|
||||
|
||||
if (length > this.max_size) {
|
||||
if (this.max_size && length > this.max_size) {
|
||||
this._header.TC = 1;
|
||||
|
||||
//Will ignore data, that exceeds length
|
||||
length = this.max_size;
|
||||
}
|
||||
let header = this._header.serialize();
|
||||
|
||||
|
||||
let data = Buffer.alloc(length)
|
||||
let offset = 0;
|
||||
|
||||
let append = (buffer: Buffer) => {
|
||||
let append = (ser: Serializer) => {
|
||||
if (offset <= length) {
|
||||
buffer.copy(data, offset, 0, buffer.length)
|
||||
offset += buffer.length;
|
||||
offset = ser.serialize(data, offset);
|
||||
}
|
||||
}
|
||||
append(header)
|
||||
@ -170,144 +137,3 @@ export class Request implements IMessage {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
export class Header implements IMessageHeader {
|
||||
ID: number;
|
||||
QR: 0 | 1;
|
||||
OPCODE: number
|
||||
AA: 0 | 1;
|
||||
TC: 0 | 1;
|
||||
RD: 0 | 1;
|
||||
RA: 0 | 1;
|
||||
Z: 0 | 1;
|
||||
AD: 0 | 1;
|
||||
CD: 0 | 1;
|
||||
RCODE: ErrorCodes;
|
||||
QDCOUNT: number;
|
||||
ANCOUNT: number;
|
||||
NSCOUNT: number;
|
||||
ARCOUNT: number;
|
||||
|
||||
constructor(header: IMessageHeader) {
|
||||
for (let k in header) {
|
||||
this[k] = header[k];
|
||||
}
|
||||
}
|
||||
|
||||
serialize() {
|
||||
let data = Buffer.alloc(12);
|
||||
data.writeUInt16BE(this.ID, 0);
|
||||
var f = 0x0000;
|
||||
f = f | (this.QR << 15);
|
||||
f = f | (this.OPCODE << 11);
|
||||
f = f | (this.AA << 10);
|
||||
f = f | (this.TC << 9);
|
||||
f = f | (this.RD << 8);
|
||||
f = f | (this.RA << 7);
|
||||
f = f | (this.Z << 6);
|
||||
f = f | (this.AD << 5);
|
||||
f = f | (this.CD << 4);
|
||||
f = f | this.RCODE;
|
||||
data.writeUInt16BE(f, 2);
|
||||
data.writeUInt16BE(this.QDCOUNT, 4)
|
||||
data.writeUInt16BE(this.ANCOUNT, 6)
|
||||
data.writeUInt16BE(this.NSCOUNT, 8)
|
||||
data.writeUInt16BE(this.ARCOUNT, 10)
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
export class Question implements IMessageQuestion {
|
||||
QNAME: string;
|
||||
QTYPE: number;
|
||||
QCLASS: number;
|
||||
|
||||
constructor(question: IMessageQuestion) {
|
||||
for (let k in question) {
|
||||
this[k] = question[k]
|
||||
}
|
||||
}
|
||||
|
||||
serialize() {
|
||||
let qname = serializeName(this.QNAME);
|
||||
let data = Buffer.alloc(qname.length + 4);
|
||||
qname.copy(data, 0, 0, qname.length);
|
||||
let offset = qname.length;
|
||||
data.writeUInt16BE(this.QTYPE, offset);
|
||||
offset += 2;
|
||||
data.writeUInt16BE(this.QCLASS, offset);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
export class RecourceRecord implements MessageRecourceRecord {
|
||||
constructor(data?: Partial<MessageRecourceRecord>) {
|
||||
if (data) {
|
||||
for (let key in data) {
|
||||
this[key] = data[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This value can be set to identify if specific record is already set
|
||||
*/
|
||||
Identifier: string;
|
||||
|
||||
NAME: string
|
||||
private _TYPE: number;
|
||||
set TYPE(value) {
|
||||
if (value < 0 || value > 65535) throw new TypeError("TYPE Range: 0 - 65.535")
|
||||
this._TYPE = value;
|
||||
}
|
||||
|
||||
get TYPE() {
|
||||
return this._TYPE;
|
||||
}
|
||||
|
||||
private _CLASS: number;
|
||||
set CLASS(value) {
|
||||
if (value < 0 || value > 65535) throw new TypeError("CLASS Range: 0 - 65.535")
|
||||
this._CLASS = value;
|
||||
}
|
||||
|
||||
get CLASS() {
|
||||
return this._CLASS;
|
||||
}
|
||||
|
||||
private _TTL: number;
|
||||
set TTL(value) {
|
||||
if (value < 0 || value > 4294967295) throw new TypeError("TTL Range: 0 - 4.294.967.295")
|
||||
this._TTL = value;
|
||||
}
|
||||
|
||||
get TTL() {
|
||||
return this._TTL;
|
||||
}
|
||||
|
||||
RDATA: Buffer;
|
||||
|
||||
get RDLENGTH() {
|
||||
return this.RDATA.length;
|
||||
}
|
||||
|
||||
public serialize() {
|
||||
// TODO: Implement compression
|
||||
let name = serializeName(this.NAME);
|
||||
let rdata = this.RDATA;
|
||||
|
||||
let data = Buffer.alloc(name.length + 10 + rdata.length) // For TYPE, CLASS, TTL, RLENGTH
|
||||
name.copy(data, 0, 0, name.length);
|
||||
let offset = name.length;
|
||||
data.writeUInt16BE(this.TYPE, offset)
|
||||
offset += 2
|
||||
data.writeUInt16BE(this.CLASS, offset)
|
||||
offset += 2
|
||||
data.writeUInt32BE(this._TTL, offset)
|
||||
offset += 4
|
||||
data.writeUInt16BE(rdata.length, offset)
|
||||
offset += 2
|
||||
rdata.copy(data, offset, 0, rdata.length)
|
||||
return data;
|
||||
}
|
||||
}
|
19
src/serializeable.ts
Normal file
19
src/serializeable.ts
Normal file
@ -0,0 +1,19 @@
|
||||
export interface Serializer {
|
||||
/**
|
||||
*
|
||||
* @param buffer Buffer to write to (should be capable to hold this.length)
|
||||
* @param offset The offset at wich to start writing
|
||||
* @returns The new offset (should be equal to original offset + this.length)
|
||||
*/
|
||||
serialize(buffer: Buffer, offset: number): number;
|
||||
|
||||
/**
|
||||
* Returns length of serialized data
|
||||
* @readonly
|
||||
*/
|
||||
length: number;
|
||||
}
|
||||
|
||||
export interface Serializeable {
|
||||
lock(): Serializer;
|
||||
}
|
129
src/test.ts
129
src/test.ts
@ -1,7 +1,10 @@
|
||||
import { assert, expect } from "chai";
|
||||
import { assert } from "chai";
|
||||
|
||||
import { parseHeader, Header, parseQuestions, Question, RecourceRecord, Request } from "./request"
|
||||
import { IMessageHeader, IMessageQuestion, QueryTypes } from "./types";
|
||||
import { parseInput, Request, questionParser, headerParser } from "./request"
|
||||
import { IMessageHeader, RecordTypes } from "./types";
|
||||
import { RDATARecord } from "./record";
|
||||
import { Question } from "./question";
|
||||
import { Header } from "./header";
|
||||
|
||||
function fromHex(data: string) {
|
||||
return Buffer.from(data.replace(/\s/g, ""), "hex");
|
||||
@ -11,21 +14,7 @@ describe("parser", function () {
|
||||
describe("header", function () {
|
||||
describe("header parser", function () {
|
||||
let should_templ: IMessageHeader = {
|
||||
ID: 0,
|
||||
QR: 0,
|
||||
OPCODE: 0,
|
||||
AA: 0,
|
||||
TC: 0,
|
||||
RD: 0,
|
||||
RA: 0,
|
||||
Z: 0,
|
||||
AD: 0,
|
||||
CD: 0,
|
||||
RCODE: 0,
|
||||
QDCOUNT: 0,
|
||||
ANCOUNT: 0,
|
||||
ARCOUNT: 0,
|
||||
NSCOUNT: 0
|
||||
ID: 0, QR: 0, OPCODE: 0, AA: 0, TC: 0, RD: 0, RA: 0, Z: 0, AD: 0, CD: 0, RCODE: 0, QDCOUNT: 0, ANCOUNT: 0, ARCOUNT: 0, NSCOUNT: 0
|
||||
}
|
||||
|
||||
let tests: { name: string, data: string, fields: Partial<IMessageHeader> }[] = [
|
||||
@ -124,29 +113,15 @@ describe("parser", function () {
|
||||
name: "Testing all Flags and Values max",
|
||||
data: "FFFF FFFF FFFF FFFF FFFF FFFF",
|
||||
fields: {
|
||||
ID: 65535,
|
||||
QR: 1,
|
||||
OPCODE: 15,
|
||||
AA: 1,
|
||||
TC: 1,
|
||||
RD: 1,
|
||||
RA: 1,
|
||||
Z: 1,
|
||||
AD: 1,
|
||||
CD: 1,
|
||||
RCODE: 15,
|
||||
QDCOUNT: 65535,
|
||||
ANCOUNT: 65535,
|
||||
NSCOUNT: 65535,
|
||||
ARCOUNT: 65535
|
||||
ID: 65535, QR: 1, OPCODE: 15, AA: 1, TC: 1, RD: 1, RA: 1, Z: 1, AD: 1, CD: 1, RCODE: 15, QDCOUNT: 65535, ANCOUNT: 65535, NSCOUNT: 65535, ARCOUNT: 65535
|
||||
}
|
||||
}
|
||||
]
|
||||
tests.forEach(function (e) {
|
||||
it(e.name, function () {
|
||||
let testdata = fromHex(e.data)
|
||||
let should: IMessageHeader = Object.assign({}, should_templ, e.fields) // Build in "clone" function
|
||||
let header = parseHeader(testdata);
|
||||
let should: IMessageHeader = { ...should_templ, ...e.fields }
|
||||
let header = <any>headerParser.parse(testdata);
|
||||
assert.hasAllKeys(header, Object.keys(should), "Parsed header is missing some fields")
|
||||
assert.deepEqual(header, should, "Parsed header has not expected values!")
|
||||
})
|
||||
@ -155,21 +130,7 @@ describe("parser", function () {
|
||||
|
||||
describe("header serializer", function () {
|
||||
let empty_header: IMessageHeader = {
|
||||
ID: 0,
|
||||
QR: 0,
|
||||
OPCODE: 0,
|
||||
AA: 0,
|
||||
TC: 0,
|
||||
RD: 0,
|
||||
RA: 0,
|
||||
Z: 0,
|
||||
AD: 0,
|
||||
CD: 0,
|
||||
RCODE: 0,
|
||||
QDCOUNT: 0,
|
||||
ANCOUNT: 0,
|
||||
ARCOUNT: 0,
|
||||
NSCOUNT: 0
|
||||
ID: 0, QR: 0, OPCODE: 0, AA: 0, TC: 0, RD: 0, RA: 0, Z: 0, AD: 0, CD: 0, RCODE: 0, QDCOUNT: 0, ANCOUNT: 0, ARCOUNT: 0, NSCOUNT: 0
|
||||
}
|
||||
let tests: { name: string, result: string, values: Partial<IMessageHeader> }[] = [
|
||||
{
|
||||
@ -287,8 +248,11 @@ describe("parser", function () {
|
||||
tests.forEach(function (e) {
|
||||
it(e.name, function () {
|
||||
let header = Object.assign({}, empty_header, e.values)
|
||||
let serialized = new Header(header).serialize();
|
||||
assert.equal(serialized.toString("hex"), e.result.replace(/\s/g, "").toLowerCase(), "Header serialization failed");
|
||||
let slz = new Header(header).lock();
|
||||
assert.equal(slz.length, 12, "Header returns wrong length");
|
||||
let res = Buffer.alloc(slz.length);
|
||||
assert.equal(slz.serialize(res, 0), 12, "Header returns wrong offset");
|
||||
assert.equal(res.toString("hex"), e.result.replace(/\s/g, "").toLowerCase(), "Header serialization failed");
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -298,75 +262,78 @@ describe("parser", function () {
|
||||
let questionData = fromHex("0474 6573 7407 6578 616d 706c 6503 636f 6d00 0001 0001")
|
||||
let questionObj = {
|
||||
QNAME: "test.example.com",
|
||||
QTYPE: QueryTypes.A,
|
||||
QTYPE: RecordTypes.A,
|
||||
QCLASS: 1
|
||||
}
|
||||
it("check question parser with one question", function () {
|
||||
|
||||
let res = parseQuestions(1, questionData)
|
||||
let should: IMessageQuestion[] = [questionObj]
|
||||
let res = questionParser.parse(questionData)
|
||||
|
||||
assert.deepEqual(res, should, "Question parser does not parse input correctly")
|
||||
assert.deepEqual(res, questionObj, "Question parser does not parse input correctly")
|
||||
})
|
||||
|
||||
it("check question serialization", function () {
|
||||
let res = new Question(questionObj).serialize()
|
||||
let slz = new Question(questionObj).lock()
|
||||
assert.equal(slz.length, 22, "Question serializer returns wrong length")
|
||||
let res = Buffer.alloc(slz.length);
|
||||
assert.equal(slz.serialize(res, 0), 22, "Question serializer returns wrong offset")
|
||||
assert.equal(res.toString("hex"), questionData.toString("hex"), "Query serializer does not serialite correctly");
|
||||
})
|
||||
})
|
||||
|
||||
it("parse valid request", () => {
|
||||
let should = {
|
||||
header: { ID: 59445, QR: 0, OPCODE: 0, AA: 0, TC: 0, RD: 1, RA: 0, Z: 0, AD: 0, CD: 0, RCODE: 0, QDCOUNT: 1, ANCOUNT: 0, NSCOUNT: 0, ARCOUNT: 0 },
|
||||
questions: [{ QNAME: 'example.com', QTYPE: 1, QCLASS: 1 }]
|
||||
}
|
||||
let data = fromHex("E835 0100 0001 0000 0000 0000 07 6578616D706c65 03636F6D 00 0001 0001");
|
||||
let res = parseInput(data);
|
||||
assert.deepEqual(res, should);
|
||||
})
|
||||
|
||||
|
||||
it("recource record serialization", function () {
|
||||
let should = "07 6578616D706C65 03 636f6D 00 0001 0001 00000640 0004 0A000001"
|
||||
let rr = new RecourceRecord()
|
||||
let rr = new RDATARecord()
|
||||
rr.CLASS = 1
|
||||
rr.NAME = "example.com"
|
||||
rr.TTL = 1600
|
||||
rr.TYPE = 1
|
||||
rr.RDATA = fromHex("0A 00 00 01")
|
||||
let res = rr.serialize().toString("hex")
|
||||
assert.equal(res, should.replace(/\s/g, "").toLowerCase(), "Serialization not working properly")
|
||||
let srl = rr.lock();
|
||||
let res = Buffer.alloc(srl.length);
|
||||
srl.serialize(res, 0);
|
||||
assert.equal(res.toString("hex"), should.replace(/\s/g, "").toLowerCase(), "Serialization not working properly")
|
||||
})
|
||||
|
||||
it("recource record constructor value assign", function () {
|
||||
let should = "07 6578616D706C65 03 636f6D 00 0001 0001 00000640 0004 0A000001"
|
||||
let rr = new RecourceRecord({
|
||||
let rr = new RDATARecord({
|
||||
CLASS: 1,
|
||||
NAME: "example.com",
|
||||
TTL: 1600,
|
||||
TYPE: 1,
|
||||
RDATA: fromHex("0A 00 00 01")
|
||||
})
|
||||
let res = rr.serialize().toString("hex")
|
||||
assert.equal(res, should.replace(/\s/g, "").toLowerCase(), "Serialization not working properly")
|
||||
let srl = rr.lock();
|
||||
assert.equal(srl.length, 27, "Record serializer returns wrong length")
|
||||
let res = Buffer.alloc(srl.length);
|
||||
assert.equal(srl.serialize(res, 0), 27, "Record serializer returns wrong offset")
|
||||
assert.equal(res.toString("hex"), should.replace(/\s/g, "").toLowerCase(), "Record serialization not working properly")
|
||||
})
|
||||
|
||||
it("full response serialization", function () {
|
||||
let reqData = fromHex("E835 0100 0001 0000 0000 0000 07 6578616D706c65 03636F6D 00 0001 0001");
|
||||
let should = "E835 8580 0001000100000000 076578616D706C6503636F6D0000010001 07 6578616D706C65 03 636F6D 00 0001 0001 0000 0640 0004 0A000001"
|
||||
let request = new Request(reqData, () => null)
|
||||
let rr = new RecourceRecord()
|
||||
let request = new Request(reqData, "")
|
||||
let rr = new RDATARecord()
|
||||
rr.CLASS = 1
|
||||
rr.NAME = "example.com"
|
||||
rr.TTL = 1600
|
||||
rr.TYPE = 1
|
||||
rr.RDATA = fromHex("0A 00 00 01")
|
||||
request.answers.push(rr)
|
||||
request._answers.push(rr)
|
||||
let data = request.serialize()
|
||||
assert.equal(data.toString("hex"), should.replace(/\s/g, "").toLowerCase(), "Whole packet serialization failed")
|
||||
})
|
||||
|
||||
// it("full response serialization benchmark", function () {
|
||||
// let reqData = fromHex("E835 0100 0001 0000 0000 0000 07 6578616D706c65 03636F6D 00 0001 0001");
|
||||
// for (let i = 0; i < 100; i++) {
|
||||
// let request = new Request(reqData, () => null)
|
||||
// let rr = new RecourceRecord()
|
||||
// rr.CLASS = 1
|
||||
// rr.NAME = "example.com"
|
||||
// rr.TTL = 1600
|
||||
// rr.TYPE = 1
|
||||
// rr.RDATA = fromHex("0A 00 00 01")
|
||||
// request.answers.push(rr)
|
||||
// request.serialize()
|
||||
// }
|
||||
// })
|
||||
})
|
23
src/types.ts
23
src/types.ts
@ -30,7 +30,7 @@ export enum ErrorCodes {
|
||||
Refused
|
||||
}
|
||||
|
||||
export enum QueryTypes {
|
||||
export enum RecordTypes {
|
||||
/**
|
||||
* IPv4 address
|
||||
*/
|
||||
@ -254,14 +254,13 @@ export interface IMessageQuestion {
|
||||
|
||||
/**
|
||||
* Two octed code that specifies the class of the Query
|
||||
* IS for internet
|
||||
* WARNING: ONLY IN IS SUPPORTED BY THIS APPLICATION
|
||||
* IN for internet
|
||||
*/
|
||||
QCLASS: number;
|
||||
}
|
||||
export interface MessageRecourceRecord {
|
||||
/**
|
||||
* Domain name to wich resource record pertains
|
||||
* Domain name to wich resource record points
|
||||
*/
|
||||
NAME: string;
|
||||
|
||||
@ -297,7 +296,7 @@ export interface MessageRecourceRecord {
|
||||
RDLENGTH: number;
|
||||
|
||||
/**
|
||||
* a variable length string of ectets taht describes
|
||||
* a variable length string of octets that describes
|
||||
* the resource. The format is defined by TYPE and CLASS
|
||||
* field.
|
||||
*
|
||||
@ -306,10 +305,10 @@ export interface MessageRecourceRecord {
|
||||
*/
|
||||
RDATA: Buffer;
|
||||
}
|
||||
export interface IMessage {
|
||||
header: IMessageHeader;
|
||||
questions: IMessageQuestion[];
|
||||
answers: MessageRecourceRecord[];
|
||||
authorities: MessageRecourceRecord[];
|
||||
additionals: MessageRecourceRecord[];
|
||||
}
|
||||
// export interface IMessage {
|
||||
// header: IMessageHeader;
|
||||
// questions: IMessageQuestion[];
|
||||
// _answers: MessageRecourceRecord[];
|
||||
// _authorities: MessageRecourceRecord[];
|
||||
// _additionals: MessageRecourceRecord[];
|
||||
// }
|
||||
|
Loading…
Reference in New Issue
Block a user