Skip to content

Commit

Permalink
feat: add quic support
Browse files Browse the repository at this point in the history
  • Loading branch information
wemeetagain committed Sep 4, 2024
1 parent 628876d commit fcfd85c
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 16 deletions.
113 changes: 97 additions & 16 deletions packages/enr/src/enr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,8 @@ export function decodeTxt(encoded: string): ENRData {

// IP / Protocol

export type Protocol = "udp" | "tcp" | "udp4" | "udp6" | "tcp4" | "tcp6";
/** Protocols automagically supported by this library */
export type Protocol = "udp" | "tcp" | "quic" | "udp4" | "udp6" | "tcp4" | "tcp6" | "quic4" | "quic6";

export function getIPValue(kvs: ReadonlyMap<ENRKey, ENRValue>, key: string, multifmtStr: string): string | undefined {
const raw = kvs.get(key);
Expand Down Expand Up @@ -191,6 +192,57 @@ export function portToBuf(port: number): Uint8Array {
return buf;
}

export function parseLocationMultiaddr(ma: Multiaddr): {
family: 4 | 6;
ip: Uint8Array;
protoName: "udp" | "tcp" | "quic";
protoVal: Uint8Array;
} {
const protoNames = ma.protoNames();
const tuples = ma.tuples();
let family: 4 | 6;
let protoName: "udp" | "tcp" | "quic";

if (protoNames[0] === "ip4") {
family = 4;
} else if (protoNames[0] === "ip6") {
family = 6;
} else {
throw new Error("Invalid multiaddr: must start with ip4 or ip6");
}
if (tuples[0][1] == null) {
throw new Error("Invalid multiaddr: ip address is missing");
}
const ip = tuples[0][1];

if (protoNames[1] === "udp") {
protoName = "udp";
} else if (protoNames[1] === "tcp") {
protoName = "tcp";
} else {
throw new Error("Invalid multiaddr: must have udp or tcp protocol");
}
if (tuples[1][1] == null) {
throw new Error("Invalid multiaddr: udp or tcp port is missing");
}
const protoVal = tuples[1][1];

if (protoNames.length === 3) {
if (protoNames[2] === "quic-v1") {
if (protoName !== "udp") {
throw new Error("Invalid multiaddr: quic protocol must be used with udp");
}
protoName = "quic";
} else {
throw new Error("Invalid multiaddr: unknown protocol");
}
} else if (protoNames.length > 2) {
throw new Error("Invalid multiaddr: unknown protocol");
}

return { family, ip, protoName, protoVal };
}

// Classes

export abstract class BaseENR {
Expand Down Expand Up @@ -228,6 +280,9 @@ export abstract class BaseENR {
get udp(): number | undefined {
return getProtocolValue(this.kvs, "udp");
}
get quic(): number | undefined {
return getProtocolValue(this.kvs, "quic");
}
get ip6(): string | undefined {
return getIPValue(this.kvs, "ip6", "ip6");
}
Expand All @@ -237,13 +292,19 @@ export abstract class BaseENR {
get udp6(): number | undefined {
return getProtocolValue(this.kvs, "udp6");
}
get quic6(): number | undefined {
return getProtocolValue(this.kvs, "quic6");
}
getLocationMultiaddr(protocol: Protocol): Multiaddr | undefined {
if (protocol === "udp") {
return this.getLocationMultiaddr("udp4") || this.getLocationMultiaddr("udp6");
}
if (protocol === "tcp") {
return this.getLocationMultiaddr("tcp4") || this.getLocationMultiaddr("tcp6");
}
if (protocol === "quic") {
return this.getLocationMultiaddr("quic4") || this.getLocationMultiaddr("quic6");
}
const isIpv6 = protocol.endsWith("6");
const ipVal = this.kvs.get(isIpv6 ? "ip6" : "ip");
if (!ipVal) {
Expand All @@ -252,13 +313,17 @@ export abstract class BaseENR {

const isUdp = protocol.startsWith("udp");
const isTcp = protocol.startsWith("tcp");
const isQuic = protocol.startsWith("quic");
let protoName, protoVal;
if (isUdp) {
protoName = "udp";
protoVal = isIpv6 ? this.kvs.get("udp6") : this.kvs.get("udp");
} else if (isTcp) {
protoName = "tcp";
protoVal = isIpv6 ? this.kvs.get("tcp6") : this.kvs.get("tcp");
} else if (isQuic) {
protoName = "udp";
protoVal = isIpv6 ? this.kvs.get("quic6") : this.kvs.get("quic");
} else {
return undefined;
}
Expand All @@ -282,7 +347,11 @@ export abstract class BaseENR {
maBuf.set(protoBuf, 1 + ipByteLen);
maBuf.set(protoVal, 1 + ipByteLen + protoBuf.length);

return multiaddr(maBuf);
const ma = multiaddr(maBuf);
if (isQuic) {
return ma.encapsulate("/quic-v1");
}
return ma;
}
async getFullMultiaddr(protocol: Protocol): Promise<Multiaddr | undefined> {
const locationMultiaddr = this.getLocationMultiaddr(protocol);
Expand Down Expand Up @@ -504,6 +573,16 @@ export class SignableENR extends BaseENR {
this.set("udp", portToBuf(port));
}
}
get quic(): number | undefined {
return getProtocolValue(this.kvs, "quic");
}
set quic(port: number | undefined) {
if (port === undefined) {
this.delete("quic");
} else {
this.set("quic", portToBuf(port));
}
}
get ip6(): string | undefined {
return getIPValue(this.kvs, "ip6", "ip6");
}
Expand Down Expand Up @@ -534,23 +613,25 @@ export class SignableENR extends BaseENR {
this.set("udp6", portToBuf(port));
}
}
setLocationMultiaddr(multiaddr: Multiaddr): void {
const protoNames = multiaddr.protoNames();
if (protoNames.length !== 2 && protoNames[1] !== "udp" && protoNames[1] !== "tcp") {
throw new Error("Invalid multiaddr");
}
const tuples = multiaddr.tuples();
if (!tuples[0][1] || !tuples[1][1]) {
throw new Error("Invalid multiaddr");
get quic6(): number | undefined {
return getProtocolValue(this.kvs, "quic6");
}
set quic6(port: number | undefined) {
if (port === undefined) {
this.delete("quic6");
} else {
this.set("quic6", portToBuf(port));
}
}
setLocationMultiaddr(multiaddr: Multiaddr): void {
const { family, ip, protoName, protoVal } = parseLocationMultiaddr(multiaddr);

// IPv4
if (tuples[0][0] === 4) {
this.set("ip", tuples[0][1]);
this.set(protoNames[1], tuples[1][1]);
if (family === 4) {
this.set("ip", ip);
this.set(protoName, protoVal);
} else {
this.set("ip6", tuples[0][1]);
this.set(protoNames[1] + "6", tuples[1][1]);
this.set("ip6", ip);
this.set(protoName + "6", protoVal);
}
}

Expand Down
47 changes: 47 additions & 0 deletions packages/enr/test/unit/enr.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,21 +117,47 @@ describe("ENR multiaddr support", () => {
expect(record.kvs.get("ip")).to.deep.equal(tuples1[0][1]);
expect(record.kvs.get("tcp")).to.deep.equal(tuples1[1][1]);
});
it("should get / set QUIC multiaddr", () => {
const multi0 = multiaddr("/ip4/127.0.0.1/udp/30303/quic-v1");
const tuples0 = multi0.tuples();

if (!tuples0[0][1] || !tuples0[1][1]) {
throw new Error("invalid multiaddr");
}

// set underlying records
record.set("ip", tuples0[0][1]);
record.set("quic", tuples0[1][1]);
// and get the multiaddr
expect(record.getLocationMultiaddr("quic")!.toString()).to.equal(multi0.toString());
// set the multiaddr
const multi1 = multiaddr("/ip4/0.0.0.0/udp/30300/quic-v1");
record.setLocationMultiaddr(multi1);
// and get the multiaddr
expect(record.getLocationMultiaddr("quic")!.toString()).to.equal(multi1.toString());
// and get the underlying records
const tuples1 = multi1.tuples();
expect(record.kvs.get("ip")).to.deep.equal(tuples1[0][1]);
expect(record.kvs.get("quic")).to.deep.equal(tuples1[1][1]);
});

describe("location multiaddr", async () => {
const ip4 = "127.0.0.1";
const ip6 = "::1";
const tcp = 8080;
const udp = 8080;
const quic = 8081;

const peerId = await createSecp256k1PeerId();
const enr = SignableENR.createFromPeerId(peerId);
enr.ip = ip4;
enr.ip6 = ip6;
enr.tcp = tcp;
enr.udp = udp;
enr.quic = quic;
enr.tcp6 = tcp;
enr.udp6 = udp;
enr.quic6 = quic;

it("should properly create location multiaddrs - udp4", () => {
expect(enr.getLocationMultiaddr("udp4")).to.deep.equal(multiaddr(`/ip4/${ip4}/udp/${udp}`));
Expand All @@ -141,6 +167,10 @@ describe("ENR multiaddr support", () => {
expect(enr.getLocationMultiaddr("tcp4")).to.deep.equal(multiaddr(`/ip4/${ip4}/tcp/${tcp}`));
});

it("should properly create location multiaddrs - quic4", () => {
expect(enr.getLocationMultiaddr("quic4")).to.deep.equal(multiaddr(`/ip4/${ip4}/udp/${quic}/quic-v1`));
});

it("should properly create location multiaddrs - udp6", () => {
expect(enr.getLocationMultiaddr("udp6")).to.deep.equal(multiaddr(`/ip6/${ip6}/udp/${udp}`));
});
Expand All @@ -149,6 +179,10 @@ describe("ENR multiaddr support", () => {
expect(enr.getLocationMultiaddr("tcp6")).to.deep.equal(multiaddr(`/ip6/${ip6}/tcp/${tcp}`));
});

it("should properly create location multiaddrs - quic6", () => {
expect(enr.getLocationMultiaddr("quic6")).to.deep.equal(multiaddr(`/ip6/${ip6}/udp/${quic}/quic-v1`));
});

it("should properly create location multiaddrs - udp", () => {
// default to ip4
expect(enr.getLocationMultiaddr("udp")).to.deep.equal(multiaddr(`/ip4/${ip4}/udp/${udp}`));
Expand All @@ -174,6 +208,19 @@ describe("ENR multiaddr support", () => {
expect(enr.getLocationMultiaddr("tcp")).to.deep.equal(multiaddr(`/ip4/${ip4}/tcp/${tcp}`));
enr.ip6 = ip6;
});

it("should properly create location multiaddrs - quic", () => {
// default to ip4
expect(enr.getLocationMultiaddr("quic")).to.deep.equal(multiaddr(`/ip4/${ip4}/udp/${quic}/quic-v1`));
// if ip6 is set, use it
enr.ip = undefined;
expect(enr.getLocationMultiaddr("quic")).to.deep.equal(multiaddr(`/ip6/${ip6}/udp/${quic}/quic-v1`));
// if ip6 does not exist, use ip4
enr.ip6 = undefined;
enr.ip = ip4;
expect(enr.getLocationMultiaddr("quic")).to.deep.equal(multiaddr(`/ip4/${ip4}/udp/${quic}/quic-v1`));
enr.ip6 = ip6;
});
});
});

Expand Down

0 comments on commit fcfd85c

Please sign in to comment.