--- /dev/null
+#!/usr/bin/env node
+
+'use strict';
+
+const BLOCKSIZE = 3;
+const CHARS1 = [
+ ' ',
+ ...'0123456789'.split(''),
+ ...'abcdefghijklmnopqrstuvwxyz'.split(''),
+ '_',
+ '-',
+];
+const SHIFT = BigInt(Math.ceil(Math.log2(CHARS1.length ** BLOCKSIZE)));
+const MASK = 2n**SHIFT - 1n;
+
+const CHARSN = [...Array(BLOCKSIZE - 1)].reduce((acc) => acc.map((v1) => CHARS1.map((v2) => ''+v1+v2)).flat(), CHARS1);
+const FMAP = new Map(CHARSN.map((v, i) => [''+v, BigInt(i)]));
+const RMAP = new Map(CHARSN.map((v, i) => [BigInt(i), ''+v]));
+
+function main(arg1, arg2) {
+ if (!arg1) {
+ console.error('Usage: hexhost fdxx::4a59954e');
+ console.error('Usage: hexhost fdxx:: hostname');
+ process.exit(1);
+ }
+
+ if (arg2) {
+ const prefix = arg1;
+ const suffix = encode(arg2).replaceAll(/(.{4})/g, '$1:').replace(/:$/, '');
+ console.log(prefix + suffix);
+ } else {
+ const [, suffix] = arg1.split(/::|:0:/);
+ console.log(decode(suffix));
+ }
+}
+
+function decode(input) {
+ input = input && input.replaceAll(':', '');
+ if (!input) {
+ throw new Error('No suffix found');
+ }
+ input = BigInt('0x' + input);
+ let output = [];
+ while (input > 0) {
+ const encodedBlock = input & MASK;
+ input >>= SHIFT;
+ const block = RMAP.get(encodedBlock);
+ if (block !== undefined) {
+ output.push(block);
+ }
+ }
+ return output.reverse().join('').trim();
+}
+
+function encode(input) {
+ if (input.length / BLOCKSIZE > (64n / SHIFT)) {
+ throw new Error('Input is too long to fit in a /64!');
+ }
+
+ input = input.toLowerCase();
+
+ let out = BigInt(0);
+ for (let i = 0; i < input.length; i += BLOCKSIZE) {
+ const block = input.substring(i, i + BLOCKSIZE).padEnd(BLOCKSIZE);
+ const encodedBlock = FMAP.get(block);
+ if (encodedBlock !== undefined) {
+ out = (out << SHIFT) + encodedBlock;
+ }
+ }
+ return out.toString(16);
+}
+
+main(process.argv[2], process.argv[3]);