diff --git a/src/convert.ts b/src/convert.ts index f7c83c49..ab24e4f4 100644 --- a/src/convert.ts +++ b/src/convert.ts @@ -38,6 +38,8 @@ export function convertToString (proto: number | string, buf: Uint8Array): strin case 4: // ipv4 case 41: // ipv6 return bytes2ip(buf) + case 42: // ipv6zone + return bytes2str(buf) case 6: // tcp case 273: // udp @@ -73,6 +75,8 @@ export function convertToBytes (proto: string | number, str: string): Uint8Array return ip2bytes(str) case 41: // ipv6 return ip2bytes(str) + case 42: // ipv6zone + return str2bytes(str) case 6: // tcp case 273: // udp diff --git a/src/index.ts b/src/index.ts index 59698a60..60b34603 100644 --- a/src/index.ts +++ b/src/index.ts @@ -30,11 +30,6 @@ const DNS_CODES = [ getProtocol('dnsaddr').code ] -const P2P_CODES = [ - getProtocol('p2p').code, - getProtocol('ipfs').code -] - /** * Protocols are present in the protocol table */ @@ -424,18 +419,31 @@ export function fromNodeAddress (addr: NodeAddress, transport: string): Multiadd if (transport == null) { throw new Error('requires transport protocol') } - let ip + let ip: string | undefined + let host = addr.address switch (addr.family) { case 4: ip = 'ip4' break case 6: ip = 'ip6' + + if (host.includes('%')) { + const parts = host.split('%') + + if (parts.length !== 2) { + throw Error('Multiple ip6 zones in multiaddr') + } + + host = parts[0] + const zone = parts[1] + ip = `/ip6zone/${zone}/ip6` + } break default: throw Error('Invalid addr family, should be 4 or 6.') } - return new DefaultMultiaddr('/' + [ip, addr.address, transport, addr.port].join('/')) + return new DefaultMultiaddr('/' + [ip, host, transport, addr.port].join('/')) } /** @@ -523,30 +531,51 @@ class DefaultMultiaddr implements Multiaddr { } toOptions (): MultiaddrObject { - const codes = this.protoCodes() - const parts = this.toString().split('/').slice(1) - let transport: string - let port: number + let family: 4 | 6 | undefined + let transport: string | undefined + let host: string | undefined + let port: number | undefined + let zone = '' + + const tcp = getProtocol('tcp') + const udp = getProtocol('udp') + const ip4 = getProtocol('ip4') + const ip6 = getProtocol('ip6') + const dns6 = getProtocol('dns6') + const ip6zone = getProtocol('ip6zone') + + for (const [code, value] of this.stringTuples()) { + if (code === ip6zone.code) { + zone = `%${value ?? ''}` + } - if (parts.length > 2) { // default to https when protocol & port are omitted from DNS addrs - if (DNS_CODES.includes(codes[0]) && P2P_CODES.includes(codes[1])) { - transport = getProtocol('tcp').name + if (DNS_CODES.includes(code)) { + transport = tcp.name port = 443 - } else { - transport = getProtocol(parts[2]).name - port = parseInt(parts[3]) + host = `${value ?? ''}${zone}` + family = code === dns6.code ? 6 : 4 } - } else if (DNS_CODES.includes(codes[0])) { - transport = getProtocol('tcp').name - port = 443 - } else { + + if (code === tcp.code || code === udp.code) { + transport = getProtocol(code).name + port = parseInt(value ?? '') + } + + if (code === ip4.code || code === ip6.code) { + transport = getProtocol(code).name + host = `${value ?? ''}${zone}` + family = code === ip6.code ? 6 : 4 + } + } + + if (family == null || transport == null || host == null || port == null) { throw new Error('multiaddr must have a valid format: "/{ip4, ip6, dns4, dns6, dnsaddr}/{address}/{tcp, udp}/{port}".') } const opts: MultiaddrObject = { - family: (codes[0] === 41 || codes[0] === 55) ? 6 : 4, - host: parts[1], + family, + host, transport, port } diff --git a/test/index.spec.ts b/test/index.spec.ts index 96100ced..24423f08 100644 --- a/test/index.spec.ts +++ b/test/index.spec.ts @@ -242,6 +242,13 @@ describe('variants', () => { expect(addr.toString()).to.equal(str) }) + it('ip6 + ip6zone', () => { + const str = '/ip6zone/x/ip6/fe80::1' + const addr = multiaddr(str) + expect(addr).to.have.property('bytes') + expect(addr.toString()).to.equal(str) + }) + it.skip('ip4 + dccp', () => {}) it.skip('ip6 + dccp', () => {}) @@ -382,6 +389,13 @@ describe('variants', () => { expect(addr.toString()).to.equal(str) }) + it('ip6 + ip6zone + udp + quic', () => { + const str = '/ip6zone/x/ip6/fe80::1/udp/1234/quic' + const addr = multiaddr(str) + expect(addr).to.have.property('bytes') + expect(addr.toString()).to.equal(str) + }) + it('unix', () => { const str = '/unix/a/b/c/d/e' const addr = multiaddr(str) @@ -562,6 +576,17 @@ describe('helpers', () => { port: 443 }) }) + + it('returns an options object from an address with an ip6 zone', () => { + expect( + multiaddr('/ip6zone/x/ip6/fe80::1/tcp/1234').toOptions() + ).to.be.eql({ + family: 6, + host: 'fe80::1%x', + transport: 'tcp', + port: 1234 + }) + }) }) describe('.protos', () => { @@ -804,6 +829,16 @@ describe('helpers', () => { }) }) + it('transforms an address with an ip6 zone', () => { + expect( + multiaddr('/ip6zone/x/ip6/fe80::1/tcp/1234').nodeAddress() + ).to.be.eql({ + address: 'fe80::1%x', + family: 6, + port: 1234 + }) + }) + it('throws on an invalid format address when the addr is not prefixed with a /', () => { expect( () => multiaddr('ip4/192.168.0.1/udp').nodeAddress() @@ -859,6 +894,18 @@ describe('helpers', () => { '/ip4/192.168.0.1/tcp/1234' ) }) + + it('parses a node address with an ip6zone', () => { + expect( + fromNodeAddress({ + address: 'fe80::1%x', + family: 6, + port: 1234 + }, 'tcp').toString() + ).to.be.eql( + '/ip6zone/x/ip6/fe80::1/tcp/1234' + ) + }) }) describe('.isThinWaistAddress', () => {