Skip to content

Commit a30c5e4

Browse files
committed
refactor: improve FakeBuffer polyfill
1 parent 61357ae commit a30c5e4

9 files changed

Lines changed: 179 additions & 52 deletions

File tree

src/main/ts/buffer.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
export interface BufferLike extends Omit<Uint8Array, 'slice'> {
2+
readUInt16BE(offset?: number): number
3+
slice(start?: number, end?: number): BufferLike
4+
toString(encoding?: 'hex'): string
5+
}
6+
7+
export const FakeBuffer = {
8+
alloc: (size: number, fill: number = 0): BufferLike => {
9+
if (size < 0)
10+
throw new RangeError('The value of \"size\" is out of range.')
11+
12+
const arr = new Uint8Array(size)
13+
if (fill !== 0)
14+
arr.fill(fill)
15+
16+
const buf = arr as BufferLike
17+
18+
return Object.assign(buf, {
19+
readUInt16BE(offset: number = 0): number {
20+
if (offset < 0 || offset + 2 > (this as any).length)
21+
throw new RangeError(`RangeError: The value of "offset" is out of range. It must be >= 0 and <= 2. Received ${offset}`)
22+
23+
return (this as any)[offset] << 8 | (this as any)[offset + 1]
24+
},
25+
slice(start?: number, end?: number): BufferLike {
26+
const sliced = Uint8Array.prototype.slice.call(this, start, end)
27+
28+
return Object.assign(sliced, {
29+
readUInt16BE: buf.readUInt16BE,
30+
slice: buf.slice,
31+
toString: buf.toString,
32+
}) as BufferLike
33+
},
34+
toString(encoding?: 'hex'): string {
35+
if (encoding !== 'hex')
36+
throw new Error("Only 'hex' encoding is supported in this polyfill")
37+
38+
return Array.from(this as any)
39+
.map((b) => (b as number).toString(16).padStart(2, '0'))
40+
.join('')
41+
},
42+
})
43+
}
44+
}
45+
46+
export const Buffer = (global as any || globalThis).Buffer || FakeBuffer

src/main/ts/core.ts

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { Buffer, type BufferLike } from './buffer.ts'
2+
13
export const IPV4 = 'IPv4'
24
export const IPV6 = 'IPv6'
35

@@ -25,19 +27,6 @@ export const setMode = (mode: 'legacy' | 'strict'): void => {
2527
throw new Error('mode must be either "legacy" or "strict"')
2628
}
2729

