Skip to content

Commit 31d54c9

Browse files
authored
feat(enr): add quic support (#293)
1 parent 628876d commit 31d54c9

File tree

2 files changed

+144
-16
lines changed

2 files changed

+144
-16
lines changed

packages/enr/src/enr.ts

Lines changed: 97 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,8 @@ export function decodeTxt(encoded: string): ENRData {
161161

162162
// IP / Protocol
163163

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

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

195+
export function parseLocationMultiaddr(ma: Multiaddr): {
196+
family: 4 | 6;
197+
ip: Uint8Array;
198+
protoName: "udp" | "tcp" | "quic";
199+
protoVal: Uint8Array;
200+
} {
201+
const protoNames = ma.protoNames();
202+
const tuples = ma.tuples();
203+
let family: 4 | 6;
204+
let protoName: "udp" | "tcp" | "quic";
205+
206+
if (protoNames[0] === "ip4") {
207+
family = 4;
208+
} else if (protoNames[0] === "ip6") {
209+
family = 6;
210+
} else {
211+
throw new Error("Invalid multiaddr: must start with ip4 or ip6");
212+
}
213+
if (tuples[0][1] == null) {
214+
throw new Error("Invalid multiaddr: ip address is missing");
215+
}
216+
const ip = tuples[0][1];
217+
218+
if (protoNames[1] === "udp") {
219+
protoName = "udp";
220+
} else if (protoNames[1] === "tcp") {
221+
protoName = "tcp";
222+
} else {
223+
throw new Error("Invalid multiaddr: must have udp or tcp protocol");
224+
}
225+
if (tuples[1][1] == null) {
226+
throw new Error("Invalid multiaddr: udp or tcp port is missing");
227+
}
228+
const protoVal = tuples[1][1];
229+
230+
if (protoNames.length === 3) {
231+
if (protoNames[2] === "quic-v1") {
232+
if (protoName !== "udp") {
233+
throw new Error("Invalid multiaddr: quic protocol must be used with udp");
234+
}
235+
protoName = "quic";
236+
} else {
237+
throw new Error("Invalid multiaddr: unknown protocol");
238+
}
239+
} else if (protoNames.length > 2) {
240+
throw new Error("Invalid multiaddr: unknown protocol");
241+
}
242+
243+
return { family, ip, protoName, protoVal };
244+
}
245+
194246
// Classes
195247

196248
export abstract class BaseENR {
@@ -228,6 +280,9 @@ export abstract class BaseENR {
228280
get udp(): number | undefined {
229281
return getProtocolValue(this.kvs, "udp");
230282
}
283+
get quic(): number | undefined {
284+
return getProtocolValue(this.kvs, "quic");
285+
}
231286
get ip6(): string | undefined {
232287
return getIPValue(this.kvs, "ip6", "ip6");
233288
}
@@ -237,13 +292,19 @@ export abstract class BaseENR {
237292
get udp6(): number | undefined {
238293
return getProtocolValue(this.kvs, "udp6");
239294
}
295+
get quic6(): number | undefined {
296+
return getProtocolValue(this.kvs, "quic6");
297+
}
240298
getLocationMultiaddr(protocol: Protocol): Multiaddr | undefined {
241299
if (protocol === "udp") {
242300
return this.getLocationMultiaddr("udp4") || this.getLocationMultiaddr("udp6");
243301
}
244302
if (protocol === "tcp") {
245303
return this.getLocationMultiaddr("tcp4") || this.getLocationMultiaddr("tcp6");
246304
}
305+
if (protocol === "quic") {
306+
return this.getLocationMultiaddr("quic4") || this.getLocationMultiaddr("quic6");
307+
}
247308
const isIpv6 = protocol.endsWith("6");
248309
const ipVal = this.kvs.get(isIpv6 ? "ip6" : "ip");
249310
if (!ipVal) {
@@ -252,13 +313,17 @@ export abstract class BaseENR {
252313

253314
const isUdp = protocol.startsWith("udp");
254315
const isTcp = protocol.startsWith("tcp");
316+
const isQuic = protocol.startsWith("quic");
255317
let protoName, protoVal;
256318
if (isUdp) {
257319
protoName = "udp";
258320
protoVal = isIpv6 ? this.kvs.get("udp6") : this.kvs.get("udp");
259321
} else if (isTcp) {
260322
protoName = "tcp";
261323
protoVal = isIpv6 ? this.kvs.get("tcp6") : this.kvs.get("tcp");
324+
} else if (isQuic) {
325+
protoName = "udp";
326+
protoVal = isIpv6 ? this.kvs.get("quic6") : this.kvs.get("quic");
262327
} else {
263328
return undefined;
264329
}
@@ -282,7 +347,11 @@ export abstract class BaseENR {
282347
maBuf.set(protoBuf, 1 + ipByteLen);
283348
maBuf.set(protoVal, 1 + ipByteLen + protoBuf.length);
284349

285-
return multiaddr(maBuf);
350+
const ma = multiaddr(maBuf);
351+
if (isQuic) {
352+
return ma.encapsulate("/quic-v1");
353+
}
354+
return ma;
286355
}
287356
async getFullMultiaddr(protocol: Protocol): Promise<Multiaddr | undefined> {
288357
const locationMultiaddr = this.getLocationMultiaddr(protocol);
@@ -504,6 +573,16 @@ export class SignableENR extends BaseENR {
504573
this.set("udp", portToBuf(port));
505574
}
506575
}
576+
get quic(): number | undefined {
577+
return getProtocolValue(this.kvs, "quic");
578+
}
579+
set quic(port: number | undefined) {
580+
if (port === undefined) {
581+
this.delete("quic");
582+
} else {
583+
this.set("quic", portToBuf(port));
584+
}
585+
}
507586
get ip6(): string | undefined {
508587
return getIPValue(this.kvs, "ip6", "ip6");
509588
}
@@ -534,23 +613,25 @@ export class SignableENR extends BaseENR {
534613
this.set("udp6", portToBuf(port));
535614
}
536615
}
537-
setLocationMultiaddr(multiaddr: Multiaddr): void {
538-
const protoNames = multiaddr.protoNames();
539-
if (protoNames.length !== 2 && protoNames[1] !== "udp" && protoNames[1] !== "tcp") {
540-
throw new Error("Invalid multiaddr");
541-
}
542-
const tuples = multiaddr.tuples();
543-
if (!tuples[0][1] || !tuples[1][1]) {
544-
throw new Error("Invalid multiaddr");
616+
get quic6(): number | undefined {
617+
return getProtocolValue(this.kvs, "quic6");
618+
}
619+
set quic6(port: number | undefined) {
620+
if (port === undefined) {
621+
this.delete("quic6");
622+
} else {
623+
this.set("quic6", portToBuf(port));
545624
}
625+
}
626+
setLocationMultiaddr(multiaddr: Multiaddr): void {
627+
const { family, ip, protoName, protoVal } = parseLocationMultiaddr(multiaddr);
546628

547-
// IPv4
548-
if (tuples[0][0] === 4) {
549-
this.set("ip", tuples[0][1]);
550-
this.set(protoNames[1], tuples[1][1]);
629+
if (family === 4) {
630+
this.set("ip", ip);
631+
this.set(protoName, protoVal);
551632
} else {
552-
this.set("ip6", tuples[0][1]);
553-
this.set(protoNames[1] + "6", tuples[1][1]);
633+
this.set("ip6", ip);
634+
this.set(protoName + "6", protoVal);
554635
}
555636
}
556637

packages/enr/test/unit/enr.test.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,21 +117,47 @@ describe("ENR multiaddr support", () => {
117117
expect(record.kvs.get("ip")).to.deep.equal(tuples1[0][1]);
118118
expect(record.kvs.get("tcp")).to.deep.equal(tuples1[1][1]);
119119
});
120+
it("should get / set QUIC multiaddr", () => {
121+
const multi0 = multiaddr("/ip4/127.0.0.1/udp/30303/quic-v1");
122+
const tuples0 = multi0.tuples();
123+
124+
if (!tuples0[0][1] || !tuples0[1][1]) {
125+
throw new Error("invalid multiaddr");
126+
}
127+
128+
// set underlying records
129+
record.set("ip", tuples0[0][1]);
130+
record.set("quic", tuples0[1][1]);
131+
// and get the multiaddr
132+
expect(record.getLocationMultiaddr("quic")!.toString()).to.equal(multi0.toString());
133+
// set the multiaddr
134+
const multi1 = multiaddr("/ip4/0.0.0.0/udp/30300/quic-v1");
135+
record.setLocationMultiaddr(multi1);
136+
// and get the multiaddr
137+
expect(record.getLocationMultiaddr("quic")!.toString()).to.equal(multi1.toString());
138+
// and get the underlying records
139+
const tuples1 = multi1.tuples();
140+
expect(record.kvs.get("ip")).to.deep.equal(tuples1[0][1]);
141+
expect(record.kvs.get("quic")).to.deep.equal(tuples1[1][1]);
142+
});
120143

121144
describe("location multiaddr", async () => {
122145
const ip4 = "127.0.0.1";
123146
const ip6 = "::1";
124147
const tcp = 8080;
125148
const udp = 8080;
149+
const quic = 8081;
126150

127151
const peerId = await createSecp256k1PeerId();
128152
const enr = SignableENR.createFromPeerId(peerId);
129153
enr.ip = ip4;
130154
enr.ip6 = ip6;
131155
enr.tcp = tcp;
132156
enr.udp = udp;
157+
enr.quic = quic;
133158
enr.tcp6 = tcp;
134159
enr.udp6 = udp;
160+
enr.quic6 = quic;
135161

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

170+
it("should properly create location multiaddrs - quic4", () => {
171+
expect(enr.getLocationMultiaddr("quic4")).to.deep.equal(multiaddr(`/ip4/${ip4}/udp/${quic}/quic-v1`));
172+
});
173+
144174
it("should properly create location multiaddrs - udp6", () => {
145175
expect(enr.getLocationMultiaddr("udp6")).to.deep.equal(multiaddr(`/ip6/${ip6}/udp/${udp}`));
146176
});
@@ -149,6 +179,10 @@ describe("ENR multiaddr support", () => {
149179
expect(enr.getLocationMultiaddr("tcp6")).to.deep.equal(multiaddr(`/ip6/${ip6}/tcp/${tcp}`));
150180
});
151181

182+
it("should properly create location multiaddrs - quic6", () => {
183+
expect(enr.getLocationMultiaddr("quic6")).to.deep.equal(multiaddr(`/ip6/${ip6}/udp/${quic}/quic-v1`));
184+
});
185+
152186
it("should properly create location multiaddrs - udp", () => {
153187
// default to ip4
154188
expect(enr.getLocationMultiaddr("udp")).to.deep.equal(multiaddr(`/ip4/${ip4}/udp/${udp}`));
@@ -174,6 +208,19 @@ describe("ENR multiaddr support", () => {
174208
expect(enr.getLocationMultiaddr("tcp")).to.deep.equal(multiaddr(`/ip4/${ip4}/tcp/${tcp}`));
175209
enr.ip6 = ip6;
176210
});
211+
212+
it("should properly create location multiaddrs - quic", () => {
213+
// default to ip4
214+
expect(enr.getLocationMultiaddr("quic")).to.deep.equal(multiaddr(`/ip4/${ip4}/udp/${quic}/quic-v1`));
215+
// if ip6 is set, use it
216+
enr.ip = undefined;
217+
expect(enr.getLocationMultiaddr("quic")).to.deep.equal(multiaddr(`/ip6/${ip6}/udp/${quic}/quic-v1`));
218+
// if ip6 does not exist, use ip4
219+
enr.ip6 = undefined;
220+
enr.ip = ip4;
221+
expect(enr.getLocationMultiaddr("quic")).to.deep.equal(multiaddr(`/ip4/${ip4}/udp/${quic}/quic-v1`));
222+
enr.ip6 = ip6;
223+
});
177224
});
178225
});
179226

0 commit comments

Comments
 (0)