import { HostName, HostNameBase, DnsType, HostNameTemplate } from "./HostName.js"; export const child = Symbol("child"); export type HostType = "gateway" | null | "vagrant" | "domain" | "root-host"; export class Resolve { static fromHostName(hostname: HostNameBase) { return makeResolve(hostname, (e: Resolve, i: number) => { return new Resolve(e.host, null, e.dns, null, null, e.parent); }); } [child]: Resolve | null; constructor( public host: string, public _type: HostType, public dns: DnsType, public gateway: string | null = null, _child: Resolve | null = null, public parent: Resolve | null = null ) { this[child] = _child; this.type = _type; } get debug() { // return JSON.stringify(this, (key: string | number, value: any): any => { // if (key == "child") { // return undefined; // } // return value; // }, 3); return JSON.stringify(this, null, 3); } get debugStr() { return this.makeString(true); } get reconstructed() { return this.makeString(false); } makeString(dbg: boolean) { let r: Resolve | null = this; let str = ""; while (r) { str = `${str}${new HostName(r.host).first.name}${dbg ? (r.type == null ? "?" : `(${r.type}${r.type == "vagrant" ? (r.gateway ? `@${r.gateway}` : "") : ""})`) : ""}${r.parent == null ? "" : ((r.dns == "dashed") ? "-" : ".")}`; r = r.parent; } return str; } get isGateway() { return this._type == "gateway"; } get grandParent(): Resolve { let recur = (x: Resolve): Resolve => { if (x.parent) { return recur(x.parent) } return x; } return recur(this); } learnFrom(teacher: Resolve): Resolve { let iterA = this.grandParent; let iterB = teacher.grandParent; let combine = (type1: string | null, type2: string | null) => { if (type1 != null && type2 !== null && type1 != type2) { throw new Error("type mismatch"); } return (type1 == null) ? type2 : type1 } let result = this.copy(); let iterResult = result.grandParent; while (true) { if (iterA.host !== iterB.host) { console.log(`${iterA.host} !== ${iterB.host}`) break; } let type = combine(iterA.type, iterB.type) as HostType; let gateway = combine(iterA.gateway, iterB.gateway); iterResult.type = type; iterResult.gateway = gateway; result.infer(); if (iterA[child] && iterB[child]) { iterA = iterA[child]!; iterB = iterB[child]!; iterResult = iterResult[child]!; } else { break; } } result.infer(); return result; } get type() { return this._type; } set type(v: HostType) { this._type = v; this.infer(); } infer() { let goUp = (x: Resolve) => { if (x._type == "root-host") { let goUpDomain = (x: Resolve) => { x._type = "domain"; if (x.parent) { goUpDomain(x.parent); } } if (x.parent) { goUpDomain(x.parent); } } if (x.parent) { goUp(x.parent); } if (x[child]) { let goDown = (x: Resolve, parent: Resolve) => { if (parent.isGateway) { x._type = "gateway"; } else if (parent._type == "vagrant") { x._type = "vagrant" } if (x[child]) { goDown(x[child]!, x); } } goDown(x[child]!, x); } } goUp(this); } // flatten(): Resolve[] { // let arr = []; // let r: Resolve = this; // while (r) { // arr.push(r); // if (!r.parent) { // break; // } // r = r.parent; // } // return arr; // } get closestResolvedGateway(): Resolve | null { let x: Resolve | null = this; while (x && (x.type == null || (x.type == "vagrant" && x.gateway == null))) { x = x.parent; } return x; } // get count() { // let cnt = 0; // let goUp = (x: Resolve | null) => { // cnt++; // if (x == null) { // return; // } // goUp(x.parent); // } // goUp(this.parent); // return cnt; // } copy() { let parent: Resolve | null = null; let goUp = (x: Resolve, kid: Resolve | null): Resolve => { let copy = new Resolve(x.host, x._type, x.dns, x.gateway, kid, null); if (x.parent) { copy.parent = goUp(x.parent, copy); } return copy; } let copy = goUp(this, null); // console.log(`${this.debugStr} == ${copy.debugStr}`); return copy; } // /** // * Helps locate a gateway or a vagrant host given the information that is one this burpa node (this process). // * This is done by filling in the type and gateways for the different components in the host. // * // * This function plays a part in burpasys algorithm. // * // * @param unresolved The host name that is unresolved or partially unresolved. // * @returns A new copy of the resolve structure with details filled in with known information at this node // */ // resolfveRoot(localHostName: HostName, localGatewayName: HostName): Resolve | null { // let resolve = (type: "gateway" | "vagrant") => { // let root = (type == "gateway") ? // resolveAsRootGateway(localGatewayName) // : resolveAsRootVagrant(localHostName, localGatewayName.name); // return this.learnFrom(root); // } // return resolve("vagrant") || resolve("gateway"); // } } export function makeResolve(host: HostNameBase, fn: (e: Resolve, i: number) => Resolve) { let i; let hosts = host.hosts; let leafResolve: Resolve | null = null; let accumulatedName = ""; let parent: Resolve | null = null; for (i = hosts.length - 1; i >= 0; i--) { if (accumulatedName == "") { accumulatedName = hosts[i].name; } else { accumulatedName = `${hosts[i].name}${hosts[i].dns == "dashed" ? "-" : "."}${accumulatedName}`; } leafResolve = fn(new Resolve(accumulatedName, null, hosts[i].dns, null, null, leafResolve), i); if (parent) { parent[child] = leafResolve; //leafResolve.child = kid; //console.log(`${parent.host} has kid ${parent[child]!.host}`); } parent = leafResolve; } // if (leafResolve) { // leafResolve.type = type; // leafResolve.gateway = gateway; // } return leafResolve as Resolve; } /** * Annotate the host as a root gateway. This means that the domains parenting this domain name * must be network domains without a burpa gateway. * * @returns */ export function resolveAsRootVagrant(name: HostNameBase, gateway: string): Resolve { let resolve = makeResolve(name, (e: Resolve, i: number) => { return new Resolve(e.host, "domain", e.dns, null, null, e.parent); }); resolve.type = "root-host"; resolve.gateway = gateway; return resolve; } /** * Annotate the host as a root gateway. This means that the domains parenting this domain name * must be network domains without a burpa gateway. * * @returns */ export function resolveAsRootGateway(name: HostName): Resolve { let resolve = makeResolve(name, (e: Resolve, i: number) => { return new Resolve(e.host, "domain", e.dns, null, null, e.parent); }); resolve.type = "gateway"; resolve.gateway = name.nameTemplate; return resolve; } // export function matchHosts(longer: Resolve, shorter: Resolve): Resolve { // if (longer.count < shorter.count) { // throw Error("Not implemented"); // } // let ret = new Resolve(longer.host, longer.type, longer.dns, longer.gateway, null, longer.parent); // let matchParent = (kid: Resolve) => { // if (kid.parent != null && kid.parent.host == shorter.host) { // kid.parent = shorter; // return true; // } // // if (kid.type !== "unknown") { // // throw err(`Trying to resolve an already resolved host`); // // } // return false; // } // let kid: Resolve | null = ret; // while (kid && !matchParent(kid)) { // let parentcopy: Resolve | null = (kid.parent) ? new Resolve(kid.parent.host, kid.parent.type, kid.parent.dns, kid.parent.gateway, null, kid.parent.parent) : null; // kid.parent = parentcopy; // kid = parentcopy; // } // return ret; // } // /** // * Combines the knowledge of two host chains. // * @param a A host that shares parent hosts with host `b` // * @param b A host that shares parent hosts with host `a` // * @returns A copy of `a` and a copy of `b` with the combined knowledge // */ // export function match(a: Resolve, b: Resolve): { a: Resolve, b: Resolve } { // let iterA = a.grandParent; // let iterB = b.grandParent; // let combine = (type1: string | null, type2: string | null) => { // if (type1 != null && type2 !== null && type1 != type2) { // throw new Error("type mismatch"); // } // return (type1 == null) ? type2 : type1 // } // let resultA = a.copy(); // let resultB = b.copy(); // let iterResA = resultA.grandParent; // let iterResB = resultB.grandParent; // while (true) { // if (iterA.host !== iterB.host) { // console.log(`${iterA.host} !== ${iterB.host}`) // break; // } // let type = combine(iterA.type, iterB.type) as HostType; // let gateway = combine(iterA.gateway, iterB.gateway); // iterResA.type = type; // iterResB.type = type; // iterResA.gateway = gateway; // iterResB.gateway = gateway; // resultA.infer(); // resultB.infer(); // if (iterA[child] && iterB[child]) { // iterA = iterA[child]!; // iterB = iterB[child]!; // iterResA = iterResA[child]!; // iterResB = iterResB[child]!; // } else { // break; // } // } // resultA.infer(); // resultB.infer(); // return { a: resultA, b: resultB } // } // export function isDownstreamOf(name: string, tophost: string) { // let resolve: Resolve | null = Resolve.fromHostName(new HostName(name)); // while (resolve) { // if (resolve.host == tophost) { // return true; // } // resolve = resolve.parent; // } // return false; // }