Skip to content

Commit

Permalink
feat: support ip6zone (#226)
Browse files Browse the repository at this point in the history
Adds support for the `ip6zone` tuple.

Fixes #134

Co-authored-by: Alex Potsides <alex@achingbrain.net>
  • Loading branch information
vogdb and achingbrain authored Jan 17, 2023
1 parent e2d05a6 commit cfacedb
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 23 deletions.
4 changes: 4 additions & 0 deletions src/convert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
75 changes: 52 additions & 23 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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('/'))
}

/**
Expand Down Expand Up @@ -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
}
Expand Down
47 changes: 47 additions & 0 deletions test/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {})

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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', () => {
Expand Down

0 comments on commit cfacedb

Please sign in to comment.