Skip to content

Instantly share code, notes, and snippets.

@dumbmoron
Created June 26, 2025 12:01
Show Gist options
  • Select an option

  • Save dumbmoron/ff423ebdbe5491834c12dc16adee519f to your computer and use it in GitHub Desktop.

Select an option

Save dumbmoron/ff423ebdbe5491834c12dc16adee519f to your computer and use it in GitHub Desktop.
type Address = {
type: 4 | 6,
value: bigint
};
type Prefix = {
prefix: Address,
length: number
};
const parse_num = (number: string, base: number = 10): bigint => {
const n = parseInt(number, base);
const s = n.toString(base);
if (isNaN(n)) {
throw new Error(`${number} is not a number`);
}
if (s !== number) {
throw new Error(`extraneous data in string: ${number.substring(s.length)}`);
}
return BigInt(n);
}
const parse_v4 = (addr: string): Prefix => {
let out: Prefix = { prefix: { type: 4, value: 0n }, length: 32 };
const slash = addr.indexOf('/');
if (slash === -1) {
out.length = 32;
} else {
const length_s = addr.substring(slash + 1);
const length = parseInt(length_s, 10);
if (isNaN(length) || length < 0 || length > 32) {
throw new Error('invalid length: ' + length_s);
}
out.length = length;
addr = addr.slice(0, slash);
}
const parts = addr.split('.');
if (parts.length > 4) {
throw new Error('invalid ipv4: ' + addr);
}
for (let i = 0; i < 4; ++i) {
if (parts[i] !== undefined) {
out.prefix.value |= parse_num(parts[i]) << (32n - 8n * BigInt(i + 1));
}
}
return out;
}
const parse_v6 = (addr: string): Prefix => {
let out: Prefix = { prefix: { type: 6, value: 0n }, length: 128 };
const slash = addr.indexOf('/');
if (slash === -1) {
out.length = 128;
} else {
const length_s = addr.substring(slash + 1);
const length = parseInt(length_s, 10);
if (isNaN(length) || length < 0 || length > 128) {
throw new Error('invalid length: ' + length_s);
}
out.length = length;
addr = addr.slice(0, slash);
}
const err = new Error('invalid ipv6: ' + addr);
const parts = addr.split(':');
if (parts.length < 2 || parts.length > 8) {
throw err;
}
let tail = 0n, writeIntoTail = false;
for (let i = 0; i < 8; ++i) {
if (parts[i] === undefined) {
break;
}
if (parts[i] === '') {
if (parts[i + 1] === '') {
i++;
}
writeIntoTail = true;
continue;
}
const octet = parse_num(parts[i], 16);
if (writeIntoTail) {
tail <<= 16n;
tail |= octet;
} else {
const offset = 128n - 16n * BigInt(i + 1);
out.prefix.value |= octet << offset;
}
}
out.prefix.value |= tail;
return out;
}
export const parse = (addr: string): Prefix => {
let value: Prefix;
if (addr.includes('.') && (value = parse_v4(addr)))
return value;
if (addr.includes(':') && (value = parse_v6(addr)))
return value;
throw "unknown address format";
}
export const stringify = (addr: Prefix | Address): string => {
let str = '';
if ('prefix' in addr) {
str += '/' + addr.length;
addr = addr.prefix;
}
let value = addr.value;
if (addr.type === 4) {
for (let i = 0; i < 4; ++i) {
str = '.' + (value & 0xffn) + str;
value >>= 8n;
}
} else if (addr.type === 6) {
let addr_str = '';
for (let i = 0; i < 8; ++i) {
let octet = (value & 0xffffn).toString(16);
if (octet === '0') {
octet = 'x';
}
addr_str = ':' + octet + addr_str;
value >>= 16n;
}
addr_str = addr_str
.replace(/(x:)+/, '::')
.replace(':::', '::')
.replace(/::x$/, '::')
.replaceAll('x', '0');
str = addr_str + str;
}
return str.substring(1);
}
export function* split(addr: Prefix, blockSize: number): Generator<Prefix> {
if (blockSize < addr.length) {
throw new Error(`requested length ${blockSize} larger than existing length ${addr.length}`);
}
const bits = BigInt(blockSize - addr.length);
for (let i = 0n; i < 2n ** bits; ++i) {
const originalLength = BigInt(addr.length);
const subnet = structuredClone(addr);
subnet.length = blockSize;
// clear all bits that are not part of the original block
subnet.prefix.value &= (1n << originalLength) - 1n << 128n - originalLength;
subnet.prefix.value |= i << (128n - BigInt(blockSize));
yield subnet;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment