From 2fdbaad1c457c3bbca4fb03cc0845f3a5ab37893 Mon Sep 17 00:00:00 2001 From: dozyio Date: Sat, 25 Jan 2025 22:00:39 +0000 Subject: [PATCH 1/2] feat: add support to for ipv4 mapped addresses --- README.md | 16 ++++++++++++---- src/parse.ts | 39 +++++++++++++++++++++++++++++++++++++-- test/index.test.ts | 39 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 87 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index df3eafa..44188ac 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ Parse a string into IP address bytes import { expect } from "chai"; import { parseIPv4, + parseIPv4Mapped, parseIPv6, parseIP, } from "@chainsafe/is-ip/parse"; @@ -54,12 +55,19 @@ expect(b1).to.deep.equal(Uint8Array.from([127, 0, 0, 1])); const b2 = parseIPv6("::1"); expect(b2).to.deep.equal(Uint8Array.from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1])); +// parse an IPv4 string into IPv4-mapped IPv6 bytes +const b3 = parseIPv4Mapped("127.0.0.1"); +expect(b3).to.deep.equal(Uint8Array.from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 127, 0, 0, 1])); + // parse a string into either IPv4 or IPv6 bytes -const b3 = parseIP("127.0.0.1"); -expect(b3).to.deep.equal(Uint8Array.from([127, 0, 0, 1])); +const b4 = parseIP("127.0.0.1"); +expect(b4).to.deep.equal(Uint8Array.from([127, 0, 0, 1])); + +const b5 = parseIP("::1"); +expect(b5).to.deep.equal(Uint8Array.from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1])); -const b4 = parseIP("::1"); -expect(b4).to.deep.equal(Uint8Array.from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1])); +const b6 = parseIP("127.0.0.1", true); +expect(b6).to.deep.equal(Uint8Array.from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 127, 0, 0, 1])); // parseIP* functions return undefined on invalid input expect(parseIP("not an IP")).to.equal(undefined); diff --git a/src/parse.ts b/src/parse.ts index de3ff38..c2af006 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -14,6 +14,25 @@ export function parseIPv4(input: string): Uint8Array | undefined { return parser.new(input).parseWith(() => parser.readIPv4Addr()); } +/** Parse IPv4 `input` into IPv6 with IPv4-mapped bytes, eg ::ffff:1.2.3.4 */ +export function parseIPv4Mapped(input: string): Uint8Array | undefined { + if (input.length > MAX_IPV4_LENGTH) { + return undefined; + } + + const ipv4 = parser.new(input).parseWith(() => parser.readIPv4Addr()); + if (ipv4 === undefined) { + return undefined + } + + return Uint8Array.from([ + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0xff, 0xff, + ipv4[0], ipv4[1], ipv4[2], ipv4[3] + ]) +} + /** Parse `input` into IPv6 bytes. */ export function parseIPv6(input: string): Uint8Array | undefined { // strip zone index if it is present @@ -27,13 +46,29 @@ export function parseIPv6(input: string): Uint8Array | undefined { } /** Parse `input` into IPv4 or IPv6 bytes. */ -export function parseIP(input: string): Uint8Array | undefined { +export function parseIP(input: string, mapIPv4ToIPv6 = false): Uint8Array | undefined { // strip zone index if it is present if (input.includes("%")) { input = input.split("%")[0]; } + if (input.length > MAX_IPV6_LENGTH) { return undefined; } - return parser.new(input).parseWith(() => parser.readIPAddr()); + + const addr = parser.new(input).parseWith(() => parser.readIPAddr()); + if (!addr) { + return undefined + } + + if (mapIPv4ToIPv6 && addr.length === 4) { + return Uint8Array.from([ + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0xff, 0xff, + addr[0], addr[1], addr[2], addr[3] + ]); + } + + return addr } diff --git a/test/index.test.ts b/test/index.test.ts index e36fc00..266706d 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,5 +1,5 @@ import { expect } from "chai"; -import { parseIP, parseIPv4, parseIPv6 } from "../src/parse.js"; +import { parseIP, parseIPv4, parseIPv6, parseIPv4Mapped } from "../src/parse.js"; import { isIP, isIPv4, isIPv6 } from "../src/is-ip.js"; const validIPv4 = [ @@ -112,6 +112,29 @@ describe("parseIPv6", () => { }); }); +describe("parse IPv4 as IPv4-mapped IPv6 address", () => { + it("should return an IPv4-mapped address", () => { + const testCase = [ + { + input: "1.2.3.4", + output: Uint8Array.from([ + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0xff, 0xff, + 1, 2, 3, 4]), + } + ] + for (const { input, output } of testCase) { + const r = parseIPv4Mapped(input) + if (r === undefined) { + throw new Error('undefined address') + } + + expect(r).to.deep.equal(output); + } + }); +}); + describe("parseIP", () => { it("should return on valid IP strings", () => { for (const { input, output } of [...validIPv4, ...validIPv6]) { @@ -120,10 +143,24 @@ describe("parseIP", () => { } }); + it("should return on valid IP strings when mapping IPv4", () => { + for (const { input, output } of [...validIPv4]) { + expect(parseIP(input)).to.deep.equal(output); + expect(isIP(input)).to.equal(true); + } + }); + it("should throw on invalid IP strings", () => { for (const input of [...invalidIPv4, ...invalidIPv6]) { expect(parseIP(input)).to.equal(undefined); expect(isIP(input)).to.equal(false); } }); + + it("should throw on invalid IP strings when mapping IPv4", () => { + for (const input of [...invalidIPv4, ...invalidIPv6]) { + expect(parseIP(input, true)).to.equal(undefined); + expect(isIP(input)).to.equal(false); + } + }); }); From d7ea08da0f7cb5e9228c7349a112bce4d3520b49 Mon Sep 17 00:00:00 2001 From: dozyio Date: Sat, 25 Jan 2025 22:05:35 +0000 Subject: [PATCH 2/2] chore: lint --- src/parse.ts | 20 +++++--------------- test/index.test.ts | 14 +++++--------- 2 files changed, 10 insertions(+), 24 deletions(-) diff --git a/src/parse.ts b/src/parse.ts index c2af006..0b63159 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -22,15 +22,10 @@ export function parseIPv4Mapped(input: string): Uint8Array | undefined { const ipv4 = parser.new(input).parseWith(() => parser.readIPv4Addr()); if (ipv4 === undefined) { - return undefined + return undefined; } - return Uint8Array.from([ - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0xff, 0xff, - ipv4[0], ipv4[1], ipv4[2], ipv4[3] - ]) + return Uint8Array.from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, ipv4[0], ipv4[1], ipv4[2], ipv4[3]]); } /** Parse `input` into IPv6 bytes. */ @@ -58,17 +53,12 @@ export function parseIP(input: string, mapIPv4ToIPv6 = false): Uint8Array | unde const addr = parser.new(input).parseWith(() => parser.readIPAddr()); if (!addr) { - return undefined + return undefined; } if (mapIPv4ToIPv6 && addr.length === 4) { - return Uint8Array.from([ - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0xff, 0xff, - addr[0], addr[1], addr[2], addr[3] - ]); + return Uint8Array.from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, addr[0], addr[1], addr[2], addr[3]]); } - return addr + return addr; } diff --git a/test/index.test.ts b/test/index.test.ts index 266706d..456bad4 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -117,17 +117,13 @@ describe("parse IPv4 as IPv4-mapped IPv6 address", () => { const testCase = [ { input: "1.2.3.4", - output: Uint8Array.from([ - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0xff, 0xff, - 1, 2, 3, 4]), - } - ] + output: Uint8Array.from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 1, 2, 3, 4]), + }, + ]; for (const { input, output } of testCase) { - const r = parseIPv4Mapped(input) + const r = parseIPv4Mapped(input); if (r === undefined) { - throw new Error('undefined address') + throw new Error("undefined address"); } expect(r).to.deep.equal(output);