28-
export function readUInt16BE(buf: Buffer | Uint8Array | DataView, offset: number = 0): number {
29-
if (typeof (buf as Buffer).readUInt16BE === 'function') {
30-
// Node.js Buffer or feross/buffer polyfill
31-
return (buf as Buffer).readUInt16BE(offset)
32-
}
33-
34-
const view = buf instanceof DataView
35-
? buf
36-
: new DataView(buf.buffer, buf.byteOffset, buf.byteLength)
37-
38-
return view.getUint16(offset, false)
39-
}
40-
4130
// Corresponds Nodejs.NetworkInterfaceBase
4231
export type Family = typeof IPV4 | typeof IPV6
4332
export function normalizeFamily(family?: string | number): Family {
@@ -137,7 +126,7 @@ export const fromLong = (n: number): string => {
137126
export const toLong = (ip: string): number =>
138127
ip.split('.').reduce((acc, octet) => (acc << 8) + Number(octet), 0) >>> 0
139128

140-
export const toString = (buff: Buffer, offset = 0, length?: number): string => {
129+
export const toString = (buff: BufferLike, offset = 0, length?: number): string => {
141130
const o = ~~offset
142131
const l = length || (buff.length - offset)
143132

@@ -148,15 +137,15 @@ export const toString = (buff: Buffer, offset = 0, length?: number): string => {
148137
// IPv6
149138
if (l === 16)
150139
return Array
151-
.from({ length: l / 2 }, (_, i) => readUInt16BE(buff, o + i * 2).toString(16))
140+
.from({ length: l / 2 }, (_, i) => buff.readUInt16BE(o + i * 2).toString(16))
152141
.join(':')
153142
.replace(/(^|:)0(:0)*:0(:|$)/, '$1::$3')
154143
.replace(/:{3,4}/, '::')
155144

156145
throw new Error('Invalid buffer length for IP address')
157146
}
158147

159-
export const toBuffer = (ip: string, buff?: Buffer, offset = 0): Buffer => {
148+
export const toBuffer = (ip: string, buff?: BufferLike, offset = 0): BufferLike => {
160149
offset = ~~offset
161150

162151
if (isV4Format(ip)) {
@@ -345,7 +334,7 @@ export const isEqual = (a: string, b: string): boolean => {
345334
for (let i = 0; i < 10; i++) if (bb[i] !== 0) return false
346335

347336
// next 2 bytes must be either 0x0000 or 0xffff (::ffff:ipv4)
348-
const prefix = readUInt16BE(bb, 10)
337+
const prefix = bb.readUInt16BE(10)
349338
if (prefix !== 0 && prefix !== 0xffff) return false
350339

351340
// last 4 bytes must match IPv4 buffer

src/test/js/export.test.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ describe('core', () => {
2020
assert.equal(typeof core.isLoopback, 'function', 'core.isLoopback')
2121
assert.equal(typeof core.isPrivate, 'function', 'core.isPrivate')
2222
assert.equal(typeof core.isPublic, 'function', 'core.isPublic')
23+
assert.equal(typeof core.isSpecial, 'function', 'core.isSpecial')
2324
assert.equal(typeof core.isV4, 'function', 'core.isV4')
2425
assert.equal(typeof core.isV4Format, 'function', 'core.isV4Format')
2526
assert.equal(typeof core.isV6, 'function', 'core.isV6')
@@ -31,7 +32,6 @@ describe('core', () => {
3132
assert.equal(typeof core.normalizeToLong, 'function', 'core.normalizeToLong')
3233
assert.equal(typeof core.not, 'function', 'core.not')
3334
assert.equal(typeof core.or, 'function', 'core.or')
34-
assert.equal(typeof core.readUInt16BE, 'function', 'core.readUInt16BE')
3535
assert.equal(typeof core.setMode, 'function', 'core.setMode')
3636
assert.equal(typeof core.subnet, 'function', 'core.subnet')
3737
assert.equal(typeof core.toBuffer, 'function', 'core.toBuffer')
@@ -70,6 +70,7 @@ describe('index', () => {
7070
assert.equal(typeof index.default.isLoopback, 'function', 'index.default.isLoopback')
7171
assert.equal(typeof index.default.isPrivate, 'function', 'index.default.isPrivate')
7272
assert.equal(typeof index.default.isPublic, 'function', 'index.default.isPublic')
73+
assert.equal(typeof index.default.isSpecial, 'function', 'index.default.isSpecial')
7374
assert.equal(typeof index.default.isV4, 'function', 'index.default.isV4')
7475
assert.equal(typeof index.default.isV4Format, 'function', 'index.default.isV4Format')
7576
assert.equal(typeof index.default.isV6, 'function', 'index.default.isV6')
@@ -81,7 +82,6 @@ describe('index', () => {
8182
assert.equal(typeof index.default.normalizeToLong, 'function', 'index.default.normalizeToLong')
8283
assert.equal(typeof index.default.not, 'function', 'index.default.not')
8384
assert.equal(typeof index.default.or, 'function', 'index.default.or')
84-
assert.equal(typeof index.default.readUInt16BE, 'function', 'index.default.readUInt16BE')
8585
assert.equal(typeof index.default.setMode, 'function', 'index.default.setMode')
8686
assert.equal(typeof index.default.subnet, 'function', 'index.default.subnet')
8787
assert.equal(typeof index.default.toBuffer, 'function', 'index.default.toBuffer')
@@ -106,6 +106,7 @@ describe('index', () => {
106106
assert.equal(typeof index.ip.isLoopback, 'function', 'index.ip.isLoopback')
107107
assert.equal(typeof index.ip.isPrivate, 'function', 'index.ip.isPrivate')
108108
assert.equal(typeof index.ip.isPublic, 'function', 'index.ip.isPublic')
109+
assert.equal(typeof index.ip.isSpecial, 'function', 'index.ip.isSpecial')
109110
assert.equal(typeof index.ip.isV4, 'function', 'index.ip.isV4')
110111
assert.equal(typeof index.ip.isV4Format, 'function', 'index.ip.isV4Format')
111112
assert.equal(typeof index.ip.isV6, 'function', 'index.ip.isV6')
@@ -117,7 +118,6 @@ describe('index', () => {
117118
assert.equal(typeof index.ip.normalizeToLong, 'function', 'index.ip.normalizeToLong')
118119
assert.equal(typeof index.ip.not, 'function', 'index.ip.not')
119120
assert.equal(typeof index.ip.or, 'function', 'index.ip.or')
120-
assert.equal(typeof index.ip.readUInt16BE, 'function', 'index.ip.readUInt16BE')
121121
assert.equal(typeof index.ip.setMode, 'function', 'index.ip.setMode')
122122
assert.equal(typeof index.ip.subnet, 'function', 'index.ip.subnet')
123123
assert.equal(typeof index.ip.toBuffer, 'function', 'index.ip.toBuffer')
@@ -127,6 +127,7 @@ describe('index', () => {
127127
assert.equal(typeof index.isLoopback, 'function', 'index.isLoopback')
128128
assert.equal(typeof index.isPrivate, 'function', 'index.isPrivate')
129129
assert.equal(typeof index.isPublic, 'function', 'index.isPublic')
130+
assert.equal(typeof index.isSpecial, 'function', 'index.isSpecial')
130131
assert.equal(typeof index.isV4, 'function', 'index.isV4')
131132
assert.equal(typeof index.isV4Format, 'function', 'index.isV4Format')
132133
assert.equal(typeof index.isV6, 'function', 'index.isV6')
@@ -138,7 +139,6 @@ describe('index', () => {
138139
assert.equal(typeof index.normalizeToLong, 'function', 'index.normalizeToLong')
139140
assert.equal(typeof index.not, 'function', 'index.not')
140141
assert.equal(typeof index.or, 'function', 'index.or')
141-
assert.equal(typeof index.readUInt16BE, 'function', 'index.readUInt16BE')
142142
assert.equal(typeof index.setMode, 'function', 'index.setMode')
143143
assert.equal(typeof index.subnet, 'function', 'index.subnet')
144144
assert.equal(typeof index.toBuffer, 'function', 'index.toBuffer')

src/test/ts/buffer.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import assert from 'node:assert'
2+
import {test, describe} from 'vitest'
3+
4+
import { FakeBuffer } from '../../main/ts/buffer.ts'
5+
6+
describe('buffer', () => {
7+
8+
test('FakeBuffer mimics to Buffer', () => {
9+
assert.throws(() => Buffer.alloc(-1), /The value of "size" is out of range/)
10+
assert.throws(() => FakeBuffer.alloc(-1), /The value of "size" is out of range/)
11+
12+
const b1 = Buffer.alloc(4, 1)
13+
assert.equal(b1.length, 4)
14+
assert.equal(b1.toString('hex'), '01010101')
15+
assert.equal(b1.readUInt16BE(), 257)
16+
assert.throws(() => b1.readUInt16BE(3), /The value of "offset" is out of range/)
17+
18+
const b2 = b1.slice(2)
19+
assert.equal(b2.length, 2)
20+
assert.equal(b2.toString('hex'), '0101')
21+
22+
const b3 = FakeBuffer.alloc(4, 1)
23+
assert.equal(b3.length, 4)
24+
assert.equal(b3.toString('hex'), '01010101')
25+
assert.equal(b3.readUInt16BE(), 257)
26+
assert.throws(() => b3.readUInt16BE(3), /The value of "offset" is out of range/)
27+
assert.throws(() => b3.toString('base64'), /Only 'hex' encoding is supported in this polyfill/)
28+
29+
const b4 = b3.slice(2)
30+
assert.equal(b4.length, 2)
31+
assert.equal(b4.toString('hex'), '0101')
32+
})
33+
})

target/cjs/core.cjs

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,48 @@ __export(core_exports, {
3535
normalizeToLong: () => normalizeToLong,
3636
not: () => not,
3737
or: () => or,
38-
readUInt16BE: () => readUInt16BE,
3938
setMode: () => setMode,
4039
subnet: () => subnet,
4140
toBuffer: () => toBuffer,
4241
toLong: () => toLong,
4342
toString: () => toString
4443
});
4544
module.exports = __toCommonJS(core_exports);
45+
46+
// src/main/ts/buffer.ts
47+
var FakeBuffer = {
48+
alloc: (size, fill = 0) => {
49+
if (size < 0)
50+
throw new RangeError('The value of "size" is out of range.');
51+
const arr = new Uint8Array(size);
52+
if (fill !== 0)
53+
arr.fill(fill);
54+
const buf = arr;
55+
return Object.assign(buf, {
56+
readUInt16BE(offset = 0) {
57+
if (offset < 0 || offset + 2 > this.length)
58+
throw new RangeError(`RangeError: The value of "offset" is out of range. It must be >= 0 and <= 2. Received ${offset}`);
59+
return this[offset] << 8 | this[offset + 1];
60+
},
61+
slice(start, end) {
62+
const sliced = Uint8Array.prototype.slice.call(this, start, end);
63+
return Object.assign(sliced, {
64+
readUInt16BE: buf.readUInt16BE,
65+
slice: buf.slice,
66+
toString: buf.toString
67+
});
68+
},
69+
toString(encoding) {
70+
if (encoding !== "hex")
71+
throw new Error("Only 'hex' encoding is supported in this polyfill");
72+
return Array.from(this).map((b) => b.toString(16).padStart(2, "0")).join("");
73+
}
74+
});
75+
}
76+
};
77+
var Buffer2 = (global || globalThis).Buffer || FakeBuffer;
78+
79+
// src/main/ts/core.ts
4680
var IPV4 = "IPv4";
4781
var IPV6 = "IPv6";
4882
var V4_RE = /^(\d{1,3}(\.|$)){4}$/;
@@ -68,13 +102,6 @@ var setMode = (mode) => {
68102
}
69103
throw new Error('mode must be either "legacy" or "strict"');
70104
};
71-
function readUInt16BE(buf, offset = 0) {
72-
if (typeof buf.readUInt16BE === "function") {
73-
return buf.readUInt16BE(offset);
74-
}
75-
const view = buf instanceof DataView ? buf : new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
76-
return view.getUint16(offset, false);
77-
}
78105
function normalizeFamily(family) {
79106
const f = `${family}`.toLowerCase().trim();
80107
return f === "6" || f === IPV6.toLowerCase() ? IPV6 : IPV4;
@@ -146,13 +173,13 @@ var toString = (buff, offset = 0, length) => {
146173
if (l === 4)
147174
return [...buff.subarray(o, o + l)].join(".");
148175
if (l === 16)
149-
return Array.from({ length: l / 2 }, (_, i) => readUInt16BE(buff, o + i * 2).toString(16)).join(":").replace(/(^|:)0(:0)*:0(:|$)/, "$1::$3").replace(/:{3,4}/, "::");
176+
return Array.from({ length: l / 2 }, (_, i) => buff.readUInt16BE(o + i * 2).toString(16)).join(":").replace(/(^|:)0(:0)*:0(:|$)/, "$1::$3").replace(/:{3,4}/, "::");
150177
throw new Error("Invalid buffer length for IP address");
151178
};
152179
var toBuffer = (ip, buff, offset = 0) => {
153180
offset = ~~offset;
154181
if (isV4Format(ip)) {
155-
const res = buff || Buffer.alloc(offset + 4);
182+
const res = buff || Buffer2.alloc(offset + 4);
156183
for (const byte of ip.split("."))
157184
res[offset++] = +byte & 255;
158185
return res;
@@ -173,7 +200,7 @@ var toBuffer = (ip, buff, offset = 0) => {
173200
} else {
174201
while (sections.length < 8) sections.push("0");
175202
}
176-
const res = buff || Buffer.alloc(offset + 16);
203+
const res = buff || Buffer2.alloc(offset + 16);
177204
for (const sec of sections) {
178205
const word = parseInt(sec, 16) || 0;
179206
res[offset++] = word >> 8;
@@ -185,7 +212,7 @@ var toBuffer = (ip, buff, offset = 0) => {
185212
};
186213
var fromPrefixLen = (prefixlen, family) => {
187214
family = prefixlen > 32 ? IPV6 : normalizeFamily(family);
188-
const buff = Buffer.alloc(family === IPV6 ? 16 : 4);
215+
const buff = Buffer2.alloc(family === IPV6 ? 16 : 4);
189216
for (let i = 0; i < buff.length; i++) {
190217
const bits = Math.min(prefixlen, 8);
191218
prefixlen -= bits;
@@ -196,7 +223,7 @@ var fromPrefixLen = (prefixlen, family) => {
196223
var mask = (addr, maskStr) => {
197224
const a = toBuffer(addr);
198225
const m = toBuffer(maskStr);
199-
const out = Buffer.alloc(Math.max(a.length, m.length));
226+
const out = Buffer2.alloc(Math.max(a.length, m.length));
200227
if (a.length === m.length) {
201228
for (let i = 0; i < a.length; i++) out[i] = a[i] & m[i];
202229
} else if (m.length === 4) {
@@ -278,7 +305,7 @@ var isEqual = (a, b) => {
278305
}
279306
if (bb.length === 4) [ab, bb] = [bb, ab];
280307
for (let i = 0; i < 10; i++) if (bb[i] !== 0) return false;
281-
const prefix = readUInt16BE(bb, 10);
308+
const prefix = bb.readUInt16BE(10);
282309
if (prefix !== 0 && prefix !== 65535) return false;
283310
for (let i = 0; i < 4; i++) if (ab[i] !== bb[i + 12]) return false;
284311
return true;
@@ -362,7 +389,6 @@ var isSpecial = (addr) => {
362389
normalizeToLong,
363390
not,
364391
or,
365-
readUInt16BE,
366392
setMode,
367393
subnet,
368394
toBuffer,

target/dts/buffer.d.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export interface BufferLike extends Omit<Uint8Array, 'slice'> {
2+
readUInt16BE(offset?: number): number;
3+
slice(start?: number, end?: number): BufferLike;
4+
toString(encoding?: 'hex'): string;
5+
}
6+
export declare const FakeBuffer: {
7+
alloc: (size: number, fill?: number) => BufferLike;
8+
};
9+
export declare const Buffer: BufferConstructor;

target/dts/core.d.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { type BufferLike } from './buffer.ts';
12
export declare const IPV4 = "IPv4";
23
export declare const IPV6 = "IPv6";
34
export declare const V4_RE: RegExp;
@@ -9,7 +10,6 @@ export declare const isV6Format: (ip: string) => boolean;
910
export declare const isV4: (ip: string) => boolean;
1011
export declare const isV6: (ip: string) => boolean;
1112
export declare const setMode: (mode: "legacy" | "strict") => void;
12-
export declare function readUInt16BE(buf: Buffer | Uint8Array | DataView, offset?: number): number;
1313
export type Family = typeof IPV4 | typeof IPV6;
1414
export declare function normalizeFamily(family?: string | number): Family;
1515
export declare const normalizeAddress: (addr: string | number) => string;
@@ -20,8 +20,8 @@ export declare const isLoopback: (addr: string | number) => boolean;
2020
export declare const loopback: (family?: string | number) => typeof V4_LB | typeof V6_LB;
2121
export declare const fromLong: (n: number) => string;
2222
export declare const toLong: (ip: string) => number;
23-
export declare const toString: (buff: Buffer, offset?: number, length?: number) => string;
24-
export declare const toBuffer: (ip: string, buff?: Buffer, offset?: number) => Buffer;
23+
export declare const toString: (buff: BufferLike, offset?: number, length?: number) => string;
24+
export declare const toBuffer: (ip: string, buff?: BufferLike, offset?: number) => BufferLike;
2525
export declare const fromPrefixLen: (prefixlen: number, family?: string | number) => string;
2626
export declare const mask: (addr: string, maskStr: string) => string;
2727
type Subnet = {

target/dts/index.d.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import * as core from './core.ts';
22
export * from './address.ts';
33
export * from './core.ts';
44
export declare const ip: {
5-
readUInt16BE(buf: Buffer | Uint8Array | DataView, offset?: number): number;
65
normalizeFamily(family?: string | number): core.Family;
76
IPV4: "IPv4";
87
IPV6: "IPv6";
@@ -21,8 +20,8 @@ export declare const ip: {
2120
loopback: (family?: string | number) => "127.0.0.1" | "fe80::1";
2221
fromLong: (n: number) => string;
2322
toLong: (ip: string) => number;
24-
toString: (buff: Buffer, offset?: number, length?: number) => string;
25-
toBuffer: (ip: string, buff?: Buffer, offset?: number) => Buffer;
23+
toString: (buff: import("./buffer.ts").BufferLike, offset?: number, length?: number) => string;
24+
toBuffer: (ip: string, buff?: import("./buffer.ts").BufferLike, offset?: number) => import("./buffer.ts").BufferLike;
2625
fromPrefixLen: (prefixlen: number, family?: string | number) => string;
2726
mask: (addr: string, maskStr: string) => string;
2827
subnet: (addr: string, smask: string) => {

0 commit comments

Comments
 (0)