mirror of https://codeberg.org/pzp/pzp-net.git
use multiaddr instead of multiserver addresses
This commit is contained in:
parent
4101e023cd
commit
c2d1adb19e
|
@ -2,10 +2,11 @@ const debug = require('debug')('ppppp:net:connections')
|
|||
const createNotify = require('pull-notify')
|
||||
const run = require('promisify-tuple')
|
||||
const IP = require('ip')
|
||||
const Multiaddr = require('./multiaddr')
|
||||
|
||||
/**
|
||||
* @typedef {import('./index').RpcConnectListener} RpcConnectListener
|
||||
* @typedef {import('./index').Address} Address
|
||||
* @typedef {import('./index').Multiaddr} Multiaddr
|
||||
* @typedef {import('./index').RPC} RPC
|
||||
* @typedef {import('./index').Peer} Peer
|
||||
* @typedef {import('./infos').Info} Info
|
||||
|
@ -17,7 +18,7 @@ const IP = require('ip')
|
|||
* | 'connecting-failed'
|
||||
* | 'disconnecting'
|
||||
* | 'disconnected';
|
||||
* address: Address;
|
||||
* multiaddr: Multiaddr;
|
||||
* pubkey: string | undefined;
|
||||
* details?: any;
|
||||
* }} ConnectionEvent
|
||||
|
@ -37,12 +38,12 @@ class Connections {
|
|||
#closed
|
||||
/** @type {NotifyEvent} */
|
||||
#notifyEvent
|
||||
/** @type {Map<Address, RPC>} */
|
||||
/** @type {Map<Multiaddr, RPC>} */
|
||||
#rpcs
|
||||
|
||||
/**
|
||||
* Used only to schedule a connect when a disconnect is in progress.
|
||||
* @type {Set<Address>}
|
||||
* @type {Set<Multiaddr>}
|
||||
*/
|
||||
#connectRetries
|
||||
|
||||
|
@ -62,7 +63,7 @@ class Connections {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {Address} address
|
||||
* @param {string} address
|
||||
* @returns {Info['inferredType']}
|
||||
*/
|
||||
static inferPeerType(address) {
|
||||
|
@ -111,28 +112,29 @@ class Connections {
|
|||
}
|
||||
|
||||
/**
|
||||
* @type {(address: Address, rpc: RPC, weAreClient: boolean) => void}
|
||||
* @type {(address: string, rpc: RPC, weAreClient: boolean) => void}
|
||||
*/
|
||||
#prepareConnectedRPC = (address, rpc, weAreClient) => {
|
||||
const initiator = weAreClient ? 'we' : 'they'
|
||||
debug('Connected to %s, %s initiated it', address, initiator)
|
||||
const multiaddr = Multiaddr.fromMs(address)
|
||||
debug('Connected to %s, %s initiated it', multiaddr, initiator)
|
||||
|
||||
const pubkey = Connections.extractSHSEPubkey(address)
|
||||
this.#rpcs.set(address, rpc)
|
||||
this.#rpcs.set(multiaddr, rpc)
|
||||
rpc.once('closed', () => {
|
||||
debug('Disconnected from %s', address)
|
||||
this.#rpcs.delete(address)
|
||||
this.#infos.update(address, { state: 'disconnected' })
|
||||
this.#notifyEvent({ type: 'disconnected', address, pubkey })
|
||||
debug('Disconnected from %s', multiaddr)
|
||||
this.#rpcs.delete(multiaddr)
|
||||
this.#infos.update(multiaddr, { state: 'disconnected' })
|
||||
this.#notifyEvent({ type: 'disconnected', multiaddr, pubkey })
|
||||
this.#infos.emit()
|
||||
})
|
||||
|
||||
const state = /**@type {Info['state']}*/ ('connected')
|
||||
const inferredType = Connections.inferPeerType(address)
|
||||
this.#infos.update(address, { state, inferredType })
|
||||
this.#infos.update(multiaddr, { state, inferredType })
|
||||
this.#notifyEvent({
|
||||
type: state,
|
||||
address,
|
||||
multiaddr,
|
||||
pubkey,
|
||||
details: { rpc, weAreClient },
|
||||
})
|
||||
|
@ -140,37 +142,38 @@ class Connections {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {string} address
|
||||
* @param {Multiaddr} multiaddr
|
||||
* @returns {Promise<RPC>}
|
||||
*/
|
||||
async connect(address) {
|
||||
async connect(multiaddr) {
|
||||
this.#assertNotClosed()
|
||||
|
||||
const prevInfo = this.#infos.get(address)
|
||||
const address = Multiaddr.toMs(multiaddr)
|
||||
const prevInfo = this.#infos.get(multiaddr)
|
||||
switch (prevInfo?.state ?? 'disconnected') {
|
||||
case 'connected': {
|
||||
const rpc = this.#rpcs.get(address)
|
||||
const rpc = this.#rpcs.get(multiaddr)
|
||||
if (!rpc) {
|
||||
// prettier-ignore
|
||||
throw new Error(`Failed to connect to ${address} due to inconsistent internal state`);
|
||||
throw new Error(`Failed to connect to ${multiaddr} due to inconsistent internal state`);
|
||||
}
|
||||
return rpc
|
||||
}
|
||||
|
||||
case 'disconnecting': {
|
||||
// If disconnecting, schedule a connect() after disconnection completed
|
||||
this.#connectRetries.add(address)
|
||||
this.#connectRetries.add(multiaddr)
|
||||
// note: control flow should fall through below!
|
||||
}
|
||||
case 'connecting': {
|
||||
return new Promise((resolve, reject) => {
|
||||
let timeout = 100
|
||||
const checkAgain = () => {
|
||||
const rpc = this.#rpcs.get(address)
|
||||
const rpc = this.#rpcs.get(multiaddr)
|
||||
if (rpc) resolve(rpc)
|
||||
else if (timeout > 5 * 60e3) {
|
||||
// prettier-ignore
|
||||
reject(new Error(`Failed to connect to ${address} after waiting a long time`))
|
||||
reject(new Error(`Failed to connect to ${multiaddr} after waiting a long time`))
|
||||
} else {
|
||||
timeout *= 2
|
||||
setTimeout(checkAgain, timeout)
|
||||
|
@ -181,20 +184,20 @@ class Connections {
|
|||
}
|
||||
|
||||
case 'disconnected': {
|
||||
debug('Connecting to %s', address)
|
||||
debug('Connecting to %s', multiaddr)
|
||||
const state = /**@type {Info['state']}*/ ('connecting')
|
||||
const pubkey = Connections.extractSHSEPubkey(address)
|
||||
this.#infos.update(address, { state })
|
||||
this.#notifyEvent({ type: state, address, pubkey })
|
||||
this.#infos.update(multiaddr, { state })
|
||||
this.#notifyEvent({ type: state, multiaddr, pubkey })
|
||||
this.#infos.emit()
|
||||
|
||||
const [err, rpc] = await run(this.#peer.connect)(address)
|
||||
if (err) {
|
||||
this.#infos.update(address, { state: 'disconnected' })
|
||||
debug('Failed to connect to %s because: %s', address, err.message)
|
||||
this.#infos.update(multiaddr, { state: 'disconnected' })
|
||||
debug('Failed to connect to %s because: %s', multiaddr, err.message)
|
||||
this.#notifyEvent({
|
||||
type: 'connecting-failed',
|
||||
address,
|
||||
multiaddr,
|
||||
pubkey,
|
||||
details: err,
|
||||
})
|
||||
|
@ -202,15 +205,15 @@ class Connections {
|
|||
throw err
|
||||
}
|
||||
|
||||
const concurrentInfo = this.#infos.get(address)
|
||||
const concurrentInfo = this.#infos.get(multiaddr)
|
||||
if (!concurrentInfo || concurrentInfo.state !== 'connected') {
|
||||
this.#prepareConnectedRPC(address, rpc, true)
|
||||
return rpc
|
||||
} else {
|
||||
const rpc2 = this.#rpcs.get(address)
|
||||
const rpc2 = this.#rpcs.get(multiaddr)
|
||||
if (!rpc2) {
|
||||
// prettier-ignore
|
||||
throw new Error(`Failed to connect to ${address} due to inconsistent internal state`);
|
||||
throw new Error(`Failed to connect to ${multiaddr} due to inconsistent internal state`);
|
||||
}
|
||||
return rpc2
|
||||
}
|
||||
|
@ -218,20 +221,21 @@ class Connections {
|
|||
|
||||
default: {
|
||||
// prettier-ignore
|
||||
debug('Unexpected control flow, peer %s has bad state %o', address, prevInfo)
|
||||
debug('Unexpected control flow, peer %s has bad state %o', multiaddr, prevInfo)
|
||||
// prettier-ignore
|
||||
throw new Error(`Unexpected control flow, peer ${address} has bad state "${prevInfo?.state ?? '?'}"`)
|
||||
throw new Error(`Unexpected control flow, peer ${multiaddr} has bad state "${prevInfo?.state ?? '?'}"`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Address} address
|
||||
* @param {Multiaddr} multiaddr
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async disconnect(address) {
|
||||
async disconnect(multiaddr) {
|
||||
this.#assertNotClosed()
|
||||
const prevInfo = this.#infos.get(address)
|
||||
const address = Multiaddr.toMs(multiaddr)
|
||||
const prevInfo = this.#infos.get(multiaddr)
|
||||
if (!prevInfo || prevInfo?.state === 'disconnected') return false
|
||||
if (prevInfo.state === 'disconnecting') return false
|
||||
|
||||
|
@ -241,7 +245,7 @@ class Connections {
|
|||
rpc = await new Promise((resolve) => {
|
||||
let timeout = 100
|
||||
const checkAgain = () => {
|
||||
const rpc = this.#rpcs.get(address)
|
||||
const rpc = this.#rpcs.get(multiaddr)
|
||||
if (rpc) resolve(rpc)
|
||||
else {
|
||||
timeout *= 2
|
||||
|
@ -252,20 +256,20 @@ class Connections {
|
|||
checkAgain()
|
||||
})
|
||||
} else if (prevInfo.state === 'connected') {
|
||||
const maybeRPC = this.#rpcs.get(address)
|
||||
const maybeRPC = this.#rpcs.get(multiaddr)
|
||||
if (!maybeRPC) {
|
||||
// prettier-ignore
|
||||
throw new Error(`Failed to disconnect from ${address} due to inconsistent internal state`);
|
||||
throw new Error(`Failed to disconnect from ${multiaddr} due to inconsistent internal state`);
|
||||
} else {
|
||||
rpc = maybeRPC
|
||||
}
|
||||
}
|
||||
|
||||
debug('Disconnecting from %s', address)
|
||||
debug('Disconnecting from %s', multiaddr)
|
||||
const state = /**@type {Info['state']}*/ ('disconnecting')
|
||||
const pubkey = Connections.extractSHSEPubkey(address)
|
||||
this.#infos.update(address, { state })
|
||||
this.#notifyEvent({ type: state, address, pubkey })
|
||||
this.#infos.update(multiaddr, { state })
|
||||
this.#notifyEvent({ type: state, multiaddr, pubkey })
|
||||
this.#infos.emit()
|
||||
// @ts-ignore
|
||||
await run(rpc.close)(true)
|
||||
|
@ -273,9 +277,9 @@ class Connections {
|
|||
|
||||
// Re-connect because while disconnect() was running,
|
||||
// someone called connect()
|
||||
if (this.#connectRetries.has(address)) {
|
||||
this.#connectRetries.delete(address)
|
||||
this.connect(address)
|
||||
if (this.#connectRetries.has(multiaddr)) {
|
||||
this.#connectRetries.delete(multiaddr)
|
||||
this.connect(multiaddr)
|
||||
}
|
||||
|
||||
return true
|
||||
|
|
20
lib/glue.js
20
lib/glue.js
|
@ -3,7 +3,7 @@ const stats = require('statistics')
|
|||
const ping = require('pull-ping')
|
||||
|
||||
/**
|
||||
* @typedef {import('./index').Address} Address
|
||||
* @typedef {import('./index').Multiaddr} Multiaddr
|
||||
* @typedef {import('./index').RPC} RPC
|
||||
* @typedef {import('./index').Peer} Peer
|
||||
* @typedef {import('./connections')} Connections
|
||||
|
@ -19,13 +19,13 @@ const PROGRAM_STARTUP = Date.now()
|
|||
*/
|
||||
function glue(infos, connections) {
|
||||
/**
|
||||
* @param {Address} address
|
||||
* @param {Multiaddr} multiaddr
|
||||
* @param {RPC} rpc
|
||||
*/
|
||||
function setupPing(address, rpc) {
|
||||
function setupPing(multiaddr, rpc) {
|
||||
const PING_TIMEOUT = 5 * 6e4 // 5 minutes
|
||||
const pp = ping({ serve: true, timeout: PING_TIMEOUT }, () => {})
|
||||
infos.updateStats(address, () => ({
|
||||
infos.updateStats(multiaddr, () => ({
|
||||
ping: {
|
||||
rtt: pp.rtt,
|
||||
skew: pp.skew,
|
||||
|
@ -36,7 +36,7 @@ function glue(infos, connections) {
|
|||
rpc.net.ping({ timeout: PING_TIMEOUT }, (err, _) => {
|
||||
console.warn('remote peer ping err', err)
|
||||
// if (err?.name === 'TypeError') {
|
||||
// infos.update(address, {stats: {ping: {fail: true}}});
|
||||
// infos.update(multiaddr, {stats: {ping: {fail: true}}});
|
||||
// }
|
||||
}),
|
||||
pp
|
||||
|
@ -47,7 +47,7 @@ function glue(infos, connections) {
|
|||
* @param {Event} ev
|
||||
*/
|
||||
function onConnectingFailed(ev) {
|
||||
infos.updateStats(ev.address, (prevStats) => ({
|
||||
infos.updateStats(ev.multiaddr, (prevStats) => ({
|
||||
failure: (prevStats?.failure ?? 0) + 1,
|
||||
stateChange: Date.now(),
|
||||
duration: stats(prevStats?.duration, 0),
|
||||
|
@ -58,18 +58,18 @@ function glue(infos, connections) {
|
|||
* @param {Event} ev
|
||||
*/
|
||||
function onConnected(ev) {
|
||||
infos.updateStats(ev.address, () => ({
|
||||
infos.updateStats(ev.multiaddr, () => ({
|
||||
stateChange: Date.now(),
|
||||
failure: 0,
|
||||
}))
|
||||
if (ev.details.weAreClient) setupPing(ev.address, ev.details.rpc)
|
||||
if (ev.details.weAreClient) setupPing(ev.multiaddr, ev.details.rpc)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Event} ev
|
||||
*/
|
||||
function bumpStateChange(ev) {
|
||||
infos.updateStats(ev.address, () => ({
|
||||
infos.updateStats(ev.multiaddr, () => ({
|
||||
stateChange: Date.now(),
|
||||
}))
|
||||
}
|
||||
|
@ -78,7 +78,7 @@ function glue(infos, connections) {
|
|||
* @param {Event} ev
|
||||
*/
|
||||
function onDisconnected(ev) {
|
||||
infos.updateStats(ev.address, (prevStats) => ({
|
||||
infos.updateStats(ev.multiaddr, (prevStats) => ({
|
||||
stateChange: Date.now(),
|
||||
duration: stats(
|
||||
prevStats?.duration,
|
||||
|
|
42
lib/index.js
42
lib/index.js
|
@ -9,7 +9,7 @@ const glue = require('./glue')
|
|||
/**
|
||||
* @typedef {import('pull-stream').Duplex<unknown, unknown>} Duplex
|
||||
* @typedef {import('./connections').ConnectionEvent} ConnectionEvent
|
||||
* @typedef {string} Address
|
||||
* @typedef {`/${string}`} Multiaddr
|
||||
* @typedef {(rpc: RPC, weAreClient: boolean) => void} RpcConnectListener
|
||||
* @typedef {{
|
||||
* shse: {pubkey: string};
|
||||
|
@ -24,12 +24,14 @@ const glue = require('./glue')
|
|||
* multiserver: {
|
||||
* parse(address: string): any
|
||||
* },
|
||||
* }} Peer
|
||||
* @typedef {Peer & {
|
||||
* stream: {address: string}
|
||||
* net: {
|
||||
* ping(opts: {timeout: number}, cb: CB<void>): Duplex;
|
||||
* listen(): import('pull-stream').Source<ConnectionEvent>;
|
||||
* },
|
||||
* }} Peer
|
||||
* @typedef {Peer & {stream: {address: string}}} RPC
|
||||
* }} RPC
|
||||
* @typedef {{
|
||||
* global: {
|
||||
* path?: string
|
||||
|
@ -97,56 +99,56 @@ function initNet(peer, config) {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {Address} address
|
||||
* @param {Multiaddr} multiaddr
|
||||
* @param {Partial<Info>} info
|
||||
*/
|
||||
function stage(address, info) {
|
||||
function stage(multiaddr, info) {
|
||||
if (info.state) throw new Error('Cannot stage peer info with "state" field')
|
||||
if (infos.has(address)) {
|
||||
if (infos.has(multiaddr)) {
|
||||
return false
|
||||
} else {
|
||||
infos.update(address, info)
|
||||
infos.update(multiaddr, info)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Address} address
|
||||
* @param {Multiaddr} multiaddr
|
||||
* @param {CB<RPC>=} cb
|
||||
*/
|
||||
function connect(address, cb) {
|
||||
connections.connect(address).then(
|
||||
function connect(multiaddr, cb) {
|
||||
connections.connect(multiaddr).then(
|
||||
(result) => cb?.(null, result),
|
||||
(err) => cb?.(err)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Address} address
|
||||
* @param {Multiaddr} multiaddr
|
||||
* @param {CB<boolean>=} cb
|
||||
*/
|
||||
function disconnect(address, cb) {
|
||||
return connections.disconnect(address).then(
|
||||
function disconnect(multiaddr, cb) {
|
||||
return connections.disconnect(multiaddr).then(
|
||||
(result) => cb?.(null, result),
|
||||
(err) => cb?.(err)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Address} address
|
||||
* @param {Multiaddr} multiaddr
|
||||
*/
|
||||
function forget(address) {
|
||||
disconnect(address, () => {
|
||||
infos.remove(address)
|
||||
function forget(multiaddr) {
|
||||
disconnect(multiaddr, () => {
|
||||
infos.remove(multiaddr)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Address} address
|
||||
* @param {Multiaddr} multiaddr
|
||||
* @param {Info} info
|
||||
*/
|
||||
function updateInfo(address, info) {
|
||||
infos.update(address, info)
|
||||
function updateInfo(multiaddr, info) {
|
||||
infos.update(multiaddr, info)
|
||||
}
|
||||
|
||||
function listen() {
|
||||
|
|
54
lib/infos.js
54
lib/infos.js
|
@ -9,7 +9,7 @@ const Obz = require('obz')
|
|||
*/
|
||||
|
||||
/**
|
||||
* @typedef {import('./index').Address} Address
|
||||
* @typedef {import('./index').Multiaddr} Multiaddr
|
||||
* @typedef {import('./stats').StatsInfo} StatsInfo
|
||||
* @typedef {{
|
||||
* state: 'connected' | 'disconnected' | 'connecting' | 'disconnecting',
|
||||
|
@ -19,11 +19,11 @@ const Obz = require('obz')
|
|||
*/
|
||||
|
||||
class Infos {
|
||||
/** @type {Map<Address, Info>} */
|
||||
/** @type {Map<Multiaddr, Info>} */
|
||||
#map
|
||||
/** @type {ReturnType<createNotify>} */
|
||||
#notify
|
||||
/** @type {Obz<Address>} */
|
||||
/** @type {Obz<Multiaddr>} */
|
||||
#onStatsUpdated
|
||||
|
||||
constructor() {
|
||||
|
@ -33,76 +33,76 @@ class Infos {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {Address} address
|
||||
* @param {Multiaddr} multiaddr
|
||||
* @returns {Info | undefined}
|
||||
*/
|
||||
get(address) {
|
||||
return this.#map.get(address)
|
||||
get(multiaddr) {
|
||||
return this.#map.get(multiaddr)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Address} address
|
||||
* @param {Multiaddr} multiaddr
|
||||
* @returns {boolean}
|
||||
*/
|
||||
has(address) {
|
||||
return this.#map.has(address)
|
||||
has(multiaddr) {
|
||||
return this.#map.has(multiaddr)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Address} address
|
||||
* @param {Multiaddr} multiaddr
|
||||
* @param {Partial<Info>} info
|
||||
* @returns {void}
|
||||
*/
|
||||
update(address, info) {
|
||||
update(multiaddr, info) {
|
||||
const hasNewStats = !!info.stats
|
||||
const prevInfo = this.#map.get(address)
|
||||
const prevInfo = this.#map.get(multiaddr)
|
||||
if (prevInfo) {
|
||||
for (const key of Object.keys(info)) {
|
||||
const k = /**@type {keyof Info}*/ (key)
|
||||
if (typeof info[k] === 'undefined') delete info[k]
|
||||
}
|
||||
this.#map.set(address, { ...prevInfo, ...info })
|
||||
this.#map.set(multiaddr, { ...prevInfo, ...info })
|
||||
} else if (!info.state) {
|
||||
this.#map.set(address, { ...info, state: 'disconnected' })
|
||||
this.#map.set(multiaddr, { ...info, state: 'disconnected' })
|
||||
} else {
|
||||
this.#map.set(address, /**@type {Info}*/ (info))
|
||||
this.#map.set(multiaddr, /**@type {Info}*/ (info))
|
||||
}
|
||||
if (hasNewStats) {
|
||||
this.#onStatsUpdated.set(address)
|
||||
this.#onStatsUpdated.set(multiaddr)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Address} address
|
||||
* @param {Multiaddr} multiaddr
|
||||
* @param {(prevStats: Partial<Info['stats']>) => Partial<Info['stats']>} getStats
|
||||
* @returns {void}
|
||||
*/
|
||||
updateStats(address, getStats) {
|
||||
const prevInfo = this.#map.get(address)
|
||||
updateStats(multiaddr, getStats) {
|
||||
const prevInfo = this.#map.get(multiaddr)
|
||||
if (!prevInfo) return
|
||||
this.#map.set(address, {
|
||||
this.#map.set(multiaddr, {
|
||||
...prevInfo,
|
||||
stats: {
|
||||
...prevInfo?.stats,
|
||||
...getStats(prevInfo?.stats),
|
||||
},
|
||||
})
|
||||
this.#onStatsUpdated.set(address)
|
||||
this.#onStatsUpdated.set(multiaddr)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Parameters<Obz<Address>>[0]} listener
|
||||
* @param {Parameters<Obz<Multiaddr>>[0]} listener
|
||||
*/
|
||||
onStatsUpdated(listener) {
|
||||
return this.#onStatsUpdated(listener)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Address} address
|
||||
* @param {Multiaddr} multiaddr
|
||||
*/
|
||||
remove(address) {
|
||||
this.#map.delete(address)
|
||||
this.#onStatsUpdated.set(address)
|
||||
remove(multiaddr) {
|
||||
this.#map.delete(multiaddr)
|
||||
this.#onStatsUpdated.set(multiaddr)
|
||||
}
|
||||
|
||||
size() {
|
||||
|
@ -118,7 +118,7 @@ class Infos {
|
|||
}
|
||||
|
||||
/**
|
||||
* @returns {pull.Source<[Address, Info]>}
|
||||
* @returns {pull.Source<[Multiaddr, Info]>}
|
||||
*/
|
||||
liveEntries() {
|
||||
return pullConcat([
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
const IP = require('ip')
|
||||
|
||||
const Multiaddr = {
|
||||
/**
|
||||
* Converts (legacy) [multiserver](https://github.com/ssbc/multiserver-address)
|
||||
* addresses to [multiaddr](https://multiformats.io/multiaddr/) (modern).
|
||||
* @param {string} msaddr
|
||||
* @returns {`/${string}`}
|
||||
*/
|
||||
fromMs(msaddr) {
|
||||
const [msTransport, msTransform] = msaddr.split('~')
|
||||
const [label1, host, port] = msTransport.split(':')
|
||||
|
||||
const hostFormat = IP.isV4Format(host)
|
||||
? 'ip4'
|
||||
: IP.isV6Format('ipv6')
|
||||
? 'ip6'
|
||||
: 'dns'
|
||||
const transport = label1 === 'net' ? 'tcp' : label1 === 'ws' ? 'ws' : null
|
||||
if (!transport) throw new Error(`Unknown transport "${label1}"`)
|
||||
const soFar = `${hostFormat}/${host}/${transport}/${port}`
|
||||
|
||||
if (msTransform) {
|
||||
const [label2, pubkey, token] = msTransform.split(':')
|
||||
if (label2 !== 'shse') throw new Error(`Unknown transform "${label2}"`)
|
||||
if (token) {
|
||||
return `/${soFar}/shse/${pubkey}.${token}`
|
||||
} else {
|
||||
return `/${soFar}/shse/${pubkey}`
|
||||
}
|
||||
} else {
|
||||
return `/${soFar}`
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Converts [multiaddr](https://multiformats.io/multiaddr/) (modern) to
|
||||
* [multiserver](https://github.com/ssbc/multiserver-address) address (legacy).
|
||||
* @param {`/${string}`} multiaddr
|
||||
* @returns {string}
|
||||
*/
|
||||
toMs(multiaddr) {
|
||||
if (!multiaddr.startsWith('/')) {
|
||||
// prettier-ignore
|
||||
throw new Error(`Invalid multiaddr "${multiaddr}"`)
|
||||
}
|
||||
const [, , host, transport, port, transform, cred] = multiaddr.split('/')
|
||||
const label1 =
|
||||
transport === 'tcp' ? 'net' : transport === 'ws' ? 'ws' : null
|
||||
if (!label1) throw new Error(`Unknown transport "${transport}"`)
|
||||
const soFar = `${label1}:${host}:${port}`
|
||||
if (transform) {
|
||||
// prettier-ignore
|
||||
if (transform !== 'shse') throw new Error(`Unknown transform "${transform}"`)
|
||||
const [pubkey, token] = cred.split('.')
|
||||
if (token) {
|
||||
return `${soFar}~shse:${pubkey}:${token}`
|
||||
} else {
|
||||
return `${soFar}~shse:${pubkey}`
|
||||
}
|
||||
} else {
|
||||
return soFar
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = Multiaddr
|
18
lib/stats.js
18
lib/stats.js
|
@ -4,7 +4,7 @@ const debug = require('debug')('ppppp:net:stats')
|
|||
const atomic = require('atomic-file-rw')
|
||||
|
||||
/**
|
||||
* @typedef {import('./index').Address} Address
|
||||
* @typedef {import('./index').Multiaddr} Multiaddr
|
||||
* @typedef {import('./infos')} Infos
|
||||
* @typedef {import('statistics').Statistics} Statistics
|
||||
* @typedef {{
|
||||
|
@ -42,7 +42,7 @@ const SelfHealingJSONCodec = {
|
|||
},
|
||||
/**
|
||||
* @param {any} input
|
||||
* @returns {Record<string, any>}
|
||||
* @returns {Record<`/${string}`, any>}
|
||||
*/
|
||||
decode(input) {
|
||||
if (!input) return {}
|
||||
|
@ -114,8 +114,10 @@ class Stats {
|
|||
return
|
||||
} else if (fileContents) {
|
||||
const vals = SelfHealingJSONCodec.decode(fileContents)
|
||||
for (const [address, statsInfo] of Object.entries(vals)) {
|
||||
this.#infos.update(address, { stats: statsInfo })
|
||||
for (const [multiaddr, statsInfo] of Object.entries(vals)) {
|
||||
this.#infos.update(/**@type {`/${string}`}*/ (multiaddr), {
|
||||
stats: statsInfo,
|
||||
})
|
||||
}
|
||||
this.#loadedResolve(true)
|
||||
debug('Loaded conn.json into ConnDB in memory')
|
||||
|
@ -180,10 +182,10 @@ class Stats {
|
|||
*/
|
||||
#writeToDisk(cb) {
|
||||
debug(`Begun serializing and writing ${Stats.FILENAME}`)
|
||||
const record = /**@type {Record<Address, StatsInfo>}*/ ({})
|
||||
for (let [address, info] of this.#infos.entries()) {
|
||||
const record = /**@type {Record<Multiaddr, StatsInfo>}*/ ({})
|
||||
for (let [multiaddr, info] of this.#infos.entries()) {
|
||||
if (info.stats) {
|
||||
record[address] = info.stats
|
||||
record[multiaddr] = info.stats
|
||||
}
|
||||
}
|
||||
const json = SelfHealingJSONCodec.encode(record)
|
||||
|
@ -198,7 +200,7 @@ class Stats {
|
|||
this.#closed = true
|
||||
this.#cancelScheduleWrite()
|
||||
this.#writeToDisk()
|
||||
;/**@type {any}*/ (this).#infos = void 0
|
||||
;/**@type {any}*/ (this.#infos) = void 0
|
||||
debug('Closed the Stats instance')
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"net:staltz.com:8008~noauth": {
|
||||
"/dns/staltz.com/tcp/8008": {
|
||||
"source": "stored"
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"net:staltz.com:8008~noauth": {
|
||||
"/dns/staltz.com/tcp/8008": {
|
||||
"duration": {
|
||||
"mean": 0,
|
||||
"stdev": 0,
|
||||
|
|
|
@ -5,8 +5,8 @@ const Path = require('node:path')
|
|||
const p = require('node:util').promisify
|
||||
const { createPeerMock } = require('./util')
|
||||
|
||||
const TEST_ADDR =
|
||||
'net:localhost:9752~shse:EqTMFv7zm8hpPyAkj789qdJgqtz81AEbcinpAs24RRUC'
|
||||
const PUBKEY = 'EqTMFv7zm8hpPyAkj789qdJgqtz81AEbcinpAs24RRUC'
|
||||
const TEST_ADDR = `/ip4/127.0.0.1/tcp/9752/shse/${PUBKEY}`
|
||||
|
||||
test('Glueing together stats with connections', async (t) => {
|
||||
await t.test('stage() is ignored when peer already connected', async () => {
|
||||
|
|
|
@ -5,7 +5,7 @@ const pull = require('pull-stream')
|
|||
const { createPeer } = require('./util')
|
||||
|
||||
const PUBKEY = 'EqTMFv7zm8hpPyAkj789qdJgqtz81AEbcinpAs24RRUC'
|
||||
const TEST_ADDR = `net:localhost:9752~shse:${PUBKEY}`
|
||||
const TEST_ADDR = `/ip4/127.0.0.1/tcp/9752/shse/${PUBKEY}`
|
||||
|
||||
test('net', async (t) => {
|
||||
await t.test('connect() rejects given unreachable address', async () => {
|
||||
|
@ -68,11 +68,11 @@ test('net', async (t) => {
|
|||
++i
|
||||
if (i === 1) {
|
||||
assert.equal(ev.type, 'connecting', 'event.type ok')
|
||||
assert.equal(ev.address, TEST_ADDR, 'event.address ok')
|
||||
assert.equal(ev.multiaddr, TEST_ADDR, 'event.address ok')
|
||||
assert.equal(ev.pubkey, PUBKEY, 'event.pubkey ok')
|
||||
} else if (i === 2) {
|
||||
assert.equal(ev.type, 'connecting-failed', 'event.type ok')
|
||||
assert.equal(ev.address, TEST_ADDR, 'event.address ok')
|
||||
assert.equal(ev.multiaddr, TEST_ADDR, 'event.address ok')
|
||||
assert.ok(ev.details, 'event.details ok')
|
||||
assert.equal(ev.details.code, 'ECONNREFUSED', 'event.details err')
|
||||
queueMicrotask(resolve)
|
||||
|
|
|
@ -24,7 +24,7 @@ test('Stats', async (t) => {
|
|||
const entriesAfter = Array.from(infos.entries())
|
||||
assert.equal(entriesAfter.length, 1, 'after loaded(), there is data')
|
||||
const [address, info] = entriesAfter[0]
|
||||
assert.equal(address, 'net:staltz.com:8008~noauth', 'the address looks ok')
|
||||
assert.equal(address, '/dns/staltz.com/tcp/8008', 'the address looks ok')
|
||||
assert.equal(info.stats.source, 'stored', 'the info looks ok')
|
||||
|
||||
stats.close()
|
||||
|
@ -64,7 +64,7 @@ test('Stats', async (t) => {
|
|||
|
||||
const entries = Array.from(infos.entries())
|
||||
assert.equal(entries.length === 1, true, 'stats has one entry')
|
||||
assert.equal(entries[0][0], 'net:staltz.com:8008~noauth', 'entry addr ok')
|
||||
assert.equal(entries[0][0], '/dns/staltz.com/tcp/8008', 'entry addr ok')
|
||||
assert.ok(entries[0][1].stats.duration, 'entry stats.duration ok')
|
||||
})
|
||||
|
||||
|
|
Loading…
Reference in New Issue