Compare commits

..

2 Commits

Author SHA1 Message Date
65b05eb449 Update types 2026-04-08 23:23:49 +08:00
777f92260e Bye buffer 2026-04-08 23:23:29 +08:00
4 changed files with 130 additions and 180 deletions

View File

@ -36,9 +36,9 @@ const inPacket: Packet = {
type: "query",
};
const inputBuf = Buffer.alloc(0);
const inputBuf = new Uint8Array(0);
const length: number = encodingLength(inPacket);
const out: Buffer = encode(inPacket, inputBuf, length - length);
const out: Uint8Array = encode(inPacket, inputBuf, length - length);
const outPacket: DecodedPacket = decode(out, 0);
const flag_qr: boolean = outPacket.flag_qr;
const flag_aa: boolean = outPacket.flag_aa;

261
index.d.ts vendored
View File

@ -40,6 +40,7 @@ export type RecordType =
| "RP"
| "SIG"
| "SOA"
| "SPF"
| "SRV"
| "SSHFP"
| "SVCB"
@ -52,6 +53,10 @@ export type RecordType =
export type RecordClass = "IN" | "CS" | "CH" | "HS" | "ANY";
export type OpCode = "QUERY" | "IQUERY" | "STATUS" | "NOTIFY" | "UPDATE" | string;
export type RCode = "NOERROR" | "FORMERR" | "SERVFAIL" | "NXDOMAIN" | "NOTIMP" | "REFUSED" | "YXDOMAIN" | "YXRRSET" | "NXRRSET" | "NOTAUTH" | "NOTZONE" | string;
export interface Question {
type: RecordType;
name: string;
@ -70,14 +75,14 @@ export interface CaaData {
export interface DnskeyData {
flags: number;
algorithm: number;
key: Buffer;
key: string | Uint8Array;
}
export interface DsData {
keyTag: number;
algorithm: number;
digestType: number;
digest: Buffer;
digest: string | Uint8Array;
}
export interface HInfoData {
@ -108,8 +113,8 @@ export interface Nsec3Data {
algorithm: number;
flags: number;
iterations: number;
salt: Buffer;
nextDomain: Buffer;
salt: Uint8Array;
nextDomain: Uint8Array;
rrtypes: string[];
}
@ -119,7 +124,7 @@ export interface RpData {
}
export interface RrsigData {
typeCovered: string;
typeCovered: RecordType | string;
algorithm: number;
labels: number;
originalTTL: number;
@ -127,7 +132,7 @@ export interface RrsigData {
inception: number;
keyTag: number;
signersName: string;
signature: Buffer;
signature: string | Uint8Array;
}
export interface SrvData {
@ -157,10 +162,10 @@ export interface TlsaData {
usage: number;
selector: number;
matchingType: number;
certificate: Buffer;
certificate: string | Uint8Array;
}
export type TxtData = string | Buffer | Array<string | Buffer>;
export type TxtData = string | Uint8Array | Array<string | Uint8Array>;
// SVCB/HTTPS Parameter types
export interface SvcParamMandatory {
@ -190,7 +195,7 @@ export interface SvcParamIpv4Hint {
export interface SvcParamEchConfig {
key: "echconfig" | 5;
value: string;
value: string | Uint8Array;
needBase64Decode?: boolean | undefined;
}
@ -206,14 +211,14 @@ export interface SvcParamDohPath {
export interface SvcParamOdoh {
key: "odoh" | 32769;
value: string;
value: string | Uint8Array;
needBase64Decode?: boolean | undefined;
}
export interface SvcParamUnknown {
key: string | number;
value?: unknown | undefined;
data?: Buffer | undefined;
data?: Uint8Array | undefined;
}
export type SvcParam =
@ -231,13 +236,13 @@ export type SvcParam =
export interface HttpsData {
priority: number;
name: string;
values?: Record<string, unknown> | undefined;
values?: Record<string, SvcParam["value"]> | undefined;
}
export interface SvcbData {
priority: number;
name: string;
values?: Record<string, unknown> | undefined;
values?: Record<string, SvcParam["value"]> | undefined;
}
// Generic answer types
@ -281,7 +286,7 @@ export type OtherRecordType =
// Specific answer types
export type StringAnswer = BaseAnswer<StringRecordType, string>;
export type BufferAnswer = BaseAnswer<OtherRecordType, Buffer>;
export type BufferAnswer = BaseAnswer<OtherRecordType, Uint8Array>;
export type CaaAnswer = BaseAnswer<"CAA", CaaData>;
export type DnskeyAnswer = BaseAnswer<"DNSKEY", DnskeyData>;
export type DSAnswer = BaseAnswer<"DS", DsData>;
@ -327,14 +332,14 @@ type OptCode<K extends OptCodeType> = OptCodes[K];
interface GenericOpt<T extends OptCodeType> {
code: OptCode<T>;
type?: T | undefined;
data?: Buffer | undefined;
data?: Uint8Array | undefined;
}
interface ClientSubnetOpt extends GenericOpt<"CLIENT_SUBNET"> {
family?: number | undefined;
sourcePrefixLength?: number | undefined;
scopePrefixLength?: number | undefined;
ip: string | undefined;
ip?: string | undefined;
}
interface KeepAliveOpt extends GenericOpt<"TCP_KEEPALIVE"> {
@ -410,8 +415,8 @@ export interface Packet {
*/
flags?: number | undefined;
opcode?: string | undefined;
rcode?: string | undefined;
opcode?: OpCode | number | undefined;
rcode?: RCode | number | undefined;
questions?: Question[] | undefined;
answers?: Answer[] | undefined;
@ -444,13 +449,13 @@ export const DNSSEC_OK: number;
export const NXDOMAIN: number;
// Main functions
export function encode(packet: Packet, buf?: Buffer, offset?: number): Buffer;
export function encode(packet: Packet, buf?: Uint8Array | ArrayBufferLike, offset?: number): Uint8Array;
export namespace encode {
let bytes: number;
}
export function decode(buf: Buffer, offset?: number): DecodedPacket;
export function decode(buf: Uint8Array | ArrayBufferLike, offset?: number): DecodedPacket;
export namespace decode {
let bytes: number;
@ -458,211 +463,135 @@ export namespace decode {
export function encodingLength(packet: Packet): number;
export function streamEncode(packet: Packet): Buffer;
export function streamEncode(packet: Packet): Uint8Array;
export namespace streamEncode {
let bytes: number;
}
export function streamDecode(buf: Buffer): DecodedPacket | null;
export function streamDecode(buf: Uint8Array | ArrayBufferLike, offset?: number): DecodedPacket | null;
export namespace streamDecode {
let bytes: number;
}
// Utility/Helper exports
export const name: {
export const name: Codec<string> & {
encode(
str: string,
buf?: Buffer,
buf?: Uint8Array | ArrayBufferLike,
offset?: number,
options?: { mail?: boolean }
): Buffer;
decode(buf: Buffer, offset?: number, options?: { mail?: boolean }): string;
): Uint8Array;
decode(buf: Uint8Array | ArrayBufferLike, offset?: number, options?: { mail?: boolean }): string;
encodingLength(name: string): number;
};
export const question: {
encode(q: Question, buf?: Buffer, offset?: number): Buffer;
decode(buf: Buffer, offset?: number): Question;
encodingLength(q: Question): number;
};
export const question: Codec<Question>;
export const answer: {
encode(a: Answer, buf?: Buffer, offset?: number): Buffer;
decode(buf: Buffer, offset?: number): Answer;
encodingLength(a: Answer): number;
};
export const answer: Codec<Answer>;
export const svcparam: {
encode(param: SvcParam, buf?: Buffer, offset?: number): Buffer;
decode(buf: Buffer, offset?: number): SvcParam;
encodingLength(param: SvcParam): number;
export const svcparam: Codec<SvcParam> & {
keyToNumber(keyName: string): number;
numberToKeyName(number: number): string;
};
export const svcb: {
encode(data: SvcbData, buf?: Buffer, offset?: number): Buffer;
decode(buf: Buffer, offset?: number): SvcbData;
encodingLength(data: SvcbData): number;
};
export const svcb: Codec<SvcbData>;
export const httpssvc: {
encode(data: HttpsData, buf?: Buffer, offset?: number): Buffer;
decode(buf: Buffer, offset?: number): HttpsData;
encodingLength(data: HttpsData): number;
};
export const httpssvc: Codec<HttpsData>;
export const a: {
encode(host: string, buf?: Buffer, offset?: number): Buffer;
decode(buf: Buffer, offset?: number): string;
encodingLength(): number;
};
export const a: Codec<string>;
export const aaaa: {
encode(host: string, buf?: Buffer, offset?: number): Buffer;
decode(buf: Buffer, offset?: number): string;
encodingLength(): number;
};
export const aaaa: Codec<string>;
export const cname: {
encode(data: string, buf?: Buffer, offset?: number): Buffer;
decode(buf: Buffer, offset?: number): string;
encodingLength(data: string): number;
};
export const cname: Codec<string>;
export const dname: {
encode(data: string, buf?: Buffer, offset?: number): Buffer;
decode(buf: Buffer, offset?: number): string;
encodingLength(data: string): number;
};
export const dname: Codec<string>;
export const ptr: {
encode(data: string, buf?: Buffer, offset?: number): Buffer;
decode(buf: Buffer, offset?: number): string;
encodingLength(data: string): number;
};
export const ptr: Codec<string>;
export const ns: {
encode(data: string, buf?: Buffer, offset?: number): Buffer;
decode(buf: Buffer, offset?: number): string;
encodingLength(data: string): number;
};
export const ns: Codec<string>;
export const mx: {
encode(data: MxData, buf?: Buffer, offset?: number): Buffer;
decode(buf: Buffer, offset?: number): MxData;
encodingLength(data: MxData): number;
};
export const mx: Codec<MxData>;
export const srv: {
encode(data: SrvData, buf?: Buffer, offset?: number): Buffer;
decode(buf: Buffer, offset?: number): SrvData;
encodingLength(data: SrvData): number;
};
export const srv: Codec<SrvData>;
export const caa: {
encode(data: CaaData, buf?: Buffer, offset?: number): Buffer;
decode(buf: Buffer, offset?: number): CaaData;
encodingLength(data: CaaData): number;
export const caa: Codec<CaaData> & {
ISSUER_CRITICAL: number;
};
export const txt: {
encode(data: TxtData, buf?: Buffer, offset?: number): Buffer;
decode(buf: Buffer, offset?: number): Buffer[];
encodingLength(data: TxtData): number;
export const txt: Codec<Uint8Array[]> & {
encode(data: TxtData, buf?: Uint8Array, offset?: number): Uint8Array;
};
export const null_: {
encode(data: Buffer | string, buf?: Buffer, offset?: number): Buffer;
decode(buf: Buffer, offset?: number): Buffer;
encodingLength(data?: Buffer | string): number;
export const null_: Codec<Uint8Array> & {
encode(data: Uint8Array | string, buf?: Uint8Array, offset?: number): Uint8Array;
encodingLength(data?: Uint8Array | string): number;
};
export const hinfo: {
encode(data: HInfoData, buf?: Buffer, offset?: number): Buffer;
decode(buf: Buffer, offset?: number): HInfoData;
encodingLength(data: HInfoData): number;
};
export const hinfo: Codec<HInfoData>;
export const soa: {
encode(data: SoaData, buf?: Buffer, offset?: number): Buffer;
decode(buf: Buffer, offset?: number): SoaData;
encodingLength(data: SoaData): number;
};
export const soa: Codec<SoaData>;
export const naptr: {
encode(data: NaptrData, buf?: Buffer, offset?: number): Buffer;
decode(buf: Buffer, offset?: number): NaptrData;
encodingLength(data: NaptrData): number;
};
export const naptr: Codec<NaptrData>;
export const dnskey: {
encode(key: DnskeyData, buf?: Buffer, offset?: number): Buffer;
decode(buf: Buffer, offset?: number): DnskeyData;
encodingLength(key: DnskeyData): number;
export const dnskey: Codec<DnskeyData> & {
PROTOCOL_DNSSEC: number;
ZONE_KEY: number;
SECURE_ENTRYPOINT: number;
};
export const rrsig: {
encode(sig: RrsigData, buf?: Buffer, offset?: number): Buffer;
decode(buf: Buffer, offset?: number): RrsigData;
encodingLength(sig: RrsigData): number;
};
export const rrsig: Codec<RrsigData>;
export const rp: {
encode(data: RpData, buf?: Buffer, offset?: number): Buffer;
decode(buf: Buffer, offset?: number): RpData;
encodingLength(data: RpData): number;
};
export const rp: Codec<RpData>;
export const nsec: {
encode(record: NsecData, buf?: Buffer, offset?: number): Buffer;
decode(buf: Buffer, offset?: number): NsecData;
encodingLength(record: NsecData): number;
};
export const nsec: Codec<NsecData>;
export const nsec3: {
encode(record: Nsec3Data, buf?: Buffer, offset?: number): Buffer;
decode(buf: Buffer, offset?: number): Nsec3Data;
encodingLength(record: Nsec3Data): number;
};
export const nsec3: Codec<Nsec3Data>;
export const ds: {
encode(digest: DsData, buf?: Buffer, offset?: number): Buffer;
decode(buf: Buffer, offset?: number): DsData;
encodingLength(digest: DsData): number;
};
export const ds: Codec<DsData>;
export const sshfp: {
encode(record: SshfpData, buf?: Buffer, offset?: number): Buffer;
decode(buf: Buffer, offset?: number): SshfpData;
encodingLength(record: SshfpData): number;
export const sshfp: Codec<SshfpData> & {
getFingerprintLengthForHashType(hashType: number): number | undefined;
};
export const tlsa: {
encode(cert: TlsaData, buf?: Buffer, offset?: number): Buffer;
decode(buf: Buffer, offset?: number): TlsaData;
encodingLength(cert: TlsaData): number;
export const tlsa: Codec<TlsaData>;
export const opt: Codec<PacketOpt[]>;
export const option: Codec<PacketOpt>;
export const unknown: Codec<Uint8Array> & {
encode(data: Uint8Array | string, buf?: Uint8Array, offset?: number): Uint8Array;
encodingLength(data: Uint8Array | string): number;
};
export const opt: {
encode(options: PacketOpt[], buf?: Buffer, offset?: number): Buffer;
decode(buf: Buffer, offset?: number): PacketOpt[];
encodingLength(options: PacketOpt[]): number;
};
// Codec interface for type-safe encode/decode implementations
export interface Codec<T = any> {
encode(data: T, buf?: Uint8Array | ArrayBufferLike, offset?: number): Uint8Array;
decode(buf: Uint8Array | ArrayBufferLike, offset?: number): T;
encodingLength(data: T): number;
}
export const unknown: {
encode(data: Buffer, buf?: Buffer, offset?: number): Buffer;
decode(buf: Buffer, offset?: number): Buffer;
encodingLength(data: Buffer): number;
};
export function record(type: RecordType): any;
export function record(type: "A" | "AAAA" | "CNAME" | "DNAME" | "NS" | "PTR"): Codec<string>;
export function record(type: "MX"): Codec<MxData>;
export function record(type: "SRV"): Codec<SrvData>;
export function record(type: "SOA"): Codec<SoaData>;
export function record(type: "TXT"): Codec<Uint8Array[]>;
export function record(type: "CAA"): Codec<CaaData>;
export function record(type: "HINFO"): Codec<HInfoData>;
export function record(type: "NAPTR"): Codec<NaptrData>;
export function record(type: "RP"): Codec<RpData>;
export function record(type: "RRSIG"): Codec<RrsigData>;
export function record(type: "DS"): Codec<DsData>;
export function record(type: "DNSKEY"): Codec<DnskeyData>;
export function record(type: "NSEC"): Codec<NsecData>;
export function record(type: "NSEC3"): Codec<Nsec3Data>;
export function record(type: "SSHFP"): Codec<SshfpData>;
export function record(type: "TLSA"): Codec<TlsaData>;
export function record(type: "SVCB"): Codec<SvcbData>;
export function record(type: "HTTPS"): Codec<HttpsData>;
export function record(type: RecordType): Codec<any>;
export {};

View File

@ -954,7 +954,8 @@ rdnskey.decode = function (buf, offset) {
rdnskey.decode.bytes = 0
rdnskey.encodingLength = function (key) {
return 6 + byteLength(key.key, 'base64')
const keydata = fromBuffer(key.key, 'base64')
return 6 + keydata.length
}
const rrrsig = exports.rrsig = {}
@ -1025,9 +1026,10 @@ rrrsig.decode = function (buf, offset) {
rrrsig.decode.bytes = 0
rrrsig.encodingLength = function (sig) {
const signature = fromBuffer(sig.signature, 'base64')
return 20 +
name.encodingLength(sig.signersName) +
byteLength(sig.signature, 'base64')
signature.length
}
const rrp = exports.rp = {}
@ -1319,7 +1321,8 @@ rds.decode = function (buf, offset) {
rds.decode.bytes = 0
rds.encodingLength = function (digest) {
return 6 + byteLength(digest.digest, 'hex')
const digestdata = fromBuffer(digest.digest, 'hex')
return 6 + digestdata.length
}
const rsshfp = exports.sshfp = {}
@ -1488,7 +1491,8 @@ rtlsa.decode = function (buf, offset) {
rtlsa.decode.bytes = 0
rtlsa.encodingLength = function (cert) {
return 5 + byteLength(cert.certificate)
const certdata = cert.certificate
return 5 + (isBuffer(certdata) ? certdata.length : byteLength(certdata))
}
const svcparam = exports.svcparam = {}

33
test.js
View File

@ -400,17 +400,30 @@ tape('name_decoding', function (t) {
// A name can be only 253 characters (when connected with dots)
const maxLength = new Uint8Array(255)
maxLength.fill(new Uint8Array([0x01, 0x61]), 0, 254)
for (let i = 0; i < 127; i++) {
maxLength[i * 2] = 0x01
maxLength[i * 2 + 1] = 0x61
}
maxLength[254] = 0x00
t.ok(packet.name.decode(maxLength) === new Array(127).fill('a').join('.'))
const tooLong = new Uint8Array(256)
tooLong.fill(new Uint8Array([0x01, 0x61]))
for (let i = 0; i < 128; i++) {
tooLong[i * 2] = 0x01
tooLong[i * 2 + 1] = 0x61
}
t.throws(function () { packet.name.decode(tooLong) }, /Cannot decode name \(name too long\)$/)
// Ensure jumps don't reset the total length counter
const tooLongWithJump = new Uint8Array(403)
tooLongWithJump.fill(new Uint8Array([0x01, 0x61]), 0, 200)
tooLongWithJump.fill(new Uint8Array([0x01, 0x61]), 201, 401)
for (let i = 0; i < 100; i++) {
tooLongWithJump[i * 2] = 0x01
tooLongWithJump[i * 2 + 1] = 0x61
}
for (let i = 0; i < 100; i++) {
tooLongWithJump[201 + i * 2] = 0x01
tooLongWithJump[201 + i * 2 + 1] = 0x61
}
tooLongWithJump.set([0xc0, 0x00], 401)
t.throws(function () { packet.name.decode(tooLongWithJump, 201) }, /Cannot decode name \(name too long\)$/)
@ -420,7 +433,9 @@ tape('name_decoding', function (t) {
// Ensure deeply nested pointers don't cause "Maximum call stack size exceeded" errors
const buf = new Uint8Array(16386)
for (let i = 0; i < 16384; i += 2) {
buf.writeUInt16BE(0xc000 | i, i + 2)
const value = 0xc000 | i
buf[i + 2] = (value >> 8) & 0xff
buf[i + 3] = value & 0xff
}
t.ok(packet.name.decode(buf, 16384) === '.')
@ -590,9 +605,9 @@ tape('nsec', function (t) {
})
// Test with the sample NSEC from https://tools.ietf.org/html/rfc4034#section-4.3
var sampleNSEC = new Uint8Array('003704686f7374076578616d706c6503636f6d00' +
var sampleNSEC = unhexlify('003704686f7374076578616d706c6503636f6d00' +
'0006400100000003041b000000000000000000000000000000000000000000000' +
'000000020', 'hex')
'000000020')
var decoded = packet.nsec.decode(sampleNSEC)
t.ok(compare(t, decoded, {
nextDomain: 'host.example.com',
@ -835,7 +850,7 @@ tape('https svcb', function (t) {
priority: 1,
name: 'foo.example.com',
values: {
key667: unhexlify('68 65 6c 6c 6f d2 71 6f 6f').toString('utf-8')
key667: new TextDecoder('utf-8').decode(unhexlify('68 65 6c 6c 6f d2 71 6f 6f'))
}
},
true // skipEncode, we cannot encode an unknown key
@ -978,6 +993,7 @@ tape('cloudflare real world svcb/https', (t) => {
{
name: 'community.cloudflare.com',
type: 'HTTPS',
qu_bit: false,
class: 'IN'
}
],
@ -1080,6 +1096,7 @@ tape('google resolver SVCB real world', function (t) {
{
name: '_dns.resolver.arpa',
type: 'SVCB',
qu_bit: false,
class: 'IN'
}
],