Created
June 26, 2025 12:01
-
-
Save dumbmoron/ff423ebdbe5491834c12dc16adee519f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